如果两种类只是数据类型不同,而其他代码是相同的,与其编写新的类声明,不如编写一种泛型(独立于类型的)栈。然后将具体的类型作为参数传递给这个类。这样就可以使用通用的代码生成存储不同类型值的栈。
可以使用typedef处理这样的需求,但是有两个问题,一、每次修改类型都必须重新编辑头文件;二、在每个程序中都只能使用这种技术生成一种栈。
C++的类模板为生成通用的类声明提供了一种更好的方法;模板提供参数化类型,能够将类型名作为参数传递给接收方来建立类或函数。
C++标准模板库(STL)提供了几种功能强大而灵活的容器类模板。
定义模板类
template <class Type>,template告诉编译器,将要定义一个模板。尖括号内容相当于函数的参数列表。
也可以这么写:
template <typename Type>
或是:
template <class T>
template <typename T>
当模板被调用时,Type将被具体的类型值(int或string)取代。
原来类的声明:
typedef unsigned long Item;
class Stack
{
private:
enum {MAX = 10};
Item items[MAX];
int top;
public:
Stack( );
bool isempty( ) const;
bool isfull( ) const;
bool push (const Item & item);
bool pop(Item & item);
}
类模板信息必须都放在头文件中,不能把模板成员函数放到独立的实现文件中。因为模板不是函数,不能单独编译。在要使用这些模板的文件中包含该头文件。
类模板:
template<class Type>
Stack<Type>::Stack()
{
}
template <class Type>
bool Stack<Type>::isempty()
{
}
template <class Type>
bool Stack<Type>::isfull()
{
}
template <class Type>
bool Stack<Type>::push(const Type & item)
{
}
template <class Type>
bool Stack<Type>::pop(Type & item)
{
}
使用类模板
仅在程序包含模板并不能生成模板类,而必须请求实例化。需要声明一个类型为模板类的对象,并且使用所需的具体类型替换泛型名。
Stack<int> kernels;
Stack<string>colonels;
看到上述两个声明后,编译器将按Stack<Type>模板来生成两个独立的类声明和两组独立的类方法。
泛型标识符——Type——被称为类型参数,这意味着它类似于变量,但是赋给它们的不能是数字,而只能是类型。
必须,显式地提供所需的类型,这与常规的函数模板是不同的。
深入探讨模板类
可以将内置类型或对象作为类模板Stack<Type>的类型。指针也是可以的。但是如果不对程序做重大修改,将无法很好地工作。
创建不同的指针是调用程序的职责,而不是栈的职责。栈的任务是管理指针,而不是创建指针。
字符串本身永远不会移动,把字符串压入栈实际上是新建一个指向该字符串的指针,即创建一个指针,该指针的值是现有字符串的地址。
构造函数使用new创建一个用于保存指针的数组,析构函数删除该数组,而不是数组元素指向的字符串。
数组模板示例和非类型模板
常用作容器类,这是因为类型参数的概念非常适合于将相同的存储方案用于不同的类型。引入模板的主要动机是:容器类可提供重用的代码。
深入探讨模板设计和使用的其他几个方面。
具体来说,探讨一些非类型(或表达式)参数以及如何使用数组。
实现一种允许指定数组大小的简单数组模板:
方法一:在类中使用动态数组和构造函数参数来提供元素数目;->旧方法
方法二:使用模板参数来提供数组的大小;
模板头:template<class T, int n>
class 关键字标识这是类型参数,int指出n的类型为int,这是非类型参数,或表达式参数;
表达式参数有一些限制:可以是整型,枚举,引用或指针。
表达式参数的优点:使用自动变量维护内存栈,而不是用构造函数方法使用的new,delete来管理内存。
表达式参数的缺点:下面的声明将生成两个独立的类声明;
ArrayTP<double, 12> eggweights;
ArrayTP< double, 13> eggweights;
而构造函数的方法,就只要一份类声明。
另一个区别是:构造函数的方法更通用。数组大小是作为类数据成员存储在定义中。
模板多功能性
模板的作用:
用作基类
用作组件类
用作其他模板的类型参数
数组模板实现栈模板
数组模板来构造数组
递归使用模板
使用多个类型参数
template<class T1, class T2>
默认类型模板参数
可以为类型参数提供默认值;
template<class T1, class T2=int>
类模板:类型参数可提供默认值;
函数模板的类型参数不可听默认值;
非类型参数:类模板,函数模板都可以提供默认值;
模板的具体化
模板以泛型的方式描述类;
而具体化是使用具体的类型生成类声明;
1、 隐式实例化
是指在需要对象之前,不会生成类;
ArrayTP<double,30> * pt; //一个指针,还没有对象被创建
pt = new ArrayTP<double,30>; //创建一个对象
2、 显式实例化
template class ArrayTP<string,100>; //会产生一个类的实例,声明一个类;
虽然没有创建或提及类对象,编译器也将生成类声明。
3、 显式具体化
指的是特定类型的定义。
4、部分具体化
成员模板
模板可用作结构、类或模板类的成员。
类的成员(数据,方法)->都可以用模板表示;
而不仅仅是类用模板表示;
模板之中有模板,嵌套的;还可以在模板之外定义方法模板,成员模板;
将模板用作参数
将模板用作模板的参数;
模板包含类型参数和非类型参数:
template <template <typename T> class Thing>
template <typename T> class Thing 是模板的参数;
其中template <typename T> class 是参数模板(把模板作为参数用);是类型;
假设有以下声明:
Crab<King> legs;
Crab<King>是模板Crab具体的类型;模板参数是King,King也必须是一个模板类。
template <typename T>
class King {…};
模板类和友元
模板类声明也可以有友元。(友元:模板or非模板, 约束or非约束)
l 非模板友元;
l 约束模板友元,即友元的类型取决于类被实例化时的类型;
l 非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元;
1、 模板类的非模板友元函数
在模板类中将一个常规函数声明为友元:
template <class T>
class HasFriend
{
public:
friend void counts();
};
counts()函数成为模板所有实例化的友元。例如,它将是类hasFriend<int>和HasFriend<string>的友元。该友元与所有模板实例化的对象都具有友元关系(一对多)。该函数不是通过对象调用,没有对象参数。
template <class T>
class HasFriend
{
public:
friend void report(HasFriend<T> &);
};
这一种带模板类参数的友元。这是一种一对一的友元关系,即该友元只是某个具体类型的模板的友元函数。
2、 模板类的约束模板友元函数
3、 模板类的非约束模板友元函数
普通类+普通友元 (一对一)友元-类而言
普通类+模板友元 (多对一)友元-类而言
模板类+普通友元 (一对多)友元-类而言
模板类+模板友元 (多对多)友元-类而言
模板别名(C++)