ITEEDU

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)相关联。

  // Fig. 12.3: tstackl.h
 // Class template Stack
 #ifndef TSTACK1_H
 #define TSTACK1 H
 #include<iostream.h> 
 template< class T >
 class Stack {
 public:
l   Stack( int = 10 );   // default constructor (stack size 10)
   ~Stack() { delete [] stackPtr; } // destructor
   bool push( const T& ); // push an element onto the stack
   bool pop( T& );      // pop an element off the stack
 private:
   int size;          // # of elements in the stack
   int top;           // location of the top element
   T *stackPtr;        // pointer to the stack
   bool isEmpty() const { return top == -1; }     // utility
   bool isFull() const { return top == size - 1; } // functions
 };
 // Constructor with default size 10
 template< class T >
 Stack< T >::Stack( int S )
 {
   size = S > 0 ? S : 10;
   top = -1;            // Stack is initially empty
   stackPtr = new T[ size ]; // allocate space for elements
 }
 // Push an element onto the stack
 // return true if successful, false otherwise
 template
 bool Stack< T >::push( const T &pushValue )
 {
   if (!isFull() ) {
     stackPtr[ ++top ] = pushValue; // place item in Stack
     return true;  // push successful
   }
   return false;    // push unsuccessful
 }
 // Pop an element off the stack
 template
 bool Stack< T >::pop( T &popValue )
 {
   if (!isEmpty() ) {
     popValue = stackPtr[ top-- ];  // remove item from Stack
     return true;  // pop successful
   }
   return ffalse;    // pop unsuccessfu
 }
 #endif
 // Fig. 12.3: fig12_03.cpp
 // Test drive for stack template
 #include <iostream.h>
 #include "tstackl.h"
 int main()
 {
   Stack< double > doubleStack( 5 );
   double f = 1.1;
   cout << "Pushing elements onto doubleStack\n";
   while ( doubleStack.push( f ) ) { // success true returned
     cout << f << ' ';
     f += 1.1;
     }
   coout << "\nStack is full. cannot push "<< f
         << "\n\nPopping elements from doubleStack\n";
   while ( doubleStack.pop( f ) )  // success true returned
     cout << f << ' ';
     cout << "\nStack is empty. Cannot pop\n";
   Stack< int > intStack;
   int i = 1;
   cout << "\nPushing elements onto intStack\n";
   while ( intStack.push( i ) ) { // success true returned
     cout << i << ' ';
     ++i;
  }
   cout << "\nStack is full. Cannot push " << i
        << "\n\nPopping elements from intStack\n";
   while ( intStack.pop( i ) )  // success true returne
     cout << i << ' ';
   cout << "\nStack is empty. Cannot pop\n";
   return O;
 }

输出结果:

Pushing elements onto doubleStack

1.1 2.2 3.3 4.4 5.5

Stack is full. Caunot push 6.6

Pepping elements from doubleStack

5.5 4.4 3.3 2.2 1.1

Stack is empty. Cannot pop

Pushing elements onto intStack

1 2 3 4 5 6 7 8 9 10

Stack is full. Cannot push 11

Popping elements form intStack

10 9 8 7 6 5 4 3 2 1

Stack is empty. Cannot pop

图12.3 演示类模板 Stack

下面建立一个测试堆栈类模板(见图12.5的输出)的驱动程序(函数main)。程序在开始的时候实例化了一个大小为5的对象doublestack。该对象声明为类Stack<double><称为double类型的Stack类)的对象。为了产生出double类型的Stack类的源代码,编译器会自动把模板中的参数类型T替换成double。尽管程序看不到这个源代码,但仍将其放进源代码中编译。

然后程序成功地把1.1、2.2、3.3、4.4和5.5这几个double值压入(push)堆栈doubleStack。当试图将第六个值压人堆栈中的时候,push循环中止(栈已经满了,因为它只能容纳5个元素)。

然后程序再将这5个元素弹出(pop)堆栈(以LIFO顺序)。在试图弹出第六个元素的时,出栈循环中止,因为这时堆栈已经空了。

接下来,程序用下面的声明语句实例化了一个int类型的堆栈intStaek:

Stack<int>intStack

因为没有指定堆栈的大小,所以使用默认构造函数(第11行)中的默认值10作为堆栈的大小。重复上述操作,用循环结构不断向intStaek中压入整数值,直到栈满为止,然后再循环从堆栈中弹出数值,直到栈空为止。

在类模板首部以外的成员函数定义都要以下面的形式开头:

template<class T>

然后,成员函数的定义与普通成员函数的定义相似,只是Stack元素的类型要用类型参数T表示。二元作用域运算符和Stack<T>类模板将成员函数的定义与正确的类模板范围联系起来。本例中,类名是Stack<T>。当建立类型为Stack<double>的对象doubleStack的时候,Stack的构造函数使用new建立了一个表示堆栈的double类型数组。因此,对于语句:

stackPtr = new T [size];

编译器将在模板类Stack<double>中生成下面的代码:

stackPtr new double[size];

注意图12.3函数main中的代码即main上半部分的doubleStack操作和main下半部分的intStaek操作基本相同。这里又可以使用函数模板。图12.4的程序用函数模板testStack进行与图12.3相同的工作,将一系列值压入Stack<T>中并从Stack<T>中弹出数值。函数模板testStack用参数T表示Stack<T>中保存的数据类型。该函数模板取4个参数:Stack<T>类型对象的引用、类型为T的值用作压入Stack<T>的第一个值、类型为T的值用作压入Stack<T>的增量值以及const char*类型的字符串表示输出的Stack<T>对象名。函数main只是实例化Stack<double>类型对象doubleStack

和实例化Stack<int>类型对象intStaek,如下所示(第37行到第38行):

testStack(doubleStack,1.1,1.1,"doubleStack");

teststack(intStack,1,1,"intstack");

注意图12.4的输出与图12.3的输出一致。

 // Fig. 12.4: fig12_04.cpp
 // Test driver for Stack template.
 // Function main uses a function template to manipulate
 // objects of type Stack< T >.
 #include <iostream.h>
 #include "tstack1.h"
 // Function template to manipulate Stack< T >
  template< class T >
 void testStack(
   Stack< T > &theStack,  // reference to the Stack< T >
   T value,             // initial value to be pushed
   T increment,          // increment for subsequent values
   const char *stackName ) // name of the Stack < T > object
 {
   cout << "\nPushing elements onto "<< stackName << '\n';
   while ( theStack.push( value ) ) { // success true returned
     cout << value << ' ';
     value += increment;
   }
   cout << "\nStack is full. Cannot push" << value
        << "\n\nPopping elements from" << stackName << '\n';
   while ( theStack.pop( value ) )  // success true returned
     cout << value << ' ';
   cout << "\nStack is empty. Cannot pop\n";
 }
 int main()
 {
   Stack< double > doubleStack( 5 );
   Stack< int > intStack;
   testStack( doubleStack, 1.1, 1.1, "doubleStack" );
   testStack( intStack, 1, 1, "intStack" );
   return O;
 }

输出结果:

Pushing elements onto doubleStack

1.2 2.2 3.3 4.4 5.5

Stack is full. Cannot push 6.6

Popping elements from doubleStack

5.5 4.4 3.3 2.2 1.1

Stack is empty. Cannot pop

Pushing elements onto intStack

1 2 3 4 5 6 7 8 9 10

Stack is full. Cannot push 11

Popping elements form intStack

10 9 8 7 6 5 4 3 2 1

Stack is empty. Cannot pop

图 12.4 向函数模板传递Stack模板对象