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 = }; // 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 = ;
} template <class Type>
bool Stack<Type>::isempty()
{
return top == ;
} 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 > )
{
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.\n"
<< "P to process a PO, or Q to quit.\n";
while (cin >> ch && std::toupper(ch) != 'Q')
{
while (cin.get() != '\n')
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\n";
else
st.push(po);
break;
case 'P':
case 'p': if (st.isempty())
cout << "stack already empty\n";
else {
st.pop(po);
cout << "PO #" << po << " popped\n";
break;
}
}
cout << "Please enter A to add a purchase order.\n"
<< "P to process a PO, or Q to quit.\n";
}
cout << "Bye\n";
return ;
}
效果:
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[] =
{
Pair<string, int>("The Purpled Duck", ),
Pair<string, int>("Jaquie's Frisco Al Fresco", ),
Pair<string, int>("Cafe Souffle", ),
Pair<string, int>("Bertie's Eats", )
}; int joints = sizeof(ratings) / sizeof (Pair<string, int>);
cout << "Rating:\t Eatery\n";
for (int i = ; i < joints; i ++)
cout << ratings[i].second() << ":\t"
<< ratings[i].first() << endl;
cout << "Oops! Revised rating:\n";
ratings[].first() = "Bertie's Fab Eats";
ratings[].second() = ;
cout << ratings[].second() << ":\t "
<< ratings[].first() << endl;
return ;
}
输出如下:
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];