学习目标
1.了解非类型模板参数
2.了解类模板的特化
3.知道模板分离编译会出现的问题
1.非类型模板参数(整型常量)
模板参数:
1.类型形参:在模板参数列表中,class/typename后的参数名称
2.非类型形参:整型常量
示例:
template<class T ,size_t N> class arr { public: //...... private: T _arr[N];//根据传递过来的N,来决定开多大的数组 size_t _size; }; void test1() { arr<int, 10> arr1; }
2.模板的特化
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板
// 函数模板 -- 参数匹配 template<class T> bool Less(T left, T right) { return left < right; } int main() { cout << Less(1, 2) << endl; // 可以比较,结果正确 Date d1(2022, 7, 7); Date d2(2022, 7, 8); cout << Less(d1, d2) << endl; // 可以比较,结果正确 Date* p1 = &d1; Date* p2 = &d2; cout << Less(p1, p2) << endl; // 可以比较,结果错误(比较的是指针) return 0; }
此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化
函数模板特化
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误// 函数模板 -- 参数匹配 template<class T> bool Less(T left, T right) { return left < right; } // 对Less函数模板进行特化 template<> bool Less<Date*>(Date* left, Date* right) { return *left < *right; }
一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。
bool Less(Date* left, Date* right) { return *left < *right; }
类模板的特化
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; };
2.偏特化
基础的类模板:
template<class T1, class T2> class Data { public: Data() { cout << "Data<T1, T2>" << endl; } private: T1 _d1; T2 _d2; };
偏特化:任何针对模版参数进一步进行条件限制设计的特化版本
偏特化有两种表中形式:1.部分特化2.参数更进一步的限制
1.部分特化:
// 将第二个参数特化为int template <class T1> class Data<T1, int> { public: Data() { cout << "Data<T1, int>" << endl; } private: T1 _d1; int _d2; };
2.参数更进一步的限制
//两个参数偏特化为指针类型 template <typename T1, typename T2> class Data <T1*, T2*> { public: Data() { cout << "Data<T1*, T2*>" << endl; } private: T1 _d1; T2 _d2; };
3.模板的分离编译
1.什么是分离编译
分离编译模式:一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程
2.模板的分离编译
2.1模板声明和定义分离为什么会导致链接错误?
我们先将模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义
//tfunc.h template<class T> T Add(const T& left, const T& right); //func.cpp template<class T> T Add(const T& left, const T& right) { return left + right; } //test.cpp int main() { Add(1,2); Add(1.0,2.0); return 0; }
效果:
这里看到链接错误了
但普通的函数时可以声明和定义分离的:
这是因为普通函数在编译的时候会被编译成指令
而模板声明和定义分离后,不知道实例化出什么类型,没有被编译成指令
于是在链接的时候,找不到模板实例化出的函数Add的地址,因此报错
进程中有一个寄存器,用来记录当前执行到那一句指令,每一句指令都有一个地址
通过寄存器可以知道这段代码执行到了哪里,当这段程序执行时间到后,被切换出去,保存这个寄存器,等后面切换回来的时候,根据这个寄存器,来继续执行后面的代码
2.2为什么声明和定义放在一起后就不会链接错误了?
预处理的过程会将头文件展开,若声明定义放在一起,在test.cpp文件中展开后,调用该函数的时候,就会知道T的类型,知道这个函数的地址,会生成相关的指令
声明和定义放在一起后直接就可以实例化在编译的时候就有地址,不需要再连接
3.解决方法
1.显示实例化:
//func.h template<class T> T Add(const T& left, const T& right); template double Add<double>(const double& left, const double& right); template int Add<int>(const int& left, const int& right);
2.声明和定义放在一起