14.4.1 定义类模板
下面以第10章的Stack类为基础来建立模板。原来的类声明如下:
typedef unsigned long Item;
class Stack
{
private:
enum {MAX = 10}; // constant specific to class
Item items[MAX]; // holds stack items
int top; // index for top stack item
public:
Stack();
bool isempty() const;
bool isfull() const;
// push() returns false if stack already is full, true otherwise
bool push(const Item & item); // add item to stack
// pop() returns false if stack already is empty, true otherwise
bool pop(Item & item); // pop top into item
};
采用模板时,将使用模板定义替换Stack声明,使用模板成员函数替换Stack的成员函数。和模板函数一样,模板类以下面这样的代码开头:
template <class Type>
关键字template告诉编译器,将要定义一个模板。尖括号中的内容相当于函数的参数列表。可以把关键字class看作是变量的类型名,该变量接受类型作为其值,把Type看作是改变量的名称。
这里使用class并不意味着Type必须是一个类;而只是表明Type是一个通用的类型说明符,在使用模板时,将使用实际的类型替换它。较新的C++实现允许在这种情况下使用不太容易混淆的关键字typename代替class:
template <typename Type> // new choice
可以使用自己的泛型名代替Type,其命名规则与其他标识符相同。当前比较流行的选项包括T和Type。在模板定义中,可以使用泛型名来标识要存储在栈中的类型。对于Stack来说,这意味着将声明中所有的typedef标识符Item替换为Type。例如,
Item items[MAX]; // holds stack items
应改为:
Type items[MAX]; // holds stack items
同样,可以使用模板成员函数替换原有类的类方法。每个函数头都将以相同的模板声明打头:
template <class Type>
同样应使用泛型名Type替换typedef标识符Item。另外,还需要将限定符从Stack::改为Stack<Type>::。例如,
bool Stack::push(const Item & item)
{
...
}
应改为:
template <class Type> // or template <typename Type>
bool Stack<Type>::push(const Type & item)
{
...
}
如果在类声明中定义了方法(内联定义),则可以省略模板前缀和类限定符。
程序清单14.13列出了类模板和成员函数。知道这些模板不是类和成员函数定义至关重要。它们是C++编译器指令,说明了如何生成类和成员函数定义。模板的具体是现——如用来处理string对象的栈类——被称为实例化(instantiation)或具体化(specialization)。不能将模板成员函数放在独立的实现文件中。由于模板不是函数,它们不能单独编译。模板必须与特定的模板实例化请求一起使用。为此,最简单的方法是将所有模板信息放在一个头文件中,并在要使用这些模板的文件中包含该文件头。
程序清单14.13 stacktp.h
// stacktp.h -- a stack template #ifndef STACKTP_H_ #define STACKTP_H_ template <class Type> class Stack { private: enum {MAX = 10}; // constant specific to class Type items[MAX]; // holds stack items int top; // index for top stack item public: Stack(); bool isempty(); bool isfull(); bool push(const Type & item); // add item to stack bool pop(Type & item); // pop top into item }; template <class Type> Stack<Type>::Stack() { top = 0; } template <class Type> bool Stack<Type>::isempty() { return top == 0; } template <class Type> bool Stack<Type>::isfull() { return top == MAX; } template <class Type> bool Stack<Type>::push(const Type & item) { if (top < MAX) { items[top++] = item; return true; } else return false; } template <class Type> bool Stack<Type>::pop(Type & item) { if (top > 0) { item = items[--top]; return true; } else return false; } #endif // STACKTP_H_
14.4.2 使用模板类
仅在程序包含模板并不能生成模板类,而必须请求实例化。为此,需要生命一个类型为模板类的对象,方法是使用所需的具体类型替换泛型名。例如,下面的代码创建两个栈,一个用于存储int,另一个用于存储string对象:
Stack<int> kernels; // create a stack of ints
Stack<string> colonels; // create a stack of string objects
看到上述声明后,编译器将按Stack<Type>模板来生成两个独立的类声明和两组独立的类方法。
程序清单14.14修改了原来的栈测试程序(程序清单11.12),使用字符串而不是unsigned long值作为订单ID。
程序清单14.14 stacktem.cpp
// stacktem.cpp -- testing the template stack class #include <iostream> #include <string> #include <cctype> #include "stacktp.h" using std::cin; using std::cout; int main() { Stack<std::string> st; // create an empty stack char ch; std::string po; cout << "Please enter A to add a purchase order. " << "P to process a PO, or Q to quit. "; while (cin >> ch && std::toupper(ch) != 'Q') { while (cin.get() != ' ') continue; if (!std::isalpha(ch)) { cout << 'a'; continue; } switch (ch) { case 'A': case 'a': cout << "Enter a PO number to add: "; cin >> po; if (st.isfull()) cout << "stack already full "; else st.push(po); break; case 'P': case 'p': if (st.isempty()) cout << "stack already empty "; else { st.pop(po); cout << "PO #" << po << " popped "; break; } } cout << "Please enter A to add a purchase order. " << "P to process a PO, or Q to quit. "; } cout << "Bye "; return 0; }
效果:
Please enter A to add a purchase order. P to process a PO, or Q to quit. A Enter a PO number to add: moonlightpoet Please enter A to add a purchase order. P to process a PO, or Q to quit. A Enter a PO number to add: moonlit Please enter A to add a purchase order. P to process a PO, or Q to quit. P PO #moonlit popped Please enter A to add a purchase order. P to process a PO, or Q to quit. P PO #moonlightpoet popped Please enter A to add a purchase order. P to process a PO, or Q to quit. P stack already empty Please enter A to add a purchase order. P to process a PO, or Q to quit. Q Bye
可以通过如下方法声明一个允许指定数组大小的简单数组模板:
template <class T, int n>
class ArrayTP
{
...
};
请注意class(或在这种上下文中等价的关键字typename)指出T为类型参数,int指出n的类型为int。这种参数(指定特定的类型而不是用作泛型名)称为非类型(non-type)或表达式(expression)参数。假设有下面的声明:
ArrayTP<double, 12> eggweights;
这将导致编译器定义名为ArrayTP<double, 12>的类,并创建一个类型为ArrayTP<double, 12>的eggweight对象。定义类时,编译器将使用double替换T,使用12替换n。
14.4.5 模板多功能性
可以将用于常规类的技术用于模板类。模板类可用作基类,也可用作组件类,还可用作其他模板的类型参数。例如,可以使用数组模板实现栈模板,也可以使用数组模板来构造数组——数组元素是基于栈模板的栈。即可以编写下面的代码:
template <typename T> // or <class T>
class Array
{
private:
T entry;
...
};
template <typename Tp>
class Stack
{
Array<Tp> ar; // use an Array<> as a component
};
...
Array < Stack<int> > asi; // an array of stacks of int
在最后一条语句中,C++98要求使用至少一个空白字符将两个>符号分开,以免与运算符>>混淆。C++11不要求这样做。
1.递归使用模板
可以递归使用模板。例如,对于前面的数组模板定义,可以这样使用它:
ArrayTP < ArrayTP<int,5>, 10> twodee;
2.使用多个类型参数
模板可以包含多个类型参数。例如,假设希望类可以保存两种值,则可以创建并使用Pair模板来保存两个不同的值(标准模板库提供了类似的模板,名为pair)。程序清单14.19所示的小程序是一个这样的示例。其中,方法first() const和second() const报告存储的值,由于这两个方法返回Pair数据成员的引用,因此让您能够通过赋值重新设置存储的值。
程序清单14.19 pairs.cpp
// pairs.cpp -- defining and using a Pair template #include <iostream> #include <string> template <class T1, class T2> class Pair { private: T1 a; T2 b; public: T1 & first(); T2 & second(); T1 first() const { return a; } T2 second() const { return b; } Pair(const T1 & aval, const T2 & bval) : a(aval), b(bval) { } Pair() {} }; template<class T1, class T2> T1 & Pair<T1,T2>::first() { return a; } template<class T1, class T2> T2 & Pair<T1,T2>::second() { return b; } int main() { using std::cout; using std::endl; using std::string; Pair<string, int> ratings[4] = { Pair<string, int>("The Purpled Duck", 5), Pair<string, int>("Jaquie's Frisco Al Fresco", 4), Pair<string, int>("Cafe Souffle", 5), Pair<string, int>("Bertie's Eats", 3) }; int joints = sizeof(ratings) / sizeof (Pair<string, int>); cout << "Rating: Eatery "; for (int i = 0; i < joints; i ++) cout << ratings[i].second() << ": " << ratings[i].first() << endl; cout << "Oops! Revised rating: "; ratings[3].first() = "Bertie's Fab Eats"; ratings[3].second() = 6; cout << ratings[3].second() << ": " << ratings[3].first() << endl; return 0; }
输出如下:
Rating: Eatery 5: The Purpled Duck 4: Jaquie's Frisco Al Fresco 5: Cafe Souffle 3: Bertie's Eats Oops! Revised rating: 6: Bertie's Fab Eats
3.默认类型模板参数
类模板的另一项新特性是,可以为类型参数提供默认值:
template <class T1, class T2 = int> class Topo {...};
这样,如果省略T2的值,编译器将使用int:
Topo<double, double> m1; // T1 is double, T2 is double
Topo<double> m2; // T1 is double, T2 is int
14.4.6 模板的具体化
*** 隐式实例化、显式实例化和显式具体化。……
14.4.7 成员模板
……
14.4.8 将模板用作参数
模板可以包含类型参数(如typename T)和非类型参数(如int n)。模板还可以包含本身就是模板的参数,这种参数是模板新增的特性,用于实现STL。
template <template <typename T> class Thing>
class Crab
模板参数是template <typename T> class Thing,其中template <typename T> class 是类型,Thing是参数。
14.4.9 模板类和友元
……
14.4.10 模板别名
template<typename T>
using arrtype = std::arrat<T,12>; // template to create multiple aliases
这将arrtype定义为一个模板别名,可使用它来指定类型,如下所示:
arrtype<double> gallons; // gallons is type std::array<double, 12>
arrtype<int> days; // days is type std::array<int, 12>
arrtype<std::string> months; // months is type std::array<std::string, 12>
C++11允许将语法using=用于非模板。用于非模板时,这种语法与常规typedef等价:
typedef const char * pc1;
using pc2 = const char *;
typedef const int *(*pa1)[10];
using ps2 = const int *(*)[10];