函数模板
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。(编译器自己推类型)
函数模板格式:
template<typename T1, typename T2,......,typename Tn> 返回值类型 函数名(参数列表){}
如下面的代码:编译器可以自己推导T的类型,但是在这一个函数中,T只能表示一种类型
template<typename T> void Swap(T& left, T& right) { T temp = left; left = right; right = temp; }
typename 关键字也可以使用 class 代替,但不能使用 struct 代替
template<class T1, class T2,......,class Tn>
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
编译器在编译阶段就会根据实例化来推演模板的类型,当实例化的参数有两种类型,而只有一种模板参数的时候,编译器不会进行隐式类型转化,会报错。那么这里有两个解决方案:1、在实例化的时候利用强制类型转化传同类型的参数;2、显示实例化
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, (int)d1);// 用户自己强转 Add<int>(a2, d2);// 显示实例化 return 0; }
如果不确定传参数的类型,防止出错,可以定义两个模板参数,这样,就算传的是不同类型的参数,也可以在计算或者返回的时候进行隐式类型转换。
template<class T1, class T2> T1 Add(T1 left, T2 right) { return left + right; } void Test() { Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化 Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数 } int main() { Test(); return 0; }
类模板
类模板定义格式:以自己定义的 Vector 类模板来举例子
template<class T1, class T2, ..., class Tn> class Vector { // 类内成员定义 };
注意:Vector 并不是具体的类,是编译器根据被实例化的类型生成具体类的模具
当类模板中的函数放在类模板外定义时,需要再单独加模板参数列表
template <class T> Vector<T>::~Vector() { if (_pData) delete[] _pData; _size = _capacity = 0; }
类模板实例化方式: 类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
Vector<int> s1; Vector<double> s2;
Vector 算是类名,而实例化后的 Vector<int> 和 Vector<double> 才是真正的类型
非类型模板参数
模板参数分类类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
template<class T, size_t N = 10> class Array { public: private: T _arry[N]; }; int main() { Array<int, 20> a1; return 0; }
上面代码,如果Array在实例化的时候只传一个 int ,那么类模板中的N默认就是 10。
但这非类型模板参数定义有以下要求:
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。(只有整形家族可以:char、int、short、long long等)
2. 非类型的模板参数必须在编译期就能确认结果。
模板特化
函数模板特化
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,如实现了一个专门用于比较小于的函数模板,当参数传过去的时候,可能会出现传指针或某个特定类型(需要其他比较方式)的情况,如下面的示例:
// 函数模板 -- 参数匹配 template<class T> bool Less(T left, T right) { return left < right; } int main() { cout << Less(1, 2) << endl; // 可以比较,结果正确 cout << Less(1.2, 1.4) << endl; // 可以比较,结果正确 int a = 1, b = 2; int* p1 = &a ,*p2 = &b; cout << Less(p1, p2) << endl; // 可以比较,但是是按照指针对应的值比较,结果错误 return 0; }
单独写一个用于指针比较的模板,编译器会在这两个函数模板中去找参数最匹配的。
template<class T> bool Less(T left, T right) { return left < right; } template<class T> bool Less(T* left, T* right) { return *left < *right; } int main() { int a = 1, b = 2; int* p1 = &a ,*p2 = &b; cout << Less(p1, p2) << endl; return 0; }
当然,也可以使用函数特化(不推荐)
函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
// 函数模板 -- 参数匹配 template<class T> bool Less(T left, T right) { return left < right; } template<> bool Less<int*>(int* left1, int* right1) { return *left1 < *right1; } int main() { int a = 1, b = 2; int* p1 = &a ,*p2 = &b; cout << Less(p1, p2) << endl; return 0; }
但是也只能有一个特化函数,再定义就会报错。
类模板特化
类模板特化分为全特化和偏特化,全特化是将模板的全部参数确定化,而偏特化是将模板参数表中的一部分参数确定化。
但是,类特化之前必须有对应的普通模板!
类模板全特化
1. 必须要先有一个基础的类模板
2. 关键字template后面接一对空的尖括号<>
3. 类名后跟一对尖括号,尖括号中指定需要特化的类型
如下面的代码样例:
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; }
类模板偏特化
将模板参数类表中的一部分参数特化,样例如下:
// 将第一个参数特化为int template <class T1> class Date<int ,T1> { public: Data() { cout << "Data<T1, int>" << endl; } private: T1 _d1; int _d2; };
偏特化还可以针对模板的参数作进一步限制,如下面的代码,进行偏特化是将模板参数的第一个参数特化为指针类型或者引用。但这种偏特化只能特化为和模板参数有关的,如 T*,T&等,不能特化为普通类型!格式如下:模板参数照常写,但是要在类名后加具体限制后的模板参数。
template<class T1, class T2> class Data { public: Data() { cout << "Data<T1, T2>" << endl; } private: T1 _d1; T2 _d2; }; //第一个参数偏特化为指针类型 template <class T1, class T2> class Data<T1*, T2> { public: Data() { cout << "Data<T1*, T2>" << endl; } private: T1 _d1; T2 _d2; }; //第一个参数偏特化为引用类型 template <class T1, class 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 test2() { Data<double, int> d1; // 调用特化的int版本 Data<int, double> d2; // 调用基础的模板 Data<int*, int> d3; // 调用特化的指针版本 Data<int&, int> d4(1, 2); // 调用特化的指针版本 } int main() { test2(); return 0; }
模板分离编译
什么是分离编译:一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
模板的编译分离就是将模板的声明放在 .h文件中,将模板的定义放在 .cpp文件中;即在头文件中进行声明,源文件中完成定义。这种定义会导致以下问题:
解决方法:将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。
模板总结
【优点】
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性
【缺陷】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
总的来说,模板让C++提升了一个档次,非常厉害!