通过前面的学习,我们已经对泛型编程、函数模板和类模板有了了解
关于模板,还有更多的内容等着我们
一、非类型模板参数
模板参数分为类型模板参数和非类型模板参数。
类型模板参数:在参数列表中,跟在class或typename后,用于接受不同类型的参数
例如:
template<class T>
class vector
{
//...
}
参数列表中的T,就是一个类型模板参数
非类型模板参数:用一个常量作为模板的参数,后续在类/模板中可以将该模板参数当成常量使用
例如:
template<class T, size_t N>
class array
{
public:
//...
private:
T _array[N];
};
其中参数列表中的N,就是一个非类型模板参数,我们可以向其传入一个参数,并在类中作为一个常量来使用
函数模板的非类型模板参数原理与类模板相同,例如:
非类型模板参数也可以给缺省值,例如:
但是需要注意以下两点:
- 浮点数、字符串以及类对象不允许作为非类型模板参数,即我们只能将整型家族的类型作为非类型模板参数
例如:
- 非类型模板参数必须在编译期就能确定结果,也就是我们不能将变量作为参数传入
例如:
C++11新增的array容器,就使用了非类型模板参数,其目的是用来替代C语言风格的定长数组
例如:
其相对于C语言风格的数组,优势在于检查越界更加严格,不管是越界读还是越界写都能马上检查出来
但是我们为什么不直接使用vector呢🤣
二、模板的特化
2.1 特化的情景
当我们使用模板进行泛型编程时,可以实现一些与类型无关的代码。
但是当遇到一些特殊类型时,我们可能就会得到一些错误的结果,这时就需要对这些特殊类型进行特殊处理,也就是模板的特化。
例如,我们实现一个用来进行小于比较的函数模板:
此时可以正常的实现比较大小的功能
但是当我们只有两个指针,想对指针指向的内容进行比较的时候,这个函数模板就可能会给出错误的结果,例如:
可以看到,在绝大多数情况下Less函数都可以正确的比较,但是遇到int*类型等特殊的场景时就会得到错误的结果。
在上述示例中,Less内部只对传入的指针本身进行比较,但是不会比较指针指向的内容,与我们的需求不符,此时就需要对模板进行特化。
2.2 特化的概念
模板的特化,即在原模板的基础上,针对特殊类型所进行特殊化的实现方式。
模板特化又分为函数模板特化和类模板特化。
2.3 函数模板特化
还是以Less为例,要对函数模板特化,首先我们需要一个函数模板
template<class T>
bool Less(T left, T right)
{
return left < right;
}
然后删除模板参数列表中的内容
template<>
bool Less(T left, T right)
{
return left < right;
}
接着在函数名后面加上一对尖括号,里面指定需要特化的类型,把函数形参列表中的类型改成该特化类型,并修改函数体中的内容
template<>
bool Less<int*>(int* left, int* right)
{
return *left < *right;
}
此时,我们就得到了一个特化后的函数模板,现在再来试试能不能比较指针
但是实际上,函数模板的特化意义不大,直接用函数重载不香吗?
2.4 类模板特化
类模板的特化又分为全特化和偏特化
(1)全特化
全特化,即将模板参数列表中所有的参数都给出确定的类型,例如:
(2)偏特化
偏特化又分为两种形式:
- 部分特化,即将参数列表中的一部分参数特化
例如:
- 对参数更进一步的限制,即不明确给出确定的参数,而是对原模板参数进行限制所得到的特化版本
例如:
三、模板分离编译
3.1 分离编译
一个程序(项目)由若干个源文件共同实现,每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式
3.2 模板的分离编译
模板是可以声明和定义分离的,前提是必须在同一个文件中
如果将模板的声明和定义分别放到两个文件,就会发生链接错误,例如:
C++程序在运行的时候,一般要经过以下步骤:
- 预处理
- 编译
- 汇编
- 链接
分离编译时,函数在编译时会生成地址和一堆汇编代码,编译器在链接时就会找到这个地址,用来调用函数
但是对于模板,在没有实例化前编译器不会生成具体的函数,也就没有对应的地址,链接时就会发生错误。
所以如果我们要对模板进行分离编译,就一定要在一个文件中进行。
如果有错误的地方,欢迎在评论区指出
完.