如何在淘宝客上做自己的网站,个人域名用来做淘宝客网站,手机怎么做网站免费的,品牌建设工作方案我们都学习和使用过模板#xff0c;而这篇文章我们来将一些更深入的知识。在此之前#xff0c;我们在使用C编程时可以看到模板是随处可见的#xff0c;它能支持泛型编程。模板包括函数模板和类模板#xff0c;我们有的人可能会说是模板函数和模板类#xff0c;但严格讲这样…我们都学习和使用过模板而这篇文章我们来将一些更深入的知识。在此之前我们在使用C编程时可以看到模板是随处可见的它能支持泛型编程。模板包括函数模板和类模板我们有的人可能会说是模板函数和模板类但严格讲这样说是错误的。而在我们实际使用中类模板用的场景是比函数模板多的如STL中vectorlist等都是类模板而算法中sortfind等是函数模板。
非类型模板参数
模板参数分为类型模板形参非类型形参。
类型形参出现在模板参数列表中跟在class或typename关键字之后的参数类型名称。我们之前一直使用的
非类型形参用一个常量作为类函数模板的一个参数在类函数模板中可将该参数当成常量来使用。
举个例子
我们想要创建一个大小固定可以存储不同类型元素的数组类我们可以这样写
#includeiostream
using namespace std;
#define N 10templateclass T
class Array
{
private:int _a[N];
};
int main()
{Arrayint arr1;Arrayint arr2;return 0;
}这时实例化出来的arr1arr2都是大小为10的定长数组。但我们假如说想改变一个数组的长度呢例如我们想将数组长度改为20怎么办呢有同学说我们可以将N define为20呀。但是假如我们想要一个数组长为10另一个长为20呢
这时候我们的非类型模板参数就有作用了我们可以将Array定义为下面的形式
#includeiostream
using namespace std;//这里的N叫做非类型模板参数,它是一个常量
templateclass T, size_t N
class Array
{
private:int _a[N];
};
int main()
{Arrayint, 10 arr1;Arrayint, 20 arr2;int n 10;Arrayint, n arr3;//error 这里非类型模板参数是常量这里会报错return 0;
}
对于非类型模板参数的使用场景STL中的deque和array容器中都用了。deque中的非类型模板参数用来传一个一个常量来控制buff的大小。
我们下面介绍一下array容器
C11支持array它的结构类似于vector只不过vector是动态数组而array是静态的。它不提供头插头插尾插尾删任意位置插入删除这种操作因为它根本不存在这种说法而且它可以直接使用operator[ ] 访问修改任意位置的数据。
array的缺陷
我们不推荐使用arrayarray底层是在栈上开辟空间的而栈空间又是很有限的例如在32位的linux下栈空间只有8M所以一般开大空间时极不推荐使用array相比之下vector就很有优势。而且在知道要开多大空间的情况下vector 也可以通过reserve一次性开好空间在后续的使用中还可以自动增容而array空间是固定的不灵活。所以我们一般不使用array。
注意
非类型模板参数只允许使用整形家族intshortlonglong longchar而浮点型类对象字符串是不允许作为非类型模板参数的非类型模板参数需要在编译的时候就确认结果因为编译器在编译阶段就需要根据传入的非类型模板参数生成对应的类或函数
模板的特化
我们先举个例子理解一下模板的特化看下面的代码
templateclass T
bool IsEqual(const T left, const T right)
{ return left right;
}
int main()
{cout IsEqual(1, 2) endl;char p1[] hello;char p2[] world;cout IsEqual(p1, p2) endl;return 0;
}我们实现了IsEqual函数用来判断两个参数是否相等。我们总共调用了两次第一次比较的是两个整数是没问题的而第二次实际上是有问题的我们本意是想比较两个字符串是否相等可是我们仔细看看我们实际上比较的p1p2两个指针。我们可能会想先判断一下传过来的参数的类型是什么再进行不同的操作就像下面这样
templateclass T
bool IsEqual(const T left, const T right)
{if(T char*)//error,C中不支持类型比较{return *left*right;}else{...}return left right;
}
int main()
{cout IsEqual(1, 2) endl;//okchar* p1 hello;char* p2 world;cout IsEqual(p1, p2) endl;//errreturn 0;
}但是很可惜C中不支持类型比较所以上面写的是错误的。
那么这里就需要我们的模板特化出手了模板特化的目的就是在原模板类的基础上针对某些类型进行特殊化处理。模板特化又分为函数模板特化和类模板特化。
函数模板的特化
首先必须有一个基础的函数模板关键字template后面接一对空的尖括号函数名后跟一对尖括号内指定需要特化的类型函数形参表必须要和函数模板的基础参数类型完全相同如果不同编译器可能会报一些奇怪的错误
我们上面的代码就可以改为
#includeiostream
using namespace std;templateclass T
bool IsEqual(const T left, const T right)
{return left right;
}template
bool IsEqualconst char *(const char* left,const char* right)
{return strcmp(left, right)0;
}
int main()
{cout IsEqual(1, 2) endl;const char* p1 hello;const char* p2 hello;cout IsEqual(p1, p2) endl;return 0;
}
说明其实一般情况下如果函数模板遇到不能处理或者处理有误的类型可以直接将该函数直接给出这叫做模板的匹配原则有现成的完全匹配的就直接调用没有现成调用的实例化模板生成。上面的代码就可以这样写
#includeiostream
using namespace std;templateclass T
bool IsEqual(const T left, const T right)
{return left right;
}bool IsEqual(const char* left,const char* right)
{return strcmp(left, right)0;
}
int main()
{cout IsEqual(1, 2) endl;const char* p1 hello;const char* p2 hello;cout IsEqual(p1, p2) endl;return 0;
}
对于模板匹配原则和函数模板特化两者底层没有任何差别如果能使用函数模板特化的时候更推荐使用函数模板特化。
类模板特化
1.全特化
就是将模板参数列表中所有参数确定化例如
#includeiostream
using namespace std;templateclass T1,class T2
class A
{
public:A(){cout T endl;}
private:T1 a1;T2 a2;
};template
class Aint,int
{
public:A(){cout int endl;}
private:
};int main()
{Adouble, doublea1;Aint, inta2;return 0;
}
2.偏特化
也叫半特化即对模板参数进行一定的确定化例如
templateclass T1//第二个参数是int类型就会调用这个
class AT1, int
{
public:A(){cout T1,int endl;}
private:
};templateclass T2//第一个参数是int类型就会调用这个
class Aint, T2
{
public:A(){cout int,T2 endl;}
private:
};templateclass T1,class T2//两个参数都是指针类型就会调用这个
class AT1*, T2*
{
public:A(){cout T1*,T2* endl;}
private:
};templateclass T1, class T2//两个参数都是引用类型就会调用这个
class AT1, T2
{
public:A(){cout T1,T2 endl;}
private:
};templateclass T1, class T2//第一个参数是指针类型第二个参数是引用类型就会调用这个
class AT1*, T2
{
public:A(){cout T1*,T2 endl;}
private:
};int main()
{Adouble, inta;Aint, charb;Adouble*, int*c;Achar, intd;Adouble*, chare;return 0;
}
上面这几种特化版本都是偏特化。
偏特化并不仅仅是指特化而是针对模板参数更进一步的条件限制所设计出来的一个特化版本例如我们可以限制他的类型是引用指针之类的。
关于特化的场景我们等到哈希表的时候会给大家介绍。
模板分离编译
我们在工作的时候写的项目都会由若干个源文件共同实现每个源文件独立编译生成目标文件最后将所有目标我呢见链接起来形成单一的可执行文件的过程称为分离编译模式。
模板的分离编译
在前面我们模拟实现stringvectorlist等STL容器的时候都没有将声明和定义分开。我们实际上是习惯将声明放在头文件中将定义放在.cpp文件中的。我们没有分离的原因就是C的模板不支持分离编译。
我们以这段代码为例
//Func.h代码:
#pragma once
#includeiostream
using namespace std;void Print();templateclass T
void F(T a);//Func.cpp代码
#includeFunc.hvoid Print()
{cout print endl;
}templateclass T
void F(T a)
{cout F(T a) endl;
}//test.cpp代码
#includeFunc.hint main()
{Print();F(10);//这里编不过return 0;
}
不支持分离编译的原因 我们都知道程序的编译过程分为预处理编译汇编链接四个步骤
其中预处理会做的事情就是头文件展开宏替换条件编译去掉注释在linux环境下就生成了.i文件
编译检查语法错误后生成汇编代码在linux环境下生成.s文件 汇编将汇编代码转成二进制机器码在linux环境下生成.o文件
链接会把.o文件里F或Print这样没有地址的地方用被修饰过的函数名去Func.o中的符号表找对应的地址然后填上地址就像上图中的地址一样。然后再把目标文件合并成可执行程序。
而问题就出现在链接的时候编译器拿着我们的函数名去符号表中找时能找到Print()函数但是却找不到F()函数这是因为在编译阶段Print()函数有定义可以生成。但是F()函数是一个函数模板他不能生成因为我们不知道T是什么类型。模板其实是调用时生成的。
如何解决
一、
这种方法实际上是不可行的就是让编译器在编译的时候去各个地方查找实例化例如我们在Func.i中看到一个模板我们就去Test.i中找实例化但是这样的话如果是一个大项目的化有几十几百个文件对于编译器的实现就复杂了。所以实际在链接前各文件间是不会交互的
二、
显示指定实例化编译器看到后就知道要把这个模板参数T实例化成什么类型。但是这样做的问题也很大就是我换个类型就又链接不上了那我们就只能使用一种类型就实例化一次这样就很麻烦。
三、
最后这种方法就很粗暴STL中用的也是这种方法就是不分离编译声明和定义都放在一个.h文件里。这样的话.h文件中就包含了模板的定义就不需要链接的时候去查找了在编译的阶段就能直接填地址了。而我们在项目中一般把这种文件命名为后缀为.hpp的文件也就是说它即是.h文件又是.cpp文件。
同样我们的类模板也不支持分离编译最好的办法也是不分离写在同一个文件中。
我在这里还要在解释我上文中的一句话模板其实是调用时实例化生成的。我们在一个类模板或者函数模板中假如写出了语法错误假如我们没有实例化运行的话编译器是检查不出错误的这就是因为模板是调用时实例化的没有实例化的时候编译器不会去检查函数模板或类模板内部的语法错误。
总结
模板优点
1.模板复用了代码节约资源更快的迭代开发有了模板才有了C的标准模板库STL的诞生
2.增强了代码的灵活性
模板缺点
1.模板会导致代码膨胀问题也会导致编译时间变长
2.假如我们使用模板时出现错误编译器提示的错误信息会非常凌乱而且准确度不高我们不能盲目地相信模板的报错。可能只是一点小错误最后却报出了一大堆错误这时候我们要优先看第一个错误。 以上就是本章的全部内容谢谢大家