1.非类型模板参数
模板参数分为 类型形参 和 非类型形参
类型形参即出现在模板参数列表中, 跟在class或者typename之类的参数类型名称
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将参数当成常量使用
#include<iostream> using namespace std; #define N 10 template <class T>//类型模板参数 class Array { public: private: T _a[N]; }; int main() { Array<int> a;//10 Array<double>b;//100 return 0; }
使用类型模板参数,虽然看似可以解决问题,但若a要10个int的数组,b要100个double的数组,宏就没办法解决了
所以为了解决这个问题,引入非类型模板参数,定义的是常量,一般是整形常量
#include<iostream> using namespace std; template <class T,size_t N>//非类型模板参数 class Array { public: private: T _a[N]; }; int main() { Array<int,10> a; Array<double,20>b; return 0; }
同样非类型模板参数还可以给缺省值 ,由于是整型常量,所以缺省值也是常量
同样在函数内部,无法对常量值进行修改
#include<iostream> using namespace std; template<class T,size_t N=20> void func(const T& a) { N = 20;//左操作数必须为左值 } int main() { func(1); }
必须为整形常量 整形包括 int 、char、 long 、long long 、short
若为double类型就会报错
2. 模板特化
使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理
函数模板特化
#include<iostream> using namespace std; class Date { public: Date(int year = 1900, int month = 1, int day = 1) : _year(year) , _month(month) , _day(day) {} bool operator<(const Date& d)const { return (_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day); } bool operator>(const Date& d)const { return (_year > d._year) || (_year == d._year && _month > d._month) || (_year == d._year && _month == d._month && _day > d._day); } friend ostream& operator<<(ostream& _cout, const Date& d) { _cout << d._year << "-" << d._month << "-" << d._day; return _cout; } private: int _year; int _month; int _day; }; 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; }
若p1与p2都为Date*,则调用Less ,会变成 left与right指针的比较 ,不符合预期,我们想要的是日期与日期之间的比较
所以为了防止这种 特殊情况的发生,所以使用 模板特化 (对某些类型进行特殊化处理)
此时就可以正常比较两个日期
函数模板特化意义不大,可以直接使用函数重载的方式来实现的
类模板特化
偏特化
偏特化——进一步的限制
必须在原模版的基础上才可以使用
针对的是指针这个泛类
全特化
必须在原模版的基础上才可以使用
只针对其中一个,如指针中的日期类
半特化
对部分参数特化
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; }; void TestVector() { Data<int, int> d1;//Data<T1, int> Data<int*, int> d3; //Data<T1, int> Data<double, int> d4; //Data<T1, int> } int main() { TestVector(); return 0; }
此时虽然有两个参数,但是只特化了一个 ,无论传的是 int、int*、double 都是走到半特化
参数的进一步限制
两个参数特化为引用类型
两个参数特化为指针类型
3. 模板的分离编译
模板并不支持分离编译 即声明在一个文件,定义在一个文件
此时调用模板add 就会报错,因为模板的声明和定义不在同一文件中
调用普通函数func,即可正常运行
模板会发生链接错误
func.h func.cpp test.cpp
预处理:头文件展开/ 注释的消除/ 条件编译/ 宏的替换
编译: 检查语法,生成汇编代码
func.s test.s
汇编:汇编代码转换为二进制机器码
func.o test.o
链接:合并生成可执行文件
a.out
由预处理阶段到 编译阶段时,通过func.i生成func.s ,生成汇编指令 ,但是只能生成func的,不能生成add的
函数被编译后生成指令,才可以找到第一句指令的地址 即函数地址
func会被编译成一堆指令,所以在func.o有func函数的地址,但是没有add的地址,因为add没有实例化 ,没办法确定T
就像是你差一点就可以交首付了,你打电话给你哥们借钱,你哥们说没问题,就像声明一样,这是一种承诺,
所以在编译阶段 add就可以过了 即call add( ? )
?代表现在没有地址
等到链接阶段才能拿到地址 ,而现在只是说得到承诺,到时候可以去拿到地址啦
但是 当你要交首付的时候,你哥们突然说借不了钱了,那这个房子首付也就交不上了
就好比 链接时 找不到add的函数地址了,所以会链接错误
链接之前不会交互,各走各的,不知道不能实例化,因为没办法确定T
解决链接错误的问题
显示实例化
虽然设置成double可以解决double的问题,但是传int 等又会报错,所以还是有很大的缺陷
局限性,实际中一般不使用
将声明和定义放在一个文件中
此时在预处理阶段将头文件展开,由于声明和定义都在一起,所以此时add函数 call时有地址存在,
不需要向func一样,链接时去寻找函数地址了
声明和定义放在一起,直接就可以实例化,编译时就有地址,不需要链接
4. 模板总结
缺陷
1. 模板会导致代码膨胀问题,也会导致编译时间变长
模板会进行实例化,所以运行时间变长,因为是一个模板,若传过来int 、char、double 类型都要实现,所以代码会膨胀
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
通常为一报错就一大堆错误,一般只需要找到第一个错误并解决它,就可以了
————————————————
版权声明:本文为CSDN博主「风起、风落」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_62939852/article/details/129735680