14-4类模板
类模板
14.4.1定义类模板
模板类的开头如下
template <类型参数表> |
类型参数表的写法如下:
class类型参数1, class类型参数2, ... |
例如
template <class Type> //这个Type可以用其它变量名替代(T) |
或者
template <typename Type> |
同时,用模板成员函数来替代原有的类方法,每个函数头都以相同的模板声明开头
template <class Type> |
例如
template <class T> |
注:不能将模板成员函数放在独立的文件中实现,因为它们不是函数,不能单独编译,必须和特定的模板实例化请求一起使用。
案例
|
14.4.2使用模板类
程序包含模板并不能生成模板类,而必须请求实例化。
因此,需要声明一个类型为模板类的对象。
Stack<int> kernels; |
上述声明编译器将使用模板生成两个独立的类声明和两组独立的类方法。
并将<>中的关键字替代原有的Type
**注:**必须显式地提供所需的类型
14.4.3深入讨论模板类
对于不同类型的参数,模板的实现效果也不同。一些实现并不正确。
1.不正确地使用指针栈
使用char*
编译时通过,但由于没有创建用于保存字符串的空间导致在读入数据时会崩溃
使用字符数组char [num]
使用数组名不能作为引用来使用,所以会出现问题
使用new来分配char*空间
只有一个变量导致压栈和出栈时的地址一样,没有效果
2.正确使用指针栈
方法之一是让调用程序创建一个指针数组,其中每个指针都指向一个分配好空间的字符串,再将这些指针放入栈。
14.4.4数组模板示例和非类型参数
讨论一些非类型参数以及如何使用数组来处理继承族
|
说明
template <class T, int n> |
关键字class指出T为类型参数,而int指出n为非类型参数(表达式参数)。
在实例化时,可以这样使用
ArrayTP<double, 12> eggweight; |
限制
1.表达式参数可以是整型、枚举、引用和指针。其余都不合法
2.模板代码不能修改参数的值,也不能使用参数的地址。
3.实例化模板时,表达式参数的值必须是常数表达式
14.4.5模板多功能性
模板类可以使用另一个模板类作为参数
Array <Stack<int>> asi; |
1.递归使用模板
例如,二维数组可以这样使用
ArrayTP <ArrayTP<int,5>,10> twodee; |
2.使用多个类型参数
模板可以包含多个类型参数
template <class T1,class T2> |
3.默认类型模板参数
可以为类型参数提供默认值
template <class T1,class T2 = int> |
注:虽然可以给类模板类型参数提供默认值,但不能为函数模板参数提供默认值。但对两者的非类型参数,都能提供默认值。
14.4.6模板的具体化
模板以泛型描述类,具体化是使用具体的类型生成类声明
1.隐式实例化
声明一个或多个对象,指出所需的类型。
注:编译器在需要对象之前不会生成类的隐式实例化
ArrayTP<double, 12> * pt; //未生成 |
2.显式实例化
使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化。
template class ArrayTP<string, 100>; |
注:该声明必须在模板定义的名称空间中
3.显式具体化
显式具体化是特定类型的定义。某些时候,可能需要为特殊类型实例化时,对模板进行修改,使其行为不同。
案例
假设已经为用于排序之后的数组的类定义了一个模板
template <typename T> |
另外假设模板使用>运算符进行比较。
当T是整型时,可以做到。
但当T是字符串时,就会出现问题。模板会正常工作,但按照字符串的地址排序,与我们所想的不符。
此时采用具体化版本
具体化格式:
template <> class Classname<specialized-type-name> {...}; |
所以我们将其具体化为如下专供模板:
template <> class SortArray<const char *> |
4.部分具体化
C++允许部分具体化,即部分限制模板的通用性
案例1
部分具体化的基础
//通用模板 |
部分具体化将template后的<>中的具体化参数去除,并在类名后加入
如果参数全部去除,则为显式具体化
template <> class Pair<int,int> {...}; |
案例2
指针形式
可以使用指针来提供特殊版本的部分具体化
template <class T> //通用版本 |
这样,凡是提供的是指针参数,都优先采用指针版本来具体化模板。
Feed<char *> fb; //指针模板 |
案例3
部分具体化还能用来设置各种限制
//通用版本 |
14.4.7成员模板
模板可以用作类,模板类等的成员
案例
|
14.4.8将模板用作参数
模板除了包含类型参数和非类型参数,还可以包含本身就是模板的参数
template <template <typename T> class Thing> |
其中
template <typename T> class
是类型,Thing
是参数
这意味着假设有如下声明:
Crab<King> legs; |
必须使得King是一个模板类,且与模板参数Thing匹配
案例
14.4.9模板类和友元
1.非模板友元
非模板友元函数是所有实例化的友元,不能通过对象调用,也没有对象参数。
可以访问:
1.全局对象
2.使用全局指针访问非全局对象
3.创建自己的对象
4.访问独立于对象的模板类的静态数据成员
注:为友元函数提供模板类参数,必须指明具体化。
案例
template <class T> |
对于每个具体化,都会生成一份对应的友元函数
注:report()本身不是模板函数,而只是使用了一个模板作为参数,也就意味着必须在类声明以外为需要的使用的友元定义显式具体化。
void report(HasFriend<short> &) {...}; |
2.约束模板友元函数
对上述方法进行改进,使友元函数本身成为模板的方法,以便不用创建各个版本的重载友元函数。
“约束”是指这个友元函数与类模板的参数保持一致,被类模板“约束”住了。
案例如下
要使类的每一个具体化都与都获得与友元匹配的具体化,有三步:
1.在类定义前面声明每个模板函数
template <typename T> void counts(); |
2.在函数中再次将模板声明为友元
template <typename TT> |
3.为友元提供模板定义
template <typename T> |
3.非约束模板友元函数
“非约束”是指友元函数是模板函数,但不被类模板的具体化所约束,而是在类模板具体化之后依旧保持模板状态,能够根据具体调用时的参数再具体化。
创建方法
在类内部声明友元的函数模板,在外部实现定义,不要再在外面做提前模板声明。
14.4.10 使用模板别名
1.使用typedef
typedef std::array<int,12> arri; |
2.使用using= (同样可用于正常的定义别名)
using arri = std::array<int,12>; |