模板进阶

张开发
2026/4/6 17:02:39 15 分钟阅读

分享文章

模板进阶
之前我们对模板进行了一个基本的认识今天我们来进行一些进阶的学习1.非类型模板参数模板参数分类类型形参与非类型形参。 整型常量类型形参出现在模板参数列表中跟在class或者typename之类的参数类型名称。非类型形参就是用一个常量作为类(函数)模板的一个参数在类(函数)模板中可将该参数当成常量来使用。// 定义一个模板类型的静态数组 templateclass T, size_t N 10 class array { public: T operator[](size_t index) { return _array[index]; } const T operator[](size_t index)const { return _array[index]; } size_t size()const { return _size; } bool empty()const { return 0 _size; } private: T _array[N]; size_t _size; };不需要模板参数直接这样都行templatesize_t N定义的是常量在编译的时候确定所以可以直接用来做数组的大小那根宏有什么区别呢宏#define N 5; class Stack { private: int _a[N]; int _top; }; int main() { Stack s1;//5 Stack s2;//10 }宏是定死了无法改变。非类型模板参数可以控制大小自由变动。templatesize_t N class Stack { private: int _a[N]; int _top; }; int main() { Stack5 s1;//5 Stack10 s2;//10 }本质底层还是生成了两个类一个N是5一个N是10。注意浮点数C20之后才支持的1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。2. 非类型的模板参数必须在编译期就能确认结果。非类型模板参数可以给缺省值templatesize_t N20 class Stack { private: int _a[N]; int _top; }; int main() { Stack s0; Stack5 s1;//5 Stack10 s2;//10 }Stack s0是C20之后才支持的所以说还是推荐使用Stack s0毕竟C老的语法也支持。多个的非类型模板参数也可以同时bool也算是整型类型也支持。templatesize_t N20,bool flag false class Stack { private: int _a[N]; int _top; };2.库里面的非类型模板参数应用array#includearray int main() { std::arrayint, 10 a1; std::arrayint, 100 a2; int a3[10]; return 0; }对于越界检查的问题比静态数组强静态数组抽查越界读不检查越界写抽查int main() { arrayint, 10 a1; arrayint, 100 a2; int a3[10]; //越界检查的问题 cout a3[10] endl; a3[10] 10; a3[11] 10; return 0; }arrayint main() { arrayint, 10 a1; arrayint, 100 a2; //array越界读写都可以检查 a1[12] 10; return 0; }但其实vector也能做检查只不过vector数据存在堆上array存在栈上2. 模板的特化2.1 概念通常情况下使用模板可以实现一些与类型无关的代码但对于一些特殊类型的可能会得到一些 错误的结果需要特殊处理// 函数模板 -- 参数匹配 templateclass T bool Less(T left, T right) { return left right; } int main() { cout Less(1, 2) endl; // 可以比较结果正确 Date d1(2026, 4, 6); Date d2(2026, 4, 21); cout Less(d1, d2) endl; // 可以比较结果正确 Date* p1 d1; Date* p2 d2; cout Less(p1, p2) endl; // 可以比较结果错误 return 0; }可以发现第三个会结果错误这是因为Less内部并没有比较p1和p2指向的对象内 容而比较的是p1和p2指针的地址这就无法达到预期而错误。此时就需要对模板进行特化。即在原模板类的基础上针对特殊类型所进行特殊化的实现方式。模板特化中分为 函数模板特化 与 类模板特化 。2.2 函数模板特化函数模板的特化步骤1. 必须要先有一个基础的函数模板2. 关键字template后面接一对空的尖括号3. 函数名后跟一对尖括号尖括号中指定需要特化的类型4. 函数形参表: 必须要和模板函数的基础参数类型完全相同如果不同编译器可能会报一些奇怪的错误。// 函数模板 -- 参数匹配 templateclass T bool Less(T left, T right) { return left right; } //特化 template bool LessDate*(Date* left,Date* right) { return left right; } int main() { cout Less(1, 2) endl; Date d1(2026, 4, 6); Date d2(2026, 4, 21); cout Less(d1, d2) endl; Date* p1 d1; Date* p2 d2; cout Less(p1, p2) endl; return 0; }如果是Date*类型就走下面那个如果是其他就走第一个从而达到特化的目的。当然了也可以直接处理。有时候函数模板的特化会有问题//推荐 bool Less(Date* left, Date* right) { return left right; }函数模板特化容易出问题templateclass T bool Less(const T left,const T right) { return left right; }严格来讲应该普通这么写(上图)但是特化这时候就会报错如果改成下图也还是不行//特化 template bool LessDate*(const Date* left,const Date* right) { return left right; }所以要把const放到*后面//特化 template bool LessDate*(Date* const left,Date* const right) { return left right; } bool Less(Date* left, Date* right) { return left right; }2.3类模板特化1.全特化全特化即是将模板参数列表中所有的参数都确定化。templateclass T1, class T2 class Data { public: Data() { cout DataT1, T2 endl; } private: T1 _d1; T2 _d2; }; //全特化 template class Dataint, char { public: Data() { cout Dataint, char endl; } private: int _d1; char _d2; }; int main() { Dataint, int d1; Dataint, char d2; }2.偏特化/半特化偏特化任何针对模版参数进一步进行条件限制设计的特化版本。偏特化有以下两种表现方式1.部分特化将模板参数类表中的一部分参数特化。//偏特化 templateclass T1 class DataT1, double { public: Data() { cout DataT1, double endl; } private: int _d1; char _d2; };templateclass T1, class T2 class Data { public: Data() { cout DataT1, T2 endl; } private: T1 _d1; T2 _d2; }; //全特化 template class Dataint, char { public: Data() { cout Dataint, char endl; } }; //偏特化 templateclass T1 class DataT1, double { public: Data() { cout DataT1, double endl; } }; int main() { Dataint, int d1; Dataint, char d2; Dataint, double d3; Datachar, double d4; }如果又有全特化又有偏特化那么编译器会选择全特化2.参数更进一步的限制偏特化并不仅仅是指特化部分参数而是针对模板参数更进一步的条件限制所设计出来的一 个特化版本。指针//偏特化传的类型是指针 templatetypename T1,typename T2 class DataT1*, T2* { public: Data() { cout DataT1*,T2* endl; } };int main() { Dataint, int d1; Dataint, char d2; Dataint, double d3; Datachar, double d4; Datachar*, double* d5; Dataint*, double* d6; }引用//偏特化传的类型是引用 templatetypename T1,typename T2 class DataT1, T2 { public: Data() { cout DataT1,T2 endl; } }; int main() { Dataint, int d1; Dataint, char d2; Dataint, double d3; Datachar, double d4; Datachar*, double* d5; Dataint*, double* d6; Dataint, double d7; Dataint,int d8; }指针引用混用也行需要一一对应//偏特化传的类型是指针和引用 templatetypename T1,typename T2 class DataT1*, T2 { public: Data() { cout DataT1*,T2 endl; } };Dataint*,int d9;还有一些小细节templatetypename T1, typename T2 class DataT1, T2* { public: Data() { cout DataT1,T2* endl; T2 y; cout typeid(y).name() endl; } };Dataint, int* d10; Dataint, int** d11;可以发现传二级变一级传一级变原。3 模板分离编译3.1什么是分离编译一个程序项目由若干个源文件共同实现而每个源文件单独编译生成目标文件最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。C/C程序要运行,一般要经历以下步骤:预处理--编译--汇编--链接Func.h Func.cpp Test.cpp预处理头文件展开/宏替换/条件编译/去掉注释.....Func.i Test.i编译:对程序按照语言特性进行词法、语法、语义分析,错误检查无误后生成汇编代码注意头文件不参与编译编译器对工程中的多个源文件是分离开单独编译的。Func.s Test.s汇编汇编代码转换成二进制机器码Func.o Test.o链接:目标文件合并在一起生成可执行文件,并且把需要的函数地址等链接上 去符号表里面找链接的地址xxx.exe/a.out链接错误LNK2019 / undefined reference的本质在编译阶段编译器只检查函数的声明是否存在比如你写了函数原型或包含了头文件但不关心定义。到了链接阶段链接器需要把每个函数调用与它的实际定义函数体关联起来。如果链接器在所有目标文件和库中都找不到这个函数的定义就会报“无法解析的外部符号”。解决方案1. 将声明和定义放到一个文件 xxx.hpp 里面或者xxx.h其实也是可以的。推荐使用这种。2. 模板定义的位置显式实例化。这种方法不实用不推荐使用。4.模板总结【优点】1. 模板复用了代码节省资源更快的迭代开发C的标准模板库(STL)因此而产生2. 增强了代码的灵活性【缺陷】1. 模板会导致代码膨胀问题也会导致编译时间变长2. 出现模板编译错误时错误信息非常凌乱不易定位错误至此模板进阶的学习就到此结束欢迎我们下次再见

更多文章