思维导图:
目录
一. 类型模板参数
1. 类型模板参数了解
类型模板参数指的是:参数类型名称出现在模板参数列表里面,并且是在class 或者是 typename
关键字之后。
2. 类型模板参数的使用
2.1 class 与 typename 的区别
一般情况下,默认使用 class 和 typename 没有什么本质区别
但是当我们在一个类里面再进行定义一个类的时候,当我们使用这个内嵌类型的时候就不能使用
class 关键字来定义参数模板了,此时需要使用typename 关键字。只单纯进行文字性输出,可能
不是很好理解,结合具体使用的场景,或许理解起来更容易。
(其实这个问题在对list 迭代器模拟实现的时候,就已经显现出来了),list 的迭代器本质是对指针
进行了封装以及相关运算符的重载。
在 类外定义一个函数,此函数可以实现对list 容器内数据的遍历。
发现此时编译报错:
分析:
因为此时list<T> 在编译的时候是一个没有进行实例化的类模板;编译器就无不知
道 list<T>::const_iterator 这是一个静态成员还是一个内嵌类型
解决:
此时的使用 typename 关键字,告知编译器这是一个内嵌类型,实例化的时候直接去类里面查找即
可。
二. 非类型模板参数
1. 非类型模板参数了解
类模板或函数模板的参数中有一个常量,可以把该参数当成一个常量来使用。
与类型模板参数的区分就是前面没有class 或者是typename
2. 非类型模板参数的使用
做个小测验:
分析:
1)非类型模板参数类型不能是浮点数,类对象,字符串。
2) 在编译的时候,非类型参数必须有一个确切的数值
三. 模板特化
在原有模板的基础上,限制模板参数的类型(也就是指定模板参数为特定的类型)
问题的导入:
写一个模板函数:比较2个数据大小
我们发现此时这个模板函数不能很好 的解决问题,这时候可以借助函数模板特化进行处理
1.函数模板特化
特化的步骤:
2. 类模板的特化
2.1 全特化
类模板全特化:把所有的模板参数指定为具体的类型
2.2 偏特化
类模板偏特化:把部分模板参数指定为具体的类型
偏特化:
1)必须先有一个原类的模板
2) 进行模板参数类型 的指定
// 原模板
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
// 偏特化同时也是对模板参数进行限制
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
private:
T1 _d1;
T2 _d2;
};
四.模板分离编译
1. 分离编译
一个工程由若干个源文件组成,每一个源文件都需要经过单独的编译,链接生成对应的目标文件,
最终把所有生成的目标文件一起进行链接,形成可执行程序。
2. 模板的分离编译
模板的声明和定义分别在不同的文件里面。
当出现进行链接的时候,出现链接错误。
分析:
一个程序的运行需要经过:预编译,编译,汇编,链接。(对于这4个过程还需要在细分,感兴趣
的友友们,可以看一下,前期的博客)
不知道各位是否有这样的疑惑:为什么 Func()不会出现链接的错误???
当对list .h 文件的内容进行汇编的时候,是可以生成对应 函数地址的,因此Func() 这个函数返回
类型,以及参数的具体情况是已知的,所有就可以进行函数栈帧的创建,就会生成对应的函数地址
3. 模板分离编译的解决
3.1 进行实例化
但是当我们实参类型是double ,char ,short的时候,就会再次出现同样的问题,需要我们一一
指定实例化,这并不是“一劳永逸”的方法
3.2 模板声明和定义在同一个文件里面
此时就不会出现链接错误:因为当对test.cpp 文件里面的内容进行汇编生成指令的时候,模板的声
明和定义都有,此时函数模板已经被实例化具体的函数了,有对应的地址。
4. 模板“复盘”
模板优点
1)可以复用
2)增强代码的灵活性(“适配性”)
模板缺点
1)代码“膨胀”,增加了编译的时间
2)编译报错的时候,错误信息不精确,定位不准确