为了实现泛型编程 (比如不同类型通用的函数), C++引入了模板的概念.
使用时在函数或类实现前(上一行), 加上template<typename T1, typename T2,......,typename Tn>
(typename可以换成class), 表示T1是一种类型,T2是另一种类型. 这样就成了一个模板.
函数模板与类型无关,函数模板不是一个函数,类模板不是一个类, 他们在使用时被参数化,根据实参类型才产生具体的函数或类. 所以每用一个不同类型了模板, 编译时编译器会根据模板产生多个具体类型的函数.
【优点】
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性
【缺陷】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
模板参数可以像函数的形参一样给出缺省参数template< typename T1= int >
使用方法:
#include <iostream> using namespace std; int Add(int left, int right) { return left + right; } //如果只给T1一个类型1,第二个add()调用将会出错 //但是如果调用时是显示实例化Add<int>(1, 2.1), 那只有一个模板参数则没问题 template<class T1, class T2> T1 Add(T1 left, T2 right) { return left + right; //返回值也可以是T2,T2则会打印3.1 //返回值也可以是自己写的具体类型. } void Test() { cout<<Add(1, 2)<<endl; // 与非函数模板类型完全匹配,不需要函数模板实例化 cout<<Add(1, 2.1)<<endl; // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数 } int main() { Test(); return 0; }
template<class T1> class vector { private: T1 DATA; }; // 注意:类模板中函数放在类外进行定义时,需要加模板参数列表 template <class T> Vector<T>::~Vector() { if(_pData) delete[] _pData; _size = _capacity = 0; } int main(){ vector<int> v;//没给默认模板参数类型的类模板实例化时要给出模板参数类型 return 0; }
模板进阶:
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用.
template<class T, size_t N = 10> //N就成为了常量10
浮点数、类对象、字符串不允许作为非类型模板参数。
非类型的模板参数必须在编译期就能确认结果。
1. 必须要先有一个基础的函数模板
2. 特化的模板在关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报错.
有些时候函数模板可以实现一些与类型无关的代码, 但是对一些特殊的类型可能会得到错误的结果, 则此时就需要针对这类型进行模板的特化, 让该类型走特化的函数:
#include <iostream> using namespace std; template<class T> bool IsEqual(T& left, T& right) { return left == right; } //没有特化的函数模板,走基础的函数模板则会出现!=的情况. template<> bool IsEqual<char*>(char*& left, char*& right) { if (strcmp(left, right) > 0) return true; return false; } void main() { char p1[] = "hello"; char p2[] = "hello"; if (IsEqual(p1, p2)) cout << "==" << endl; else cout << "!=" << endl; }
类模板的特化有全特化和偏特化两种方式 (偏特化也有两种方式) :
#include <iostream> using namespace std; 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; }; //偏特化 template <class T1> class Data<T1, int>{ public: Data() { cout << "Data<T1, int>" << endl; } private: T1 _d1; int _d2; }; //两个参数偏特化为指针类型 template <typename T1, typename T2> class Data <T1*, T2*> { public: Data() { cout << "Data<T1*, T2*>" << endl; } private: T1 _d1; T2 _d2; }; //两个参数偏特化为引用类型 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 main(){ Data<int, double> d2; // 调用基础的模板 Data<int, char> d0; // 调用指定的全特化 Data<double, int> d1; // 调用偏特化的int版本 Data<int*, int*> d3; // 调用特化的指针版本 Data<int&, int&> d4(1, 2); // 调用特化的指针版本 }
什么是分离编译: 一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式.
模板不同分离编译, 因为模板分离编译会二次编译, 会在链接时出错, 在不久的将来有望实现编译器支持export关键字实现模板的分离编译.
如果将模板的声明和定义放在不同的文件, 比如:
导致编译时无法产生具体的add函数, 主函数调用这个函数模板会在链接时因找不到具体函数的地址而报错.
解决方法
1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h.
2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用.