在本篇博客中,作者将会讲解C++模板的进阶用法。在学习进阶用法之前,你先要学会初阶的用法,如果你要学习初阶用法,可以看看下面这篇博客。
一.非类型模板参数
在模板初阶中,我们用到的模板都是类型模板参数,即要替换的参数都是一个类型。
如下图所示:
那么在Arr这个类中,我们可以实例化出一个10个空间的数组,但是如果我们要同时实例化出一个10个空间和100个空间的数组时,我们这个类是不满足的。
所以这个时候要用到非类型模板参数。
如下图所示:
此时这个时候,我们就可以同时实例化出两个Arr对象,同时这两个对象的大小是可以不一样的。STL库中array的实现原理就是这样。
注意!!!
非类型模板参数通常都是一个整数,比如:short、int、long long、size_t等等。
浮点数、类对象以及字符串不能作为非类型模板参数。
非类型模板参数必须要编译期就要知道结果。
二. 模板的特化
模板的特化就是将一个已经存在的模板再去写一个它的模板参数特化的版本。
光说很难说的通,我们来看看例子。
函数模板特化
如下图所示,我们写了一个比较函数,在第一次比较的时候,比较的是a、b两个变量,这个时候并没有问题。
而在第二组的比较中,出现了问题,正常来说,我们会希望得到一个正确的结果,即输出1,因为这个字符数组的内容是一致的,但是结果并不是我们想要的,因为,这个时候比较的是p1和p2这两个指针,很显然,这两个指针指向的地址是不同的,为了能达到我们想要的结果,这个时候要写一个模板特化。
模板特化写法如下:
函数模板特化步骤
1.首先要有一个基础的模板函数。
2.关键字template后面是一个空的<>
3.函数名后面跟着一个<>,<>里面是指定需要特定的类型
4.函数形参表:函数形参的形式要与基础模板函数完全相同。
类模板特化
在类模板特化中,又分为全特化和偏特化。
全特化
全特化就是将模板参数全部指定为特定的类型。如下图所示:
偏特化
在偏特化中又分为部分特化和更进一步限制参数。
部分特化
部分特化顾名思义就是特化部分参数。
更进一步限制参数
更进一步的限制参数可以表现为将类型限制为指针或者是引用。
三.模板分离编译
什么是分离编译,分离编译就是在一个项目工程中,有多个.c文件,要运行的时候,将所有.c文件分别编译成目标文件,最后再把所有目标文件链接起来形成我们的可执行程序。
如下图所示:
经常写代码的同学,应该对这种编写模式并不陌生,同时,我们在写程序的时候,可能会把函数的声明和定义分开来写。
如下图所示。
在上面的代码中,当我们运行的时候,程序不会出错,会正常的运行起来。
但是当我们将Add函数换成函数模板的时候,编译会报错。
在下面这种情况中,会报错。
原因
在讲解原因之前,我们要先来回顾一下编译链接的部分。
一个程序在形成之前要经历四个阶段:
预处理:
进行头文件的包含、宏替换、条件编译、去注释
编译:
将我们的C++代码编译成汇编代码
汇编:
将汇编代码编译成二进制机器码
链接:
将多个.c形成的.obj文件链接起来
注意!!!
每个.c文件的前三个阶段都是单独进行的,不会涉及到别的.c文件
同时,这里有个非常重要的点,程序在运行的时候,之所以能找到要调用的函数,那是因为在调用函数的地方,会有该函数地址,通过这个函数的地址就能找到该函数,从而去调用它。
非模板函数情况
首先我们来看看第一种情况为什么能编译通过。
预处理阶段
在第一种情况中,就只有个头文件的包含,其他都没涉及到。
头文件包含后Add.h的代码会包到Add.c和test.c的代码中。
如下:
后面的阶段
此时Add.c和test.c会分别编译成汇编代码,后变成二进制机器码,最后在链接。
模板函数情况
在模板函数中,之所以会报错是因为main函数找不到Add函数的地址而报的错。
预处理
在预处理阶段,和上面一样,Add.h文件在Add.c和test.c中展开。
编译
总结
所以模板函数不适合分离编译,如果是一个模板函数,则函数的定义和声明要写到一个文件中。
四.模板总结
【优点】
1. 模板复用了代码,节省资源,更快的迭代开发, C++ 的标准模板库 (STL) 因此而产生2. 增强了代码的灵活性【缺陷】1. 模板会导致代码膨胀问题,也会导致编译时间变长2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误