第十章 模板
模板( Template) 是 C++支持参数化多态的工具,使用
模板可以减少程序员的重复劳动,使程序员可以建立具有通用
类型的函数库和类库。
所谓模板是一种使用无类型参数来产生一系列函数或类的
机制, 是 C++的一个重要特性 。 它的实现, 方便了更大规模的
软件开发 。
若一个程序的功能是对某种特定的数据类型进行处理,则
可以将所处理的数据类型说明为参数,以便在其他数据类型的
情况下使用,这就是模板的由来。模板是以一种完全通用的方
法来设计函数或类而不必预先说明将被使用的每个对象的类型。
通过模板可以产生类或函数的集合,使它们操作不同的数据类
型,从而避免需要为每一种数据类型产生一个单独的类或函数。
10.1 模板概念
为什么要引进模板概念:
例如, 设计一个求两参数最大值的函数, 不使用模板时, 需要定
义四个函数:
int max(int a,int b) {return(a>b)?a,b; }
long max(long a,long b) {return(a>b)?a,b; }
double max(double a,double b) {return(a>b)?a,b; }
char max(char a,char b) {return(a>b)?a,b; }
我们从上可以看到, 这四个函数的功能都是一样的, 只不过
函数的参数类型和返回类型不一样, 显然这样设计就显的程序代
码很冗余, 那怎么解决该问题呢:
采用宏定义,#define max(x,y) ((x>y)?(x):(y))
在 C++中, 宏替换不进行任何类型检查, 则显然不符合
C++的强类型语言, 即数据类型必须强制说明和检查 。 不鼓励
使用宏定义, 解决这问题可以用 C++的模板函数解决 。
10.1 模板概念
C++程序由类和函数组成, 模板也分为类模板 ( class
template) 和函数模板 ( function template) 。 在说明了
一个函数模板后, 当编译系统发现有一个对应的函数调用时,
将根据实参中的类型来确认是否匹配函数模板中对应的形参,
然后生成一个重载函数 。 该重载函数的定义体与函数模板的函
数定义体相同, 它称之为模板函数 ( template function) 。
同样,在说明了一个类模板之后,可以创建类模板的实
例,即生成模板类。
若使用 模板函数,则只需定义一个 函数模板,
template <class type>
type max(type a,type b)
{ return(a>b)?a,b; }
10.2 函数模板
C++提供的函数模板可以定义一个对任何类型变量进行操
作的函数, 从而大大增强了函数设计的通用性 。 使用函数模板
的方法是先说明函数模板, 然后实例化成相应的模板函数进行
调用执行 。
函数模板说明:
函数模板的一般说明形式如下:
template <class 模板参数表 >
<返回值类型 > <函数名 >( 模板函数形参表 )
{ //函数定义体 ;
}
其中, template是定义模板的关键字, <模板参数表 >可
以包含基本数据类型, 也可以包含类类型, 如果类型形参多于
一个, 则每个类型参数都要使用 class,并且它们之间用逗号隔
开 。 <模板函数形参表 >中的参数必须是惟一的 。
10.2 函数模板
函数模板定义不是一个实实在在的函数, 编译系统不为
其产生任何执行代码 。 该定义只是对函数的描述, 表示它每
次能单独处理在类型形式参数表中说明的数据类型 。
#include <iostream.h>
template <class T>
T max(T x,T y)
{ return(x>y?x:y);
}
main()
{ int x = 3,y = 4;
double a = 1.2,b = 3.4;
cout << max(x,y) << endl;
cout << max(a,b) << endl;
}
结果:
4
3.4
#include <iostream.h>
template <class T>
T min(T a[],int n)
{ int i;
T minv=a[0];
for(i=1; i<n; i++)
if(minv>a[i])
minv=a[i];
return minv;
}
例:编写一个对具有 n个元素的数组 a[ ]求最小值的程序,
要求将求最小值的函数设计成函数模板。
void main()
{ int a[]={1,3,0,2,7,6,4,5,2};
double b[]={1.2,-3.4,6.8,9,8};
cout<<”a 数组的最小值为:”
<< min(a,9)<< endl;
cout<<”b数组的最小值为:”
<< min(b,4)<< endl;
}
此程序的运行结果为:
a数组的最小值为,0
b数组的最小值为,-3.4
10.2 函数模板
使用函数模板:
函数模板是对一组函数的描述说明, 不直接产生可执行的
代码, 所以不能直接执行, 而模板函数是某个函数模板的实例化,
它是可以执行的 。 一个函数模板可以产生多个模板函数 。
当编译系统发现有一个函数调用,<函数名 >( <实参表
>) ;时, 将根据 <实参表 >中的类型生成一个重载函数即模板
函数 。 该模板函数的定义体与函数模板的函数定义体相同, 而 <
形参表 >的类型则以 <实参表 >的实际类型为依据 。
对模板函数的说明和定义必须是全局作用域。模板不能被说
明为类的成员函数。
模板函数有一个特点,虽然模板参数 T可以实例化成各种类
型,但是采用模板参数 T的各参数之间必须保持完全一致的类型。
模板类型并不具有隐式的类型转换,例如在 int与 char之间、
float与 int之间,float与 double之间等的隐式类型转换。而这
种转换在 C++中是非常普遍的。
10.2 函数模板
模板函数也遵循普通函数重载规则支持重载,一般来说,
重载函数主要解决函数名相同、算法相同、但参数个数或类型
不同的问题,而函数模板只解决函数名相同、算法相同、参数
个数也相同,仅类型不同的问题。 并且要注意函数模板和重载
函数的二义性问题。
#include <iostream.h>
template <class Type>
Type max(Type v1,Type v2)
{ return(v1 > v2?v1:v2);
}
void main()
{ cout<<max(100,300)<<endl;
cout<<max(32.1,3.14)<<endl;
cout<<max(“Zhang”,”Li”)<<endl;
}
char *max(char *v1,char *v2)
{return(strcmp(x,y)>0?x:y);}
所以,当一般函数与同名模板函数同
时存在时,调用顺序是一般函数优先。
10.2 函数模板
下面我们编写一个通用的排序程序:
#include <iostream.h> // 10-2.cpp
template <class T>
void GeneralSort(T *a,int n)
{ int i,j; T tt;
for(i=1; i < n-1; i ++) {
k = i;
for(j=i+1; j <n; j ++)
if(a[j] < a[k]) k = j;
if(k != i) {
tt = a[k];
a[k] = a[i];
a[i] = tt;}
}
}
}
main()
{ int i,x[] = {5,2,8,7};
double y[] = {2.8,1.2,
3.3,0.6};
GeneralSort(x,4);
GeneralSort(y,4);
for(i=0; i < 4; i ++)
cout << x[i] <<,,;
cout << endl;
for(i=0; i < 4; i ++)
cout << y[i] <<,,;
cout << endl;
}
10.2 函数模板
函数模板方法克服了 C语言解决上述问题时用大量不同
函数名表示相似功能的坏习惯;克服了宏定义不能进行参数
类型检查的弊端;克服了 C++函数重载用相同函数名字重
写几个函数的繁琐。因而,函数模板是 C++中功能最强的
特性之一,具有宏定义和重载的共同优点,是提高软件代码
重用率的重要手段。
10.3 类模板
与使用模板函数的原因类似, 有时也会遇到一个类体结构
完全相同, 只是内部的某些数据类型不同的情况, 这时候, 为
了实现代码重用, 在 C++语言中, 可以使用 模板类 。
类模板使的在设计一个类时, 可以将数据类型做为类的参
数 。 它可以为各种不同的数据类型的类定义一种模板, 在引用
时使用不同的数据类型实例化该类模板, 从而形成一个类的集
合, 所以, 对象是类的特例, 而类又是类模板的特例, 类模板
也称为类发生器 。 譬如我们设计一个整数数组类如下:
class int_array {
int *data; int n;
public:
int_array(int *dt,int i) {data = dt; n = i;}
void sort();
int find(int a);
int sum();
};
10.3 类模板
该类只能处理整数数组类, 如果还需处理实数数组类时:
class float_array {
float *data; int n;
public:
float_array(float *dt,int i) {data = dt; n = i;}
void sort();
int find(float a);
float sum();
};
如果还需处理 char,long等数组呢?
从上面两个类我们可以看出, 这些类结构相同, 只是所处理
的数组元素类型不同, 导致要设计每种类型的不同类, 造成
大量代码重复 。 解决这问题就要用类模板 。
10.3 类模板
类模板定义说明:
template <class 模板类形参 1,class 模板类形参 2,… >
class <类名 > {
// 类说明体;
};
模板类成员函数定义:
在使用内联函数定义时, 模板成员函数的定义与普通的成员
函数定义类似 。 在类体外定义时:
template <class 模板类形参 1,class 模板类形参 2,… >
<返回类型 ><类名 ><类属形参表 >::<成员函数 >( 函数参数表 )
{
// 成员函数定义体 ;
}
10.3 类模板
<模板类形参 >中的形参要加上 class关键词, 形参可以是
C++中的任何基本的或用户定义的类型 。 对在形参表中的每个
类型, 必须要使用关键词 class,如果多于一个, 则每个形参都
要使用关键词 class。
<模板类形参 >也可以包含表达式参数,表达式参数经常是
数值。对模板类进行实例化时给这些参数所提供的变量必须是
常量表达式。类模板参数列表决不能是空的,如果其中有一个
以上的参数,则这些参数必须要用逗号分开。
如,template <class T1,int exp1,class T2>
class someclass {
// 类体内容;
};
类模板 someclass的第二个参数是表达式。
类模板的成员函数的体外定义,每个前面都必须用与声明
该类模板一样的表示形式加以声明,其他部分同一般的成员函
数定义。
10.3 类模板
这样的一个说明 ( 包括成员函数定义 ) 不是一个实实在
在的类, 只是对类的描述, 称为类模板 ( class template) 。
类模板必须用类型参数将其实例化为模板类后, 才能用来生成
对象 。 一般地, 其表示形式为:
类模板名 <类型实参 > 对象名 ( 值实参表 )
例如,array <int> nn(ndt,5);
array <float> ff(fdt,5);
someclass <int,10,float> pp;
其中类型实参表表示将类模板实例化为模板类时所用到的
类型(包括系统固有的类型和用户已定义类型),值实参表表
示将该模板类实例化为对象时其构造函数所用到的变量。一个
类模板可以用来实例化多个模板类。
10.3 类模板
template <class T> //10-3-1.CPP
class array {
T *data; int n;
public:
array(T *dt,int i) {
data = dt; n = i;
}
void sort();
int find(T a);
T sum() {
T s = 0;
for(int i=0; i < n; i ++)
s += data[I];
return s;
}
};
在类体外定义函数 sum如下:
template <class T>
T array<T>::sum()
{ T s = 0;
for(int i=0; i<n; i++)
s += data[i];
return s;
}
void main()
{ int ndt[] = {7,8,1,4,6};
float fdt[] = {1.2,6.7,
2.5,9.8 };
array <int> nn(ndt,5);
array <float> ff(fdt,4);
cout<< nn.sum() << endl;
cout<< ff.sum() << endl;
}
10.3 类模板
#include <iostream.h>
template <class Type>
class Array {
private:
Type *element;
int size;
public:
Array(const int size);
~Array();
Type &operator[](const int index);
void operator=(Type temp);
void sort();
int find(Type a);
Type sum();
};
10.3 类模板
template <class Type> inline
Array<Type>::Array(const int s)
{ size = s;
element = new Type[size];
for(int i=0; i < size; i ++)
element[i] = 0;
}
template <class Type> inline
Array<Type>::~Array()
{ delete element ;
}
10.3 类模板
template <class Type>
Type &Array<Type>::operator[](const int index)
{ if(index < 0 || index > size-1) {
cout <<,\n Index value of,<< index
<<, is out of bound.\n” ;
exit(2);
}
return(element[index]);
}
template <class Type>
void Array<Type>::operator=(Type temp)
{
for(int i=0; i < size; i ++)
element[i] = temp;
}
10.3 类模板
void main()
{ Array <int> Iobj(20);
Array <double> Dobj(20);
Array <char> Cobj(20);
cout.flags(ios::showpoint);
for(int i=0;i < 20; i++) {
Iobj[i] = i; Dobj[i] = i;
}
Cobj = ?C?; Dobj[22] = 15.87;
for(i=0;i < 20; i++) {
cout << setw(3) << Iobj[i] << setw(15)
<< Dobj[i] <<,, << Cobj[i] << endl;
}
}
10.3 类模板的继承关系
模板类的继承关系:
模板类的派生继承和普通类相似,也分为 public,protected
和 private三种。模板派生类中的成员访问规则和普通类也是
一样的,一个模板类可以作为一个普通的派生类,也可以作为
其他模板类的基类。
class Base { // 10-3-3.cpp
protected:
int val;
public:
get_value()
{ cout <<,Value:” << val << endl;
return;
}
};
10.3 类模板的继承关系
template <class Type>
class SubClass:public Base {
private:
Type data;
public:
set_value(Type obj)
{ val = 100;
data = obj;
}
get_data()
{ cout <<,Data:”
<< data << endl;
}
};
main()
{ SubClass <int> isc;
SubClass <float> fsc;
isc.set_value(20);
fsc.set_value(8.56);
isc.get_value();
isc.get_data();
fsc.get_value();
fsc.get_data();
}
10.3 类模板的继承关系
若模板类为基类,那么派生类也必定是模板类,派生类的
形式参数表中必须包含基类中的所有形式参数,并且这两个参
数表中的参数在派生类中都可以使用:
#include <iostream.h> // 10-3-4.cpp
template <class Type>
class Base {
public:
show(Type obj)
{ cout << "Base::Show," << obj << endl;
}
};
10.3 类模板的继承关系
template <class Type1,class Type2>
class SubClass:public Base<Type2> {
public:
subshow(Type1 obj1,Type2 obj2)
{ cout << "Sub::Show," << obj1 << " "
<< obj2 << endl;
}
};
main()
{ SubClass <char *,double> co;
SubClass <int,char> io;
co.show(3.14);
co.subshow("Pi is,",3.1415);
io.show('X');
io.subshow(100,'P');
}