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