第六章 模 板
6,1 模板的概念
C++是强类型语言, 因此我们定义求最大值函数 max()时, 需要
对不同的数据类型分别定义不同的版本, 例如,
int max(int x,int y)
{ return (x>y)?x, y;}
float max(float x,float y)
{return (x>y)?x, y;}
double max(double x,double y)
{return (x>y)?x, y;}
这些函数版本执行的功能都是相同的, 只是参数类型和返回类型不
同, 能否为上述这些函数只写出一套代码? 解决问题的一个方
法就是使用模板 。 所谓模板, 就是写一个 函数模子, 用这个模子
套印出许多 功能相同, 参数类型和返回类型不同的 函数 。 模板是
实现了真正的代码可重用性 。
可以这么说,函数重载是指用 同一个名字 定义不同的函数, 这些
函数 功能不同, 执行不 同的操作 。
函数 模板 是指用 同一个名字 定义不同的函数, 这些函数 功能相同,
而参数类型和返回类型不同 。
1
模板
模板分为函数模板 ( 模子 ) 和类模板 ( 模子 ), 允许用户分别用
它们构造 ( 套印 ) 出 ( 模板 ) 函数和 ( 模板 ) 类 。
图显示了模板 ( 函数模板和类模板 ), 模板函数, 模板类和对象
之间的关系 。
模 板
(函数模板和类模板)
模板函

模板类
对象
实例化
实例化
实例化
模子
2
6.2 函数模板与模板函数
6.2.1 函数模板的声明与模板函数的生成
函数模板的声明格式如下,
template <class type>
返回类型 函数名 ( 参数表 )
{
函数体
}
其中 template是一个声明模板的关键字, 它表示声明一个模板 。
关键字 class表明后面的 type是模板形参, 在使用函数模板时,
必须将其实例化, 即用实际的数据类型替代它 。
例如, 将求最大值函数 max () 定义成函数模板, 如下所示,
template<class T>
T max(T x,T y)
{
return (x>y)? x:y;
}
3
例 6.1 函数模板的程序
# include<iostream.h>
# include<string.h>
template<class AT>
AT max(AT x,AT y )
{ return (x>y)? X, y; }
void main()
{ int il=10,i2=56;
float fl=12.5,f2=24.5;
double b1=50.344,d2=4656.346;
char c1=?k?,c2=?n?;
cout<<”the max of il,i2 is:,<<max(i1,i2)<<endl;
cout<<”the max of fl,f2 is:,<<max(f1,f2)<<endl;
cout<<”the max of dl,d2 is:,<<max(d1,d2)<<endl;
cout<<”the max of cl,c2 is:,<<max(c1,c2)<<endl;
}
程序运行结果如下,
the max of il,i2 is,56
the max of fl,f2 is,24.5
the max of dl,d2 is,4656.346
the max of cl,c2 is,n
4
函数模板和模板函数的关系
例 6.2 与指针有关的模板
#include<iostream.h>
template<class T>
T sum(T *array,int size=0)
{
T total=0;
for(int i=0; i<size; i++) total+=array[i];
return total;
};
函数模板
max(x,y)
模板函数
max(c1,c2)
(c1,c2为字符型
)
实例化
5
模板函数
max(f1,f2)
(f1,f2为浮点型
)
模板函数
max(d1,d2)
(d1,d2为双精型
)
模板函数
max(i1,i2)
(i1,i2为整型 )
实例化 实例化 实例化
接 1 例 6.2
int int_array[]={1,2,3,4,5,6,7,8,9,10};
double
double_array[]={1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9,10.10};
void main()
{
int itotal=sum(int_array,10);
double dtotal=sum(double_array,10);
cout<<”The summary of integer array are:” <<itotal<<endl;
cout<<”The summary of double array are:” <<dtotal<<endl;
}
程序运行结果为,
The summary of integer array are:55
The summary of double array are:59.6
几点说明,
? 在函数模板中允许使用多个类型参数 。 但在 template定义部分的
每个模板形参前必须有关键字 class。
6
几点说明,
例 6.3
#include<iostream.h>
template<class type1,class type2>
void myfunc(type1 x,type2 y)
{
cout<<x<<??<<y<<end1;
}
void main()
{ myfunc(10,”hao”);
myfunc(0.123,10L);
}
程序运行结果为,
10 hao
0.123 10
7
? 在 template语句与函数模板定义语句之间不允许有别的语句。
//这是不能编译的
Template <class T>
int iI; //错误,不允许有别的语句
T max(T x,T y)
{ return(x>y)? x, y;}
? 模板函数类似于重载函数,只不过它更严格一些而已。函数被重
载的时候,在每个函数体内可以执行不同的动作,但同一函数模板
实例化后的所有模板函数都必须执行相同的动作。
//不能做 函数模板的
void outdate(int i)
{ cout<<i; }
void outdata(double d)
{cout<<”d=”<<d<<endl;}
8
6.2.2 函数模板的异常处理
虽然函数模板中的模板形参 T可以实例化为各种类型, 但实例化 T的
各模板实参之间必须保持完全一致的类型, 否则会发生错误 。 请
看下面的例子 。
Template<class T>
T max(T x,T y)
{ return(x>y)?x:y; }
void fun(int i,char c)
{ max(i,i); //正确, 调用 max(int,int)
max(c,c); //正确, 调用 max(char,char)
max(i,c); //错误
max(c,i); //错误
}
这里出现错误的原因是, 如对语句 max(i,c);
编译器将先按变量 i将 T解释为 int类型, 此后出现的模板实参 c不能
解释为 int类型时, 便发生错误 。 解决这个问题有以下两种方法:
? 采用强制类型转换, 如将调用语句
max(i,c); 改写成 max(i,int(c));
9
? 用非模板函数重载函数模板,这种重载有两种表述方式,
① 只声明一个非模板函数的原型,而不给出函数体,它的函数体是
借用函数模板的函数体。当执行此重载版本时会自动调用函数模
板的函数体。
template<class T>
T max(T x,T y)
{ return(x>y)? x:y; }
int max (int,int); //非模板函数的原型
void fun(int i,char c)
{ max(i,i); //正确,调用 max(int,int)
max(c,c); //正确,调用 max(char,char)
max(i,c); //正确,调用 max(int,int),它支持数据间的隐式转换
max(c,i); //正确,调用 max(int,int),它支持数据间的隐式转换
}
② 定义一个完整的非模板函数,此方法定义的重载函数,所带参数
的类型可以随意,就像一般的重载函数一样定义。例如,
char *max( char *x,chen *y)
{ return(strcmp(x,y>0)?x:y;) }
10
当出现调用语句 max(“abcd”,”efgh”);时,执行的是这个重载的非
模板函数。
在 c++中函数模板与同名的非模板函数重载时,调用的顺序遵
循下述约定
?寻找一个参数完全匹配的函数,如果找到了就调用它。
?寻找一个函数模板,将其实例化,产生一个匹配的模板函数,若
找到了,就调用它。
?若?和?都失败,再试一试低一级的对函数的重载方法,若找到
了,就调用它。
11
6.3类模板和模板类
一个类模板 (也称为类属类或类生成类 )允许用户为类定义一种模
子, 使得 实例化 类中的某些数据成员, 某些成员函数的参数或者
返回值, 能取任意数据类型 。
定义一个类模板, 其格式如下,
template<class Type>
class 类名 {
//…
};
关键字 class表明后面的 Type是模板参数 。 在 实例化 类定义中,欲采
用通用数据类型的数据成员, 成员函数的参数或返回值, 前面需
要加上 Type。
例如, 下面的程序中建立了一个用来实现堆栈的类模板 。
12
const int size=10
Template<class Type>
class stack{
Type stck[size];
Int tos;
public,
void init() {tos=0;}
void push(Type ch);
Type pop();
};
成员函数 push() 和 pop() 在类定义体外定义为
template<class Type>
void stack<Type>::push(Type ob)
{ if(tos= =size)
{ cout<<”stack is full”;
return;
}
stck[tos]=ob;
tos++;
}
13
template<class Type>
Type stack<Type>::pop()
{ if(tos= =0)
{ cout<,”stack is empty”;
return 0;
}
tos--;
return stck[tos];
}
类模板不代表一个具体的, 实际的类,而代表一类类 。 实际上,类
模板的使用就是将类模板实例化成一个具体的类, 它的格式为,
类名 <实际的类型 >对象名 ;
例如,使用上面的类模板,创建两个模板参数为 char型的对象,语句
如下,
stack<char>s1,s2;
14
类模板
stack(Type)
模板类
stack(char) 模板类stack(int)
模板类
stack(double)
实例化 实例化 实例化
15
例 6.4 类模板 stack的例子,在此建立了字符型和整型两个堆栈。
#include<iostream.h>
const int size=10;
template<class Type> //声明一个类模板
class stack{ //定义类模板
Type stck[size]; //数组可取任意类型,即模板参数类型
Type
Int tos;
public,
void init() { tos=0; }
void push(Type ch); //参数取 Type类型
Type pop(); //返回类型取 Type类型
};
16
template<class Type>
void stack<Type>::push(Type ob)
{ if (tos= =size)
{
cout<<”stack is full”;
return;
}
stck[tos]=ob;
tos++;
}
template<class Type>
Type stack<Type>::pop()
{ if (tos= =0)
{
cout<<”stack is empty”;
return 0;
}
tos--;
return stck[tos];
}
17
接 1 例 6.4
void main()
{ //定义字符堆栈
stack<char>s1,s2;
int i;
s1.init();
s2.init();
s1.push(?a?);
s2.push(?x?);
s1.push(?b?);
s2.push(?y?);
s1.push(?c?);
s2.push(?z?);
for(i=0;i<3;i++)
cout<<”pops1:”<<s1.pop()<<endl;
for(i=0;i<3;i++)
cout<<”pops2:”<<s2.pop()<<endl;
18
//定义整型堆栈
stack<int>is1,is2; //创建两个模板参数为 int型的对象
is1.init();
is2.init();
is1.push(1);
is2.push(2);
is1.push(3);
is2.push(4);
is1.push(5);
is2.push(6);
for(i=0;i<3;i++)
cout<<”pop is1:”<<is1.pop()<<endl;
for(i=0;i<3;i++)
cout<<”pop is2:”<<is2.pop()<<endl;
}
程序运行结果如下,
pop s1:c pop s2:z pop is1,5 pop is2,6
pop s1:b pop s2:y pop is1,3 pop is2,4
pop s1:a pop s2:x pop is1,1 pop is2,2
19
例 6.5 建立一个单向链表类模板,然后建立一个保存字符的链表类。
#include <iostream.h>
template<class data_t>
class list{
data_t data;
list *next;
public,
list(data_t d);
void add(list *node)
{ node->next=this;
next=0;
}
list *getnext() {return next;}
data_t getdata() {return data;}
};
template<class data_t>list<data_t>::list(data_t d)
{
data=d;next=0;
}
20
void main()
{ list<char>start(?a?);
list<char>*p,*last;
int i;
//build a list
last=&start;
for(i=1;i<26;i++)
{ p=new list<char>(?a?+i);
p->add(last);
last=p;
}
cout<<endl;
//follow the list
p=&start;
while(p)
{ cout<<p->getdata();
p=p->getnext();
}
}
程序运行结果如下,
abcdefghijklmnopqrstuvwxyz
21
说明,
(1)在每个类模板定义之前,都需要在前面加上模板声明
template<class Type>
类模板在使用时, 必须在名字后面缀上模板参数 <Type>
stack<Type>
(2)模板类可以有多个模板参数, 在下面的例中建立了使用两个模板
参数的类模板 。
例 6.6 使用两个模板参数的类模板
#include<iostream.h>
template<class T1,class T2> //声明具有两个参数的模板
class myclass{ //定义类模板
T1 i;
T2 j;
public,
myclass(T1 a,T2 b) { i=a; j=b; }
void show() { cout<<”I=”<<i<<” j=”<<j<<endl; }
};
22
void main()
{ myclass <int,double>od1(12,0.15);
myclass <char,char *>ob2(?x?,”This is a test”);
od1.show();
ob2.show();
}
程序运行结果如下, I=12 j=0.15
I=x j=This is a test
23
6.4 应用举例 例 6.7 template<class T>
struct quenode {
T nodedata;
quenode *next;
};
这里用模板的形式定义了队列链表的一个结点的构造,struct也可
改为 class。
也用模板的形式定义了队列类,其中 4个数据成员,10个函数成员,
template <class T>
class queue {
protected,//保护段
① int quesize; //定义队列长度
② quenode<T> *head; //定义队列列头
③ quenode<T> *tail; //定义队列列尾
④ bool allocateerror;
24
T 类型的数据 quenode类型的指针
nodedata next
4个数据成员
quesize
bool类型 整数 指向列头的指针 指向列尾的指针
head tail allocateerror
① queue &copy (queue &q); //队列 拷贝函数
public,
② queue(); //构造函数
③ queue(queue &q) { head=NULL; tail=NULL; copy(q); }
//构造函数,建 q队列
④ ~queue() { clearque(); } //析构函数
⑤ bool getallocateerror() { return allocateerror; }
⑥ void push(T &); //将结点插入队尾
⑦ bool pop(T &); //从队头取结点
⑧ bool isempty() { return (quesize==0)? true,false ;}
//判断队列是否为空
⑨ void clearque(); //清空队列
⑩ queue &operator= (queue &q) { copy(q); return *this; }
//重载赋值运算符
};
其中,黑色的函数名 ③④⑤⑧⑩已在类内完成定义,红色 的函数
名 ①②⑥⑦⑨以 模板函数的形式 在下面定义,
25
② template<class T>
queue<T>::queue() //定义 构造函数
{ quesize=0;
allocateerror=false;
head=NULL;
tail=NULL;
}
① template<class T>
queue<T> &queue<T>::copy(queue<T> &que)
//将队列 que复制给当前队列对象
{ quenode<T> *p,*q,*r;
if (head) clearque();
quesize=que.quesize; //传递队列长度
allocateerror=false;
head=NULL;
tail=NULL;
26
if (!que.head) return *this; //若队列为空,则返回
head=new quenode<T>; //为队列头结点分配存储空间
if (!head) //若分配失败,则返回
{ allocateerror=true;
return *this;
}
head->nodedata=que.head->nodedata;
//将 que队列 头结点值赋 给当前队列 头结点
head->next=NULL;
tail=head; //将队列尾也指向此 结点
r=NULL;
p=head; //p 指针也指向此 结点
q=que.head->next; //q 指针指向 que队列的第二个 结点
27
while (q)
{ r=new quenode<T>; //为结点 r 分配存储 空间
if(! r )
{ allocateerror=true;
return *this;
}
r->nodedata=q->nodedata;
r->next=NULL;
p->next=r ; //将结点 r 链接到当前队列的链上
tail=r ; //队尾指针指向 r,因为 r为最后一个 结点
p=p->next; //指针后移
q=q->next;
}
return *this;
}
28
⑥ template<class T>
void queue<T>::push(T &x) //向队尾插入 结点
{ quenode<T> *p;
p=new quenode<T>; //为 p 结点分配存储 空间
if( !p ) //若分配失败,则返回
{ allocateerror=true;
return;
}
p->nodedata=x;
if (tail) //若队列非空
{ p->next=NULL;
tail->next=p; //将 p 结点链接到尾指针 tail后
tail =p; //修改队尾指针
}
else //若队列为空
{ p->next=NULL;
tail=p; //p结点为队列的头,又为尾
head=p;
}
quesize++; //长度加 1
}
29
⑦ template<class T>
bool queue<T>::pop(T &x) //从队头取一结点
{ quenode<T> *p;
if (head) //若队列非空
{ x=head->nodedata; //将队头的数据内容赋给 x
p=head;
head=head->next; //修改队头指针
if (head==NULL) //若队列已删空,则 tail也改为 NULL
tail=NULL;
delete p; //删除原头结点
quesize--; //长度减 1
return true;
}
return false;
}
30
⑨ template<class T>
void queue<T>::clearque() //将队列清空
{ T p;
allocateerror=false;
while (pop(p)) ; //循环提取队列中各结点,实现清除
head=tail=NULL;
}
31
定义一个具体的职工类,4个数据成员,2个函数成员
class staff{
public,
char name[80];
int age;
float salary;
char sex[8];
void assign(char *name,int age,float salary,char *sex)
{ strcpy(staff::name,name);
staff::age=age;
staff::salary=salary;
strcpy(staff::sex,sex);
}
void print()
{ printf(“%10s%6d%10.2f%8s\n”,name,age,salary,sex); }
};
32
定义一个外部函数,
void viewque(queue<staff> &que)
{ //显示实例化后的队列 que中的各结点
int i=1;
staff p;
queue<staff>quecopy(que);
clrscr();
while(quecopy.pop(p)) //循环提取队列中各结点
{ gotoxy(1,i+5);
printf(“%2d:”,i++);
p.print();
}
}
33
void main()
{ queue<staff> que; //定义队列类对象 que
staff p; //定义职工类对象 p
p.assign(“陈伟林”,47,1500,“男” ); //给 p对象赋值
que.push(p); //将 p 对象压入队列 que
p.assign(“王凌”,34,850.5,“男” );
que.push(p);
p.assign(“张大霖”,27,1200,“男” );
que.push(p);
p.assign(“方丽碧”,51,2000,“女” );
que.push(p);
viewque(que); //显示队列中的各结点
}
34