📄 12章 模板.txt
字号:
C++大学教程 第12章 模板
教学目标
●用函数模板生成相关(重载)函数组
●区分函数模板与模板函数
●用类模板生成相关类型组
●区分类楼板与模板类
●了解如何重载模板函数
●了解模板、友元、继承与静态成员之间的关系
12.1 简介
本章介绍C++最强大的特性之一 —— 模板。模板使我们可以用一个代码段指定一组相关(重载)函数(称为模板函数)或一组相关类(称为模板类)。
我们可以对数组排序函数编写一个函数模板,然后Cc++自动生成模板函数,可以对int数组、float数组和字符串数组等等进行排序。
第3章介绍了函数模板。如果读者没有阅读该章,则这里再提供一些介绍和例子。
我们可以对堆栈类编写一个类模板,然后让C++自动生成如int、float和string堆栈类的类模板。
注意区分函数摸板与模板函数:函数模板和类模板像是具有各种形状的模板,而模板函数和模板类则相当于按照模板描绘,其形状都是相同的.只是画上不同的颜色。
软件工程视点12. 1
模板是C++的软件复用的功能之一。
本章介绍一些函数模板和类模板的例子,并介绍模扳与其他C++特性(如重载、继承、友元和static成员)之间的关系。
这里介绍的模扳机制的设计和细节基于Bjarne Stroustrup的论文《Parameterized Types for C++》,发表于1988年10月在科罗拉多州丹佛举办的USENIX C++会议上(Proceedings of the USENIX C++ Conference)。
本章只是关于模板问题的简介,第20章“标准模板库(STL)”将深入介绍模板容器类、迭代器和STL算法。第20章有几十个基于摸板的“有生命力的代码”,演示了更复杂的模板编程技术。
12.2 函数模板
重载函数通常是基于不同的数据类型完成类似的操作。如果对每种数据类型的操作是相同的,那么用函数模扳完成这项工作更为简洁和方便。程序员对函数模板的定义只编写一次。基于调用函数时提供的参数类型,C++自动产生单独的目标代码函数来正确地处理每种类型的调用。在C浯言中,这个任务是用预处理指令#define建立的宏完成的(见第17章)。但是,宏可能会产生副作用,并且使编译器不能进行类型检查。函数模板和宏一样的简洁,并且还能让编译器进行全面的类型检查。
测试与调试提示12.1
函数模板和宏一样允许软件复用。但与宏不同的是,函数模板还可以消除许多类型错误,因为C++提供了安全的全面类型检查。
所有的函数模板定义都是用关键字template开始的,该关键字之后是用尖括号<>括起来的形式参数表。每一个形式参数之前都有关健字class,例如:
template<class T>
或
template<class ElementType>
或
template<class BorderType,class FillType>
内部类型和自定义类型可用来指定传递给函数的参数类型、函数返回类型和声明函数中变量,函数模板中的形式参数的用法与之类似。该函数定义的方式与定义其他函数类似。注意关键字class指定函数模板类型参数,实际上表示“任何内部类型或用户自定义类型”。
常见编程错误12.1
函数模板的每个形式类型参数之前不放置关键字class(或新的关键字typename)。
下面看看图12.1的printArray函数模板,这个函数的用法见图12.2的完整程序。
1 template< class T >
2 void printArray( const T *array, const int count )
3 {
4 for ( int i = 0; i < count; i++ )
5 cout << array [i ] << " ";
6
7 cout << endl;
8 }
图 12. 1 函数模板
该函数模板把惟一的形式参数T(T一般作为类型参数)声明为函数printArray打印的数组类型。当编译器检测到程序源代码中调用函数printArray时,用printArray的第一个参数的类型替换掉整个模板定义中的T,并建立用来打印指定类型数组的一个完整的模板函数,然后再编译这个新建的函数。图12.2的程序演示了三个printArray函数,这三个函数分别需要一个int类型的数组、一个double类型的数组和一个char类型的数组。int类型数组的实例函数如下所示:
void printArray( const int *array, const int count )
{
for (int i = O; i < count; i++ )
cout << array [ i ] <<" ";
count << endl;
}
模板函数中的每一个形式参数要在函数参数表中至少出现一次。形式参数的名字可以只在模板函数的形式参数表中出现一次。同一个形式参数名可用于多个模板函数。
图12.2的程序反映了模板函数printArray的用法。程序首先实例化int数组a、double数组b和char数组c,长度分别为5、7、6。然后调用pfintArray打印每个数组,一次用a的第一个参数,类型为int*;一次用b的第一个参数,类型为double*;一次用c的第一个参数,类型为char*。
例如,下列语句:
printArray(a,aCount);
使编译器实例化printArray模板函数,类型参数T为int。下列语句:
printArray(b,bCount);
使编译器实例化第二个pfintArry模板函数,类型参数T为double。下列语句:
printArray(c,cCount);
使编译器实例化第三个printArray模板函数,类型参数T为char。
本例中,模板机制使程序员不必用下列原型编写三个重载函数:
void printArray( const int*, const int );
void printArray( const double*, const int );
void printArray( const char*, const int );
1 // Fig 12.2: fig12_02.cpp
2 // Using template functions
3 #include <iostream.h>
4
5 template< class T >
6 void printArray( const T *array, const int count )
7 {
8 for (int i = 0; i < count; i++ )
9 cout << array[ i ] <<" ";
10
11 cout << endl;
12}
13
14 int main()
15 {
16 const int aCount = 5, bCount = 7, cCount = 6;
17 int a[ aCount ] = { 1, 2, 3, 4, 5 };
18 double b[ bCount ] = { 1.1, 2.2, 3.3, 4.4, 6.5, 6.6, 7.7 };
19 char c[ cCount ] = "HELLO"; // 6th position for null
20
21 cout << "Array a contains:" << endl;
22 printArray( a, aCount ); // integer template function
23
24 cout << "Array b contains:" << endl;
25 printArray( b, bCount ); // double template function
26
27 cout << "Array c contains:" << endl;
28 printArray( c, cCount ); // character template function
29
30 return 0;
31 }
输出结果:
Array a contains:
1 2 3 4 5
Array b contains:
1.1 2.2 3.3 4.4 5.5 6.6 7.7
Array c contains:
H E L L O
图 12.2 使用模板函数
性能提示 12. 1
模板提供了软件复用的好处。请记住,尽管模板只编写一次,但程序中仍然实例化多个模板类的副本。这些副本会占用大量内存。
12. 3 重载模板函数
模板函数与重载是密切相关的。从函数模板产生的相关函数都是同名的,因此编译器用重载的解决方法调用相应函数。
函数模板本身可以用多种方式重载。我们可以提供其他函数模板,指定不同参数的相同函数名。例如,图12.2的printArray函数模板可以用另一printArray函数模板重载,用参数lowSubscriPt和highSubscript指定要打印的数组部分(见练习12.4)。
函数模板也可以用其他非模板函数(同名而参数不同)重载。例如,图12.1的printArray函数模板可以用一个非模板函数重载,指定以整齐的表格式分栏打印字符串数组(见练习12.5)。
常见编程错误12.2
如果使用用户自定义类的类型调用模板,而模板时该类型对象使用==、+、<=等运算符,那么这些运算符需要重载。如果不重载这些运算符,则会发生错误,固为编译器在这些函数不存在的情况下仍然调用这些重载的运算符函数。
编译器通过匹配过程确定调用哪个函数。首先,编译器寻找和使用最符合函数名和参数类型的函数调用。如果找不到,则编译器检查是否可以用函数模板产生符合函数名和参数类型的模板函数。
过去,这种与模板的匹配过程要求所有参数类型都完全匹配,而不能进行自动转换。现在已经没有这么严格,可以采用通常的重载规则。
常见编程错误12.3
编译器通过匹配过程确定调用哪个函数,如果找不到匹配或产生多个匹配,就全产生编译错误。
12.4 类模板
堆栈独立于栈中数据项的类型,这一点不难理解。但是,用程序实现堆栈的时候又必须提供数据类型,这为实现软件的复用性提供了一次很好的机会。所用的方法是描述一个通常意义上的堆栈,然后建立这个类的实例类。所建的实例类虽然是通用类的副本,但是它具有指定的类型。C++的模板类提供了这种功能。
软件工程视点12.2
类模板通过实例化通用类的特定版本提高了软件的复用性。
为了说明如何定制通用类的模板以形成指定的模板类,模板类需要一种或多种类型参数,所以模板类也常常称为参数化类型。
需要生成多种模板类的程序员只需简单地编写—个通用类模板的定义。在需要用模板建立一个新类的时候,程序员只需要用一种简洁的表示方法,编译器就会写出模板类的源代码。例如,堆栈类的模板可以作为编写各种类型堆栈的基础(如float类型、int类型或char类型的堆栈等等)。
图12.3中的程序定义了Stack(堆栈)的类模板。模板类与通常的类定义没有什么不同,只是以如下所示的首部开头(第8行):
template<class T>
上述首部指出了这是一个类模板的定义,它有一类型参数T(表示所要建立的Stack类的类型)。程序员不需要专门使用标识符T,任何标识符都可以使用。Stack中存储的元素类型在Stack类首部和成员函数定义中一般表示为T。稍后将介绍如何将T与特定类型(如double或id)相关联。
I // Fig. 12.3: tstackl.h
2 // Class template Stack
3 #ifndef TSTACK1_H
4 #define TSTACK1 H
5
6 #include <iostream.h>
7
8 template< class T >
9 class Stack {
10 public:
1l Stack( int = 10 ); // default constructor (stack size 10)
12 ~Stack() { delete [] stackPtr; } // destructor
13 bool push( const T& ); // push an element onto the stack
14 bool pop( T& ); // pop an element off the stack
15 private:
16 int size; // # of elements in the stack
17 int top; // location of the top element
18 T *stackPtr; // pointer to the stack
19
20 bool isEmpty() const { return top == -1; } // utility
21 bool isFull() const { return top == size - 1; } // functions
22 };
23
24 // Constructor with default size 10
25 template< class T >
26 Stack< T >::Stack( int S )
27 {
28 size = S > 0 ? S : 10;
29 top = -1; // Stack is initially empty
30 stackPtr = new T[ size ]; // allocate space for elements
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -