C++模板为生成通用的类型声明提供了更好的支持,它可以将类型名(如int
、float
)作为参数名传递给接收方来建立类或函数。
问题场景
设想下,现在我们需要实现存储int
类型的栈,如下:
然后不到 2 分钟,三下五除二立马完成:
1 |
|
结果没过几天,发现int
类型不适合需求了,要改float
…..这下可算张心眼了,万一下次再改咋办?索性把类型使用typedef
定义下,所以我们有了第二版:
1 |
|
这次修改之后,可以缓口气了。
嗯,咖啡还是很香的! 直到你即需要存储int
型的栈,又需要float
类型的栈….除了 copy 一份代码,似乎没有其他办法了…
不过,今天的大招可以解决该问题-C++模板,一起来瞅瞅!
基本语法
1 |
|
现在,我们既有了即可以存储int
类型又可以存储float
类型的栈:
1 |
|
编译器会根据我们的声明,自动生成类,这样我们就不用自己复制粘贴了。
高级特性
看到上面的实现,是不是觉得这就是OC
中的泛型啊!没错,C++的模板确实和泛型很像,但它比泛型更强大。
非类型参数
模板不仅支持类型参数,并且支持非类型参数:
1 |
|
之后,我们可以指定该栈的大小:
1 |
|
这里需要注意下面几个方面:
- 非类型参数支持:整形、枚举、引用或指针,它是常量,不能修改
- 这里编译器会生成 2 个类,分别是存储 10、100 个元素的栈;若想改进,可以将数量作为参数传入构造器,动态生成内部的数组。
递归
一个模板的类型参数是其本身,可以递归。
1 |
|
这里定义了一个含有 20 个元素的listStack
,而每一个元素又是一个包含 10 个 int 类型的栈。相当于二维数组。
多个类型参数
模板支持多个类型参数:
1 |
|
默认值
类型参数和非类型参数都是可以有默认值的:
1 |
|
设置别名
别名可以简化代码书写,也可以提高可读性。例如上面的WGStack
,可以有下面的别名设置:
1 |
|
模板的具体化
由模板生成具体类,称为具体化。模板的具体化一共有 3 中形式:
隐式实例化
我们上面使用的例子,都属于该方式:直到需要生成具体类的时候,才会去生成,由编译器决定何时生成。
1 |
|
显式实例化
使用特定语法,书写语句,指示编译器生成具体类,即使没有使用该类生成对象。
1 |
|
部分具体化
例如,对于上面的模板:
1 |
|
该模板指定了两个类型参数,加入我们想实现:当T2为int类型时,生成的类有自己的行为
,那么就可以在现有基础上进行部分具体化:
1 |
|
当我们将所有类型参数都指定时,就出现了特殊情况,称之为显示具体化
。例如:
1 |
|
需要注意,对于WGPair<int, int> intPair;
声明,它符合隐式实例化
、部分具体化
以及显示具体化
三种,编译器会选择具体化程度最高的模板显示具体化
。
tips:
- 实例化,由模板到类的过程。
- 具体化,由泛型到具体类型的过程。
还可以借助部分具体化,来使用现有类型参数填充其他类型参数:
1 |
|
在成员变量中使用模板
模板可以作为结构、类或模板类的成员。
1 |
|
或者将内部的嵌套移动到外部实现,需要注意嵌套类的实现方式。
1 |
|
在模板参数中使用模板
模板可以声明类型参数,这个类型参数可以是另一个模板。
注意:和模板的递归使用有差别
1 |
|
模板类中的友元
模板类也可以声明友元函数,分为 3 类:非模板友元、约束模板友元、非约束模板友元。建议大家动手操作下,便于理解。
非模板友元
在模板类中声明正常的函数作为友元。例如:
1 |
|
约束模板友元
友元函数是模板函数,有自己的类型参数。例如:
1 |
|
非约束模板友元
在类的内部可以创建非约束模板友元
。例如:
1 |
|
总结下这三种友元的区别:
- 非模板友元,友元函数本身是常规函数,这样在该函数中访问模板实例化后的具体类时,无法做到通用性。
- 约束模板友元,友元函数是模板函数,这样模板类的类型参数和模板函数的类型参数可以相互具体化,具有通用性。
- 非约束模板友元,定义在模板类内部的模板函数,但是该模板函数的类型参数代表了,模板类的某一个实例化类。
参考
参考书籍均有PDF
版本,如有需要添加微信Niwaguan-_-
。
- C++ Primer Plus 第六版