目录
一、模板参数
模板参数分为类型形参和非类型形参。
1.类型形参
出现在模板参数列表中,跟在class或typename之后的参数类型名称。如下代码中跟在class后的T就是类型形参:
template<class T>
void Swap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
2.非类型形参
用一个常量作为类或函数模板的一个参数,在类或函数模板中把这个参数当成常量来用,如下代码中的N就是非类型形参,它是模板的另外一个参数:
//非类型模板参数
template<class T,size_t N>//N被当作常量,不允许被修改
class Car
{
private:
T _number[N];//汽车数量
};
int main()
{
Car<int, 3> car1;//N=3
Car<int, 20> car2;//N=20
return 0;
}
可以使用缺省的模板参数
//非类型模板参数
template<class T = int, size_t N = 100>//缺省模板参数
class Car
{
private:
T _number[N];//汽车数量
};
int main()
{
Car<int, 3> car1;//N=3
Car<int, 20> car2;//N=20
Car<> car3;//使用了缺省的模板参数
return 0;
}
注意:
(1)非类型模板参数必须是整形,不允许其他类型。
template<double N>//N是非类型模板参数,编译报错,类型非法,不允许double型
(2)非类型的模板参数必须在编译期就能确认结果。
//非类型模板参数
template<class T, size_t N>//模板参数没有给默认值
class Car
{
private:
T _number[N];//汽车数量
};
int main()
{
Car<int> car4;//编译报错,没有指定非类型模板参数的值
return 0;
}
二、模板的特化
有时候,编译默认函数模板或者类模板不能正确处理需要的逻辑,需要针对指定类型进行特殊化处理,就要做模板的特化。
比如如下代码:
template<class T>
bool IsEqual(const T& T1, const T& T2)
{
return T1 == T2;
}
void Test()
{
char c1[] = "C++";
char c2[] = "C++";
cout << IsEqual(c1, c2) << endl;
const char* p1 = "C++";
const char* p2 = "C++";
cout << IsEqual(p1, p2) << endl;
}
int main()
{
Test();
return 0;
}
发现打印结果一个为0,一个为1:
c1和c2是数组,在栈上,c1是第一个数组的地址,c2是第二个数组的地址,c1和c2分别把常量区的字符串"C++"拷贝过去了,c1是第一个数组的地址,c2是第二个数组的地址,所以它俩不相等。
p1和p2是指针,都指向常量字符串"C++","C++"位于常量区,p1和p2里面存的都是地址,都存的是"C++"的地址,所以p1和p2相等。
虽然p1和p2的比较结果是1,是正确的。但是比较两个字符串是否相等,应该比较的是这两个字符串的ASCII码,而不是直接用==来比较。这就需要模板的特化。
模板的特化是在原有模板的基础上,针对特殊类型,所进行特殊化的实现方式,特化的本质是显式指定实例化的模板。模板特化分为函数模板特化和类模板特化。
1.函数模板特化
函数模板特化的步骤:
(1)必须有基础模板函数
(2)template后面跟一对空的尖括号<>
(3)函数名后跟一对尖括号,用来指定特化的类型
(4)函数形参表:必须要和模板函数的基础参数类型完全相同,否则编译器报错。
如上面的字符串比较不能用==来判断字符串是否相等,需要进行模板特化,用strcmp来比较:
#include<iostream>
using namespace std;
//函数模板
template<class T>
bool IsEqual(const T& T1, const T& T2)
{
return T1 == T2;
}
//标准的模板特化
template<>
bool IsEqual<const char*>(const char* const& left, const char* const& right)
{
if (strcmp(left, right) == 0)
{
return true;
}
return false;
}
void Test()
{
const char* p1 = "C++";
const char* p2 = "C++";
cout << IsEqual(p1, p2) << endl;
}
int main()
{
Test();
return 0;
}
以上第 11-19行是标准的模板特化的写法,为了实现简单,通常都将函数直接给出:
//简写的模板板特化
bool IsEqual(char* left, char* right)
{
if (strcmp(left, right) == 0)
{
return true;
}
return false;
}
2.类模板特化
类模板在没有写类特化之前,调的都是原模板的,如下代码,定义了类模板:
#include<iostream>
using namespace std;
//类模板
template<class T1, class T2>
class Data
{
public:
Data()
{
cout << "Data<T1,T2>" << endl;
}
};
void Test_Class()
{
Data<int, int> d1;
Data<int, char> d2;
}
int main()
{
return 0;
}
类模板分为全特化和偏特化,但是类模板只有一种写法,没有简写形式
(1)全特化
全特化是把模板参数列表中的所有参数都确定化。
比如想把T1指定成int,T2指定成char
#include<iostream>
using namespace std;
//类模板
template<class T1, class T2>
class Data
{
public:
Data()
{
cout << "Data<T1,T2>" << endl;
}
};
//类模板的全特化,将T1指定成int,T2指定成char
template<>
class Data<int, char>
{
public:
Data()
{
cout << "Data<int,char>" << endl;
}
private:
int _d1;
char _d2;
};
void Test_Class()
{
Data<int, int> d1;
Data<int, double> d2;
Data<int, char> d3;
}
int main()
{
return 0;
}
那么Data<int, char> d3;就会去调用全特化的模板。Data<int, int> d1;和Data<int, double> d2;就会调用原有的类模板:
(2)偏特化
偏特化是任何针对模板参数进一步进行条件限制的特化版本。偏特化分为两种表现方式:
①部分特化:把模板参数类表中的一部分参数特化
//偏特化-部分特化,把第二个参数特化为double
template<class T1>
class Data<T1, double>
{
public:
Data()
{
cout << "Data<T1,T2>" << endl;
}
private:
T1 _d1;
double _d2;
};
这时候d2就会去调这个偏特化 :
②对参数进一步限制:对模板参数做更进一步的条件限制
可以将参数偏特化为指针类型 :
//偏特化-两个参数偏特化为指针类型,只指定指针,什么类型的指针都可以
template<typename T1,typename T2>
class Data<T1*, T2*>
{
public:
Data()
{
cout << "Data<T1*,T2*>" << endl;
}
private:
T1 _d1;
T2 _d2;
};
void Test_Class()
{
Data<int, int> d1;
Data<int, double> d2;
Data<int, char> d3;
Data<char*, char*> d4;//会调用两个参数偏特化为指针类型的偏特化
}
也可以将参数偏特化为引用类型:
//偏特化-两个参数偏特化为引用类型,只指定引用,什么类型的引用都可以
template<typename T1, typename T2>
class Data<T1&, T2&>
{
public:
Data(const T1& d1,const T2& d2)
:_d1(d1)
,_d2(d2)
{
cout << "Data<T1&,T2&>" << endl;
}
private:
const T1& _d1;
const T2& _d2;
};
void Test_Class()
{
Data<int, int> d1;
Data<int, double> d2;
Data<int, char> d3;
Data<char*, char*> d4;//会调用两个参数偏特化为指针类型的偏特化
Data<double&, double&> d5(2.1, 3.2);//会调用两个参数偏特化为引用类型的偏特化
}
最后,编译器匹配参数时,会自动匹配最匹配的那个,编译器实例化之后就没有模板了,实例化之后就会把T1&替换成double&,把T2&替换成double&,就变成了普通类。
假如参数类型一个是指针,一个是引用呢?将一个参指定为指针类型,另外一个参数指定为引用类型
//偏特化-一个参数偏特化为指针类型,一个参数偏特化为引用类型
template<typename T1, typename T2>
class Data<T1*, T2&>
{
public:
Data()
{
cout << "Data<T1*,T2&>" << endl;
}
};
Data<int*, double&> d6;//会调用一个参数偏特化为指针类型,一个参数偏特化为引用类型的偏特化
三、模板分离编译
分离式编译:一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,将所有目标文件连接起来形成单一的可执行文件的过程称为分离编译模式。
1.模板支持分离编译吗
对于分离式编译的模板:
template.h
template<class T>
T Add(const T& left, const T& right);
template.cpp
#include "template.h"
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
template-main.cpp
#include "template.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}
上述代码运会报错:
2.为什么模板不支持分离编译
程序要运行,需要经过预处理、编译、汇编、链接4个阶段
(1)预处理
包含头文件、宏替换、、条件编译、删除注释
(2)编译
语法分析、词法分析、语义分析,符号汇总
(3)汇编
汇编代码转换成机器指令 、生成符号表
(4)链接
链接目标文件和连接库、合并符号表、重定位
预处理阶段,#include "template.h"包含头文件,认为会有Add函数的定义。编译时在符号表里填上符号和地址,生成符号表,编译阶段只有函数声明,没有函数定义,如果有人调用了这个符号,就能通过符号表找到这个符号对应的地址。但是函数模板不会生成对应符号。汇编时把符号转换成二进制,这样,机器就可以识别了。链接时会去找函数地址进行合并重定位。
虽然编译时,想生成函数模板对应的符号,但是此时并不知道T是什么,由于在链接之前,各个文件不知道对方的存在,所以template-main.cpp无法告诉template.cpp T具体是什么,template.cpp中无法对T进行实例化,会寄希望于最后一步链接,所以不会生成对应符号。即,定义的地方(template.cpp)不实例化,而实例化的地方(template-main.cpp)没有定义,只有声明。没有实例化T,生成不了函数模板的符号。
3.如何编译模板文件
如何解决函数模板不能生成对应符号,导致程序运行不了的问题呢?有两种方法
(1)显式指定实例化
模板定义时直接指定T的类型,即增加模板的特化,将template.cpp改为:
#include "template.h"
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
//增加int类型的模板特化
template<>
int Add<int>(const int& left, const int& right)
{
return left + right;
}
//增加double类型的模板特化
template<>
double Add<double>(const double& left, const double& right)
{
return left + right;
}
(2)将声明和定义放到一个文件中(推荐)
将template.h和template.cpp合并成一个文件,这个文件名可以是template.h,也可以是template.cpp,此时template就只剩下一个文件了。
template.h
template<class T>
T Add(const T& left, const T& right);
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
template.main不变
#include "template.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}
编译通过。