目录
1. 函数模板
1.1 函数模板概念
1.2 函数模板格式
1.3 函数模板的原理
1.4 函数模板的实例化
1.4.1 隐式实例化
1.4.2 显示实例化
1.5 模板参数的匹配原则
2. 类模板
2.1 类模板的定义格式
2.2 类模板的实例化
3. 非类型模板参数
4. 模板的特化
4.1 类模板特化
4.1.1 全特化
4.1.2 偏特化
4.2 函数模板特化
5. 模板分离编译
1. 函数模板
1.1 函数模板概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
1.2 函数模板格式
template<typename T>
返回值类型 函数名(参数列表){}
template<typename T> void Swap( T& left, T& right) { T temp = left; left = right; right = temp; }
注意:typename是用来定义模板参数关键字,也可以使用class。
1.3 函数模板的原理
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。
1.4 函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。
模板参数实例化分为:隐式实例化和显式实例化。
1.4.1 隐式实例化
隐式实例化:让编译器根据实参推演模板参数的实际类型
template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 10.0, d2 = 20.0; Add(a1, a2); Add(d1, d2); Add(a1, d1); //这个不行,因为两个参数类型不同 // 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化 Add(a, (int)d); return 0; }
1.4.2 显示实例化
显式实例化:在函数名后的<>中指定模板参数的实际类型
int main(void) { int a = 10; double b = 20.0; // 显式实例化 Add<int>(a, b); return 0; }
1.5 模板参数的匹配原则
1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
// 专门处理int的加法函数 int Add(int left, int right) { return left + right; } // 通用加法函数 template<class T> T Add(T left, T right) { return left + right; } void Test() { Add(1, 2); // 与非模板函数匹配,编译器不需要特化 Add<int>(1, 2); // 调用编译器特化的Add版本 }
2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板
// 专门处理int的加法函数 int Add(int left, int right) { return left + right; } // 通用加法函数 template<class T1, class T2> T1 Add(T1 left, T2 right) { return left + right; } void Test() { Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化 Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数 }
3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
2. 类模板
2.1 类模板的定义格式
template<class T1, class T2, ..., class Tn> class 类模板名 { // 类内成员定义 };
注意:类模板中函数放在类外进行定义时,需要加模板参数列表。
2.2 类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Vector类名,Vector<int>才是类型 Vector<int> s1; Vector<double> s2;
3. 非类型模板参数
模板参数分为类型模板参数与非类型模板参数。
1. 类型:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
2. 非类型:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
【注意】
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。
1. 根据传入的N,开不同大小的空间。
2. 这里的N是常量,只能传常量。
3. 只支持整型。
【实例】
1. array对应的是C语言的数组。
2. C语言的数组对越界检查是抽查,array的检查更好。因为C语言的方括号访问本质是数组解引用,array是函数调用,函数里面可以检查。
void test4() { array<int, 10> a1; int a2[10]; a1[16] = 1; //array会越界报错 a2[16] = 1; //数组不会越界报错 }
4. 模板的特化
在原模板的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。
4.1 类模板特化
4.1.1 全特化
全特化:将模板参数列表中所有的参数都确定化。
template<class T1, class T2> class Data { public: Data() {cout<<"Data<T1, T2>" <<endl;} private: T1 _d1; T2 _d2; }; template<> class Data<int, char> { public: Data() {cout<<"Data<int, char>" <<endl;} private: int _d1; char _d2; }; void TestVector() { Data<int, int> d1; Data<int, char> d2; //会走更匹配的 }
4.1.2 偏特化
偏特化:确定部分模板参数或对模版参数进行进一步限制。
template<class T1, class T2> class Data { public: Data() {cout<<"Data<T1, T2>" <<endl;} private: T1 _d1; T2 _d2; }; //部分特化 template <class T1> class Data<T1, int> { public: Data() {cout<<"Data<T1, int>" <<endl;} private: T1 _d1; int _d2; }; //进一步限制参数 template <class T1, class T2> class Data <T1*, T2*> { public: Data() {cout<<"Data<T1*, T2*>" <<endl;} private: T1 _d1; T2 _d2; };
void test2 () { Data<double, int> d1; // 调用特化的int版本 Data<int, double> d2; // 调用基础的模板 Data<int*, int*> d3; // 调用特化的指针版本 }
【实例】
仿函数用来比较指针类型的话不准确,所以需要专门弄一个指针的版本。
template<class T> struct Less { bool operator()(const T& x, const T& y) const { return x < y; } }; // 对Less类模板按照指针方式特化 template<class T> struct Less<T*> { bool operator()(Date* x, Date* y) const { return *x < *y; } };
4.2 函数模板特化
函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同
// 函数模板 template<class T> bool Less(T left, T right) { return left < right; } // 特化 template<> bool Less<Date*>(Date* left, Date* right) { return *left < *right; }
【注意】
一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。
bool Less(Date* left, Date* right) { return *left < *right; }
5. 模板分离编译
1. 模板不能分离编译,因为编译器对工程的多个源文件是分开单独编译的,在源文件中编译器看到你的时候只是模板,并不会生成具体的函数,导致链接是找不到对应的地址。
【解决办法】
1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。
2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。
【模板优点】
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性
【模板缺点】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
code-cpp: C++代码 (gitee.com)