前言
模板特化对函数和函数都可以使用。它的作用是以某一模板函数或某个模板类为例,大部分情况下需要写的函数或内容是一致的,但是有些特别情况,所以我们需要单独拎出来。
模板参数
- 模板参数可分为类型形参和非类型形参。
类型形参: 出现在模板参数列表中,跟在class或typename关键字之后且class和typename可以混用。 - 如下,是一个非类型模板参数的使用实例。这样传参使得更简洁的方式定义一个数组大小。
template<class T, size_t N> //N:非类型模板参数
class StaticArray
{
public:
size_t arraysize()
{
return N;
}
private:
T _array[N]; //利用非类型模板参数指定静态数组的大小
};
- 使用:
int main()
{
StaticArray<int, 10> a1; //定义一个大小为10的静态数组
cout << a1.arraysize() << endl; //10
return 0;
}
注意:
- 非类型模板参数只能用整型类型。字符串、类对象、浮点数都不行。
- 非类型模板参数需要在编译阶段确认结果。即初始化时就给值。
模板特化
函数模板特化
模板用在函数上使得多种类型的参数都能用同一种方式处理,但是存在某一些参数情况下需要用不同的处理方式,所以需要模板特化。
如下代码,普通数值类型能比较,但是字符串类型就比较不了,所以我们需要模板特化。
template<class T>
bool IsEqual(T x, T y)
{
return x == y;
}
- 函数模板特化的方式:
- 首先必须要有一个基础的函数模板。
- 关键字template<> 关键字template空的尖括号<>。
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型。
- 函数形参表必须要和模板函数的基础参数类型完全相同,否则不同的编译器可能会报一些奇怪的错误。
处理string类型的比较,需要按字典序比较,所以特化出能比较string类型的模板特化函数,函数特化代码如下:
//基础的函数模板
template<class T>
bool IsEqual(T x, T y)
{
return x == y;
}
//对于char*类型的特化
template<>
bool IsEqual<char*>(char* x, char* y)
// 不写<char*>也可以
{
return strcmp(x, y) == 0;
}
类模板的特化:
类模板特化分全特化和偏特化(半特化)。区别是全部特化和部分特化。
- 全特化:
有类模板代码如下:
template<class T1, class T2>
class Dragon
{
public:
//构造函数
Stu()
{
cout << "Stu<T1, T2>" << endl;
}
private:
T1 _D1;
T2 _D2;
};
如果想对T1、T2是double和int时做特殊处理,则可写如下代码:
//对于T1是double,T2是int时进行特化
template<>
class Stu<double, int>
{
public:
//构造函数
Stu()
{
cout << "Stu<double, int>" << endl;
}
private:
double _D1;
int _D2;
};
- 调用:
Stu<int, double> s1; // 调用特化
Stu<int, int> s2; // 调用普通
- 总结类全特化的写法:
- 要先写普通类模板。
- 写类前先加:template<>。
- 类名后跟一对尖括号,尖括号中指定需要特化的类型。
- 半特化:参数更进一步限制。
代码如下:我们对T1做限制
template<class T2>
class Dragon<double, T2>
{
public:
//构造函数
Stu()
{
cout << "Stu<double, T2>" << endl;
}
private:
double _D1;
T2 _D2;
};
- 总结偏特化的方式:
- template 偏特化类之前,先声明哪个参数不特化
- 类定义的<>中在适当位置加入类型
- 参数更进一步的限制:偏特化不仅仅是特化部分参数,还可以根据模板参数比如使用T1、T2时候,我们限定传参为T1*、T2*时使用特殊化的类模板。
代码如下:
//两个参数偏特化为指针类型
template<class T1, class T2>
class Stu<T1*, T2*>
{
public:
//构造函数
Stu()
{
cout << "Stu<T1*, T2*>" << endl;
}
private:
T1 _D1;
T2 _D2;
};
//两个参数偏特化为引用类型
template<class T1, class T2>
class Stu<T1&, T2&>
{
public:
//构造函数
Dragon()
{
cout << "Stu<T1&, T2&>" << endl;
}
private:
T1 _D1;
T2 _D2;
};
T1、T2同时为指针类型或为引用类型时,会分别调用两个特化类模板。
Stu<int, int> s1; // T1 T2
Stu<int*, int*> s2; // T1* T2*
Stu<int&, int&> s3; // T1& T2&
这里理解为,如果我们要特化关于指针或引用类型时,特化方式和普通默认类型稍有不一样,需要template<class T1, class T2>,全写出来,
class Stu<T1&, T2&>,类后的两个参数也全写出来,且涉及指针和引用类型算一种偏特化。
模板分离编译
- (不是模板分离编译)分离编译指的是:一个程序(项目)由若干个源文件共同实现,每个源文件编译生成目标文件,最后将所有模板文件链接起形成单一可执行文件的过程叫分离编译,
- 模板的分离编译:头文件只声明模板,而模板函数的实现在cpp。此外,任何程序都需要main.cpp(规范,当然放在某个别的cpp中也行)。如下图三部分:
使用这三个文件生成可执行文件,链接阶段会产生报错。
回忆程序运行经过四个步骤:
- 预处理: 头文件展开、去注释、宏替换、条件编译等。
- 编译: 检查代码的规范性、是否有语法错误等,确定代码实际要做的工作,在检查无误后,将代码翻译成汇编语言。
- 汇编: 把编译阶段生成的文件转成目标文件。
- 链接: 将生成的各个目标文件进行链接,生成可执行文件。
因为上面三个被干成了两部分,没有连接起来。
模板分离编译失败的原因:
在函数模板定义的地方(Add.cpp)没有进行实例化,而在需要实例化函数的地方(main.cpp)没有模板函数的定义,无法进行实例化。
所以:为什么在链接阶段错误?
答:在链接时候main中调用的两个Add实际上没有被真正定义,main发现Add没有定义是因为cpp中的实现了的函数模板没有生成对应函数,因为没有做实例化,因为函数模板不知道T实例化为什么类型。
避坑方案
- 在模板定义时显示实例化,如下:不建议,用到一个类型就要实例化一个类型,干不完的
- 将模板的声明和定义放一起。推荐
模板优缺点:
优点:灵活、复用简单,STL的重要工具。
缺点:代码膨胀、编译时间过长、报错信息难看、不容易定位。
题目
下列的模板声明中,其中几个是正确的( )
1)template
2)template<T1,T2>
3)template<class T1,T2>
4)template<class T1,class T2>
5)template<typename T1,T2>
6)template<typename T1,typename T2>
7)template<class T1,typename T2>
8)<typename T1,class T2>
9)template<typeaname T1, typename T2, size_t N>
10)template<typeaname T, size_t N=100, class _A=alloc<T>>
11)template<size_t N>
9、11中size_t N是非类型形参,9和11都是模板声明,关键点是跟在class和typename中即可。
共有:4\6\7\8\10\11共6个。
非模板的函数的调用优先级更高。
模板的编译:
模板不支持分离编译,分离后的模板实现需要自己写实例化,很麻烦。