- 一、非类型模板参数
- 1.1 概念
- 1.2 C++11 array
- 二、模板的特化
- 2.1 函数模板特化
- 2.2 类模板特化
- 2.2.1 全特化
- 2.2.2 偏特化
- 三、模板的分离编译
- 3.1 什么是分离编译?
- 四、模板总结
一、非类型模板参数 1.1 概念
模板参数分为类型形参与非类型形参。
类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
以下代码中,如果我们想让st1的大小为100,st2的大小为200,如何去控制?
#define N 100 templateclass myStack { public: void Push(const T& x) {} private: T _a[N]; size_t _top; }; int main() { myStack st1; // 大小为100 myStack st2; // 大小为200 return 0; }
为此,我们就可以去增加一个非类型模板参数(常量)去解决以上问题
// 定义一个非类型模板参数N,N是一个常量 templateclass myStack { public: void Push(const T& x) {} private: T _a[N]; // N必须是一个常量,常量才能作为数组的大小 size_t _top; }; int main() { myStack st1; // 大小为100 myStack st2; // 大小为200 return 0; }
因此当我需要一个常量的时候,就可以去使用非类型模板参数
非类型模板参数要求是整形常量,不可以是其他类型
templateclass A {}; int main() { A<100> aa; return 0; }
非类型模板参数可以给缺省参数,但是缺省必须是从右往左缺省
template1.2 C++11 arrayclass myStack { public: void Push(const T& x) {} private: T _a[N]; size_t _top; }; int main() { myStack st1; // 大小为100 myStack st2; // 缺省 return 0; }
C++11中出现了一个新东西:array固定大小的数组(不支持pushback等),array就使用了非类型模板参数
int main()
{
array aa1; // C++11
int aa2[10]; // C语言
aa1[4] = 4;
aa2[4] = 4;
return 0;
}
C语言的数组与C++11中的array区别就是:一个是指针,一个是对象,并且在物理上,他们的大小都是一样的
vector的空间在堆上,array在栈上(栈空间不大)
所以array在实际上没有什么太大的价值
array唯一的优势:
array对越界的检查更加严格,C语言的越界不一定能检查出来,但是array可以查到
array的operator[]中间可以断言检查
二、模板的特化 2.1 函数模板特化
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,如下:
templatebool ObjLess(const T& left, const T& right) // obj:对象 { return left < right; } int main() { cout << ObjLess(1, 2) << endl; Date* p1 = new Date(2022, 3, 26); Date* p2 = new Date(2022, 4, 26); cout << ObjLess(p1, p2) << endl; return 0; }
templatebool ObjLess(const T& left, const T& right) // obj:对象 { return left < right; } int main() { cout << ObjLess(1, 2) << endl; Date* p2 = new Date(2022, 4, 26); Date* p1 = new Date(2022, 3, 26); cout << ObjLess(p1, p2) << endl; return 0; }
以上两段代码的p1,p2顺序交换后,答案就不对了——因为比较的是地址
解决方案1:参数匹配
bool ObjLess(const Date*& left, const Date*& right) // T就是Date*
{
return *left < *right; // 让Date之间进行比较,而非是Date*
}
解决方案2:专用化/特化 ——最好不去使用,一般使用方案1
函数模板的特化在函数名后面加上一个类型,然后调用这个类型来传参
templatebool ObjLess(T left, T right) // T就是Date* { return left < right; } template<> bool ObjLess (Date* left, Date* right) // 特化 { return *left < *right; } int main() { cout << ObjLess(1, 2) << endl; Date* p2 = new Date(2022, 4, 26); Date* p1 = new Date(2022, 3, 26); cout << ObjLess(p1, p2) << endl; return 0; }
函数模板的特化步骤: 1. 必须要先有一个基础的函数模板 2. 关键字template后面接一对空的尖括号<> 3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型 4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。2.2 类模板特化 2.2.1 全特化
类模板特化相比函数模板特化的区别在于类名后面不加模板参数,而函数模板特化中 ,函数后面要加模板参数
而全特化即是将模板参数列表中所有的参数都确定化。也就是所有模板参数都给一个确定的值
templateclass Data { public: Data() { cout << "Data " << endl; } private: T1 _d1; T2 _d2; }; // 特化——针对某些特殊类型特殊处理 template<> class Data // 类名后面不加模板参数 { public: Data() { cout << "Data " << endl; } private: int _d1; char _d2; }; void TestVector() { Data d1; // 调第一个 Data d2; // 调第二个 } int main() { TestVector(); return 0; }
特化的场景:
对比较方式进行特化,这样子就不需要去显示的传仿函数了
template2.2.2 偏特化struct Less { // 左<右 bool operator()(const T& x, const T& y) const { return x < y; } }; // 类型是Date*时,就不让他去调用普通类型直接比较 template<> struct Less { // 左<右 bool operator()(Date* x, Date* y) const { return x < y; } };
偏特化:任何针对模版参数进一步进行条件限制设计的特化版本,偏特化不一定是给部分值,而是对参数进一步的限制
// 类模板实例化 templateclass Data { public: Data() { cout << "Data " << endl; } private: T1 _d1; T2 _d2; }; // 全特化: template <> class Data { public: Data() { cout << "Data " << endl; } private: int _d1; char _d2; }; // 偏特化 —— 只要第二个参数是char就匹配这个偏特化 template class Data { public: Data() { cout << "Data " << endl; } private: T1 _d1; char _d2; }; int main() { Data d1; Data d2; Data d3; Data d4; return 0; }
下面代码也是一种偏特化,将参数的进一步的限制
templateclass Data // 只要是指针类型就走到这里 { public: Data() { cout << "Data " << endl; } private: T1 _d1; char _d2; }; template class Data // 只要是指针类型就走到这里 { public: Data() { cout << "Data " << endl; } private: T1 _d1; char _d2; }; int main() { Data d1; Data d2; Data d3; Data d4; return 0; }
三、模板的分离编译 3.1 什么是分离编译?
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
.h是放结构定义和函数声明
.cpp是存放函数的定义
为什么声明要与定义分离?——不这样子做项目难以维护
声明和定义分离的优势是方便维护 .h 了解到框架设计的基本功能 .cpp 了解具体实现细节
模板不支持分离编译
若将他们进行分离:链接错误
// vector.h templatevoid F2(const T& n);
// vector.cpp templatevoid F2(const T& n) // 编译的是实例化后的代码,此处不处理,因为没有实例化,所以没办法编译生成汇编代码 { cout << "void F2(const T& n)" << endl; }
类模板声明与定义分离
// vector.h templateclass A { public: A(); private: int _a; };
// vector.cpp templateA ::A() :_a(0) { }
// test.cpp
#include "vector.h"
int main()
{
A* p; // 有声明可以用这个类型——指针是4个字节固定大小,不存在链接不上的问题
A aa; // 这里就不行,因为他要去调用构造函数,只有声明,没有定义——链接找不到地址
return 0;
}
四、模板总结
【优点】
- 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
- 增强了代码的灵活性
【缺陷】
- 模板会导致代码膨胀(会实例化出多份)问题,也会导致编译时间变长
- 出现模板编译错误时,错误信息非常凌乱,不易定位错误


![[C++]模板进阶 [C++]模板进阶](http://www.mshxw.com/aiimages/31/1025813.png)
