模板提供参数化类型,即能够将类型名作为参数传递给接收方来建立类或函数。
-
定义类模板:
- 类模板语法:
#pragma once //stacktp.h -- a stack template #ifndef STACKTP_H_ #define STACKTP_H_ //声明 template <class Type> //或者tmplate <typename Type> class Stack { private: enum{MAX=10}; Type items[MAX]; int top; public: Stack(); bool isempty(); bool isfull(); bool push(const Type& item); bool pop(Type& item); //如果在类声明中定义了⽅法(内联定义),则可以省略模板前缀和类限定符。 }; //实现 template <class Type> //使用模板成员函数替换原有类的方法,每个函数头以template <class Type>声明打头; Stack<Type>::Stack() //同样应使⽤泛型名Type替换typedef标识符Item。另外,还需将类限定符从Stack::改为Stack<Type>:: { 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]; //根据push的实现可知栈顶top指向的位置是没有数据的,所以使用--top return true; } else { return false; } } #endif // STACKTP_H_
-
关键字template告诉编译器,将要定义⼀个模板。
-
尖括号中的内容 相当于函数的参数列表。
-
可以把关键字class看作是变量的类型名,该变量接受类型作为其值,把Type看作是该变量的名称。
-
当模板被调⽤时, Type将被具体的类型值(如int或string)取代。
-
在模板定义中,可以使 ⽤泛型名来标识要存储在栈中的类型。
模板的具体实现——如⽤来处理string对象的栈类—— 被称为实例化(instantiation)或具体化(specialization)。
- 不能将模板 成员函数放在独⽴的实现⽂件中(以前,C++标准确实提供了关键字 export,让您能够将模板成员函数放在独⽴的实现⽂件中,但⽀持该关 键字的编译器不多;C++11不再这样使⽤关键字export,⽽将其保留⽤于 其他⽤途)。
- 由于模板不是函数,它们不能单独编译。
- 模板必须与特定的模板实例化请求⼀起使⽤。为此,最简单的⽅法是将所有模板信息放在⼀个头⽂件中,并在要使⽤这些模板的⽂件中包含该头⽂件。
-
使用模板类:
-
仅在程序包含模板并不能⽣成模板类,⽽必须请求实例化。
-
需要声明⼀个类型为模板类的对象,⽅法是使⽤所需的具体类型替换泛型名。
Stack<int> kernels; Stack<string> colonels; //编译器将按Stack<Type>模板来⽣成两个独⽴的类声明和两组独⽴的类⽅法。 //类声明Stack<int>将使⽤int替换模板中所有的Type //类声明Stack<string>将⽤string替换Type。
-
泛型标识符——例如这⾥的Type——称为类型参数(type parameter),这意味着它们类似于变量,但赋给它们的不能是数字,⽽ 只能是类型。
-
必须显式地提供所需的类型,这与常规的函数模板是不同的,因为编译器可以根据函数的参数类型来确定要⽣成哪种函数;
//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; 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 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:red Please enter A to add a purchase order, P to process a PO, or Q to quit. A Enter a PO number to add:blue Please enter A to add a purchase order, P to process a PO, or Q to quit. A Enter a PO number to add:silver Please enter A to add a purchase order, P to process a PO, or Q to quit. P PO #silverpopped Please enter A to add a purchase order, P to process a PO, or Q to quit. P PO #bluepopped Please enter A to add a purchase order, P to process a PO, or Q to quit. P PO #redpopped 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
-
-
深入探讨模板类:
可以将内置类型或类对象⽤作类模板Stack的类型。指针可以 吗?
例如,可以使⽤char指针替换程序清单14.14中的string对象吗?毕竟,这种指针是处理C-⻛格字符串的内置⽅式。
答案是可以创建指针栈,但如果不对程序做重⼤修改,将⽆法很好地⼯作。编译器可以创建 类,但使⽤效果如何就因⼈⽽异了。
-
不正确的使用指针栈
切忌盲目使用模板
-
正确使用指针栈
方法:
- 让调⽤程序提供⼀个指针数组,其中每个指针都指向不同的字符串。
- 注意,创建不同指针是调⽤程序的职责,⽽不是栈的职责。
- 栈的任务是管理指针,⽽不是创建指针。
可以在模板声明或模板函数定义内使⽤Stack;
在类的外⾯, 即指定返回类型或使⽤作⽤域解析运算符时,必须使⽤完整的
Stack<Type>
。
-
-
数组模板示例和非类型参数:
-
模板常⽤作容器类,这是因为类型参数的概念⾮常适合于将相同的存储⽅案⽤于不同的类型。
-
为容器类提供可重⽤代码是引⼊模板 的主要动机为容器类提供可重⽤代码是引⼊模板 的主要动机.
-
允许指定数组⼤⼩的简单数组模板。
- ⼀种⽅法是在类中使⽤动态数组和构造函数参数来提供元素数⽬;
- 另⼀种⽅法是使⽤模板参数来提供常规数组的⼤⼩,C++11新增的模板array就是这样做的。
//arraytp.h -- Array Template #ifndef Arraytp_H_ #define Arraytp_H_ #include<iostream> #include<cstdlib> template<class T,int n> //使⽤**模板参数**来提供常规数组的⼤⼩ class ArrayTP //表达式参数int n可以是整型、枚举、引⽤或指针。因此,double m是不合法的,但double * rm和double * pm是合法的。 { private: T ar[n]; public: ArrayTP() {}; explicit ArrayTP(const T& v); virtual T& operator[](int i); virtual T operator[](int i)const; }; template<class T,int n> ArrayTP<T, n>::ArrayTP(const T& v) { for (int i = 0; i < n; i++) { ar[i] = v; } } template<class T, int n> T& ArrayTP<T, n>::operator[](int i) { if (i < 0 || i >= n) { std::cerr << "Brror in array limits: " << i << "is out of range\n"; std::exit(EXIT_FAILURE); } return ar[i]; } template<class T, int n> T ArrayTP<T, n>::operator[](int i)const { if (i < 0 || i >= n) { std::cerr << "Brror in array limits: " << i << "is out of range\n"; std::exit(EXIT_FAILURE); } return ar[i]; } #endif // !Arraytp_H_
-
关键字class(或在这种上下⽂中等价的关键字typename)指出T为类型参数,
-
int指出n的类型为int,这种参数(指定特殊的类型⽽不是⽤ 作泛型名)称为⾮类型(non-type)或表达式(expression)参数。
-
模板代码不能修改参数的值,也不能使⽤参数的地址。所 以,在ArrayTP模板中不能使⽤诸如n++和&n等表达式。
-
实例化模板时,⽤作表达式参数的值必须是常量表达式。
-
表达式参数⽅法使⽤的是为⾃动变量维护的内存栈。执⾏速度将更快,尤其是在使⽤了很多⼩型数组时。
-
表达式参数⽅法的主要缺点是,每种数组⼤⼩都将⽣成⾃⼰的模 板。也就是说,下⾯的声明将⽣成两个独⽴的类声明:
-
ArrayTP<double,12> eggweights; ArrayTP<double,13> donuts;
-
构造函数⽅法使⽤的是通过new和delete管理的堆内存;
-
下⾯的声明只⽣成⼀个类声明,并将数组⼤⼩信息传递给类的构 造函数:
-
Stack<int> eggs(12); Stack<int> dunkers(13);
-
另⼀个区别是,构造函数⽅法更通⽤,这是因为数组⼤⼩是作为类成员(⽽不是硬编码)存储在定义中的。这样可以将⼀种尺⼨的数组赋给另⼀种尺⼨的数组,也可以创建允许数组⼤⼩可变的类。
-
-
模板多功能性:
可以将⽤于常规类的技术⽤于模板类。
- 模板类可⽤作基类,也可⽤作组件类,还可⽤作其他模板的类型参数。
- 可以使⽤数组模板实现栈模板,
- 也可以使⽤数组模板来构造数组——数组元素是基于栈模板的栈。
template <typename T> class Array { private: T entry; ... public: ... }; template <typename Type> class GrowArray:public Array<Type>{...}; template <typename Tp> class Stack { Array<tp>ar; ... }; ... Array<Stack<int>> asi; //C++98要求使⽤⾄少⼀个空⽩字符将两个>符号分开,以免与运算符>>混淆。C++11不要求这样做。
-
递归使用模板
-
在模板语法中,维的顺序与等价的⼆维数组相反。
-
递归使用例子:
ArrayTP<ArrayTP<int,5>,10> twodee; //这使得twodee是⼀个包含10个元素的数组,其中每个元素都是⼀个包含5个int元素的数组。 //与之等价的常规数组声明如下: int twodee[10][5];
例子:
//twod.cpp -- making a 2-d array #include<iostream> #include"arraytp.h" int main(void) { using std::cout; using std::endl; ArrayTP<int, 10> sums; ArrayTP<double, 10> aves; ArrayTP<ArrayTP<int, 5>, 10> twodee; int i, j; for (i = 0; i < 10; i++) { sums[i] = 0; for (j = 0; j < 5; j++) { twodee[i][j] = (i + 1) * (j + 1); sums[i] += twodee[i][j]; } aves[i] = (double)sums[i] / 10; } for ( i = 0; i < 10; i++) { for (j = 0; j < 5; j++) { cout.width(2); cout << twodee[i][j] << ' '; } cout << ": sum = "; cout.width(3); cout << sums[i] << ", average = " << aves[i] << endl; } cout << "Done.\n"; return 0; }
运行结果:
1 2 3 4 5 : sum = 15, average = 1.5 2 4 6 8 10 : sum = 30, average = 3 3 6 9 12 15 : sum = 45, average = 4.5 4 8 12 16 20 : sum = 60, average = 6 5 10 15 20 25 : sum = 75, average = 7.5 6 12 18 24 30 : sum = 90, average = 9 7 14 21 28 35 : sum = 105, average = 10.5 8 16 24 32 40 : sum = 120, average = 12 9 18 27 36 45 : sum = 135, average = 13.5 10 20 30 40 50 : sum = 150, average = 15 Done.
-
-
使用多个参数类型
- 模板可以包含多个类型参数。
- 假设希望类可以保存两种值, 则可以创建并使⽤Pair模板来保存两个不同的值(标准模板库提供了类 似的模板,名为pair)。
例子:
//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>{"Duck",5}, Pair<string,int>{"Fresco",4}, Pair<string,int>{"Cafe",5}, Pair<string,int>{"Eats",3} }; int joints = sizeof(ratings) / sizeof(Pair<string, int>); cout << "Rating:\t Eatery\n"; for (int i = 0; i < joints; i++) { cout << ratings[i].second() << ":\t" << ratings[i].first() << endl; } cout << "Oops! Resvised rating:\n"; ratings[3].first() = "Fab"; ratings[3].second() = 6; cout << ratings[3].second() << ":\t" << ratings[3].first() << endl; return 0; }
运行结果:
Rating: Eatery 5: Duck 4: Fresco 5: Cafe 3: Eats Oops! Resvised rating: 6: Fab
- 在main( )中必须使⽤ Pair来调⽤构造函数,并将它作为sizeof的参数。这是因为类 名是Pair,⽽不是Pair。
- Pair是另⼀个完全不同的类的名称。
-
默认类型模板参数
-
模板的具体化:
-
成员模板:
-
将模板用作参数:
-
模板类和友元:
-
模板别名: