第 20 讲 模板
教学目的与要求:
了解模板的功能和作用 。
掌握函数模板和类模板的声明和使用 。
教学内容提要:
1,函数模板;
2,函数模板参数;
3,类模板;
教学重点,函数模板和类模板的声明和使用 。
教学难点,函数模板的申明和使用 。
教学进度,P222~ P238
教学过程:
所谓模板是一种使用无类型参数来产生一系列函数或类的机制,是 C++的一个重要特性。它的实现,方便了更大规模的软件开发。
模板是以一种完全通用的方法来设计函数或类而不必预先说明将被使用的每个对象的类型 。 通过模板可以产生类或函数的集合,使它们操作不同的数据类型,从而避免需要为每一种数据类型产生一个单独的类或函数 。
例如:设计一个求两参数最大值的函数,不使用模板时,需要定义四个函数:
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;}
若使用模板,则只定义一个函数:
Template<class type>
type max(type a,type b)
{return(a>b)?a,b;}
利用函数模板,可以建立一个具有通用功能的函数,支持不同的函数参数和返回值,达到减少代码书写量的目的。
【 19.1 函数模板 】
C++程序由类和函数组成,模板也分为类模板( class template)
和函数模板( function template)。
1,函数模板的一般说明形式如下:
template <模板形参表 >
<返回值类型 > <函数名 >( 模板函数形参表 )
{
//函数定义体
}
其中,<模板形参表 >可以包含基本数据类型,也可以包含类类型 。 类型形参需要加前缀 class。 如果类型形参多于一个,则每个类型形参都要使用 class。 <模板函数形参表 >中的参数必须是惟一的,而且在 <函数定义体 >中至少出现一次 。
函数模板只是说明,不能直接执行,需要实例化为模板函数后才能执行 。
当编译系统发现有一个函数调用:
<函数名 >( <实参表 >) ;
时,将根据 <实参表 >中的类型生成一个重载函数即模板函数 。 该模板函数的定义体与函数模板的函数定义体相同,而 <形参表 >的类型则以 <
实参表 >的实际类型为依据 。
2,使用函数模板
template <class T>
T abs(T value)
{
return value>0? value,-value;
}
例如重载函数,求绝对值的函数只要声明一个函数模板:
void main()
{
int nValue=-1,nResult;
double dblValue=-1.2,dblResult;
nResult=abs(nValue);
dblResult=abs(dblValue);
cout<<nValue<<″ ″<<nResult<<endl;
cout<<dblValue<<″ ″<<dblResult<<endl;
} -1 1
-1.2 1.2
第一次使用函数模板 abs()时,
实参为整数,由此可以推导出函数模板中的参数类型 T为整数,函数的返回值也是整数。 第二次调用 abs()时实参为双精度型,由此推导出函数模板中的参数类型 T为双精度型,函数的返回值为双精度型。
在主程序中可以这样使用函数模板:
例:编写一个对具有 n个元素的数组 a[ ]求最小值的程序,要求将求最小值的函数设计成函数模板。
#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;
}
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
template<class T>
void swap(T& a,T& b){
T t=a; a=b; b=t;
}
int fn(){
int ix=6,iy=7,ia=3,ib=5;
swap(ix,iy); //产生函数定义体
swap(ia,ib); //不产生函数定义体
//...
}
模板函数定义也是函数定义的一种,必须符合 C++函数的一次定义规则。例:
swap(ia,ib)的函数调用时,由于系统中已经存在 int型的
swap模板函数的定义,所以就不再生成 swap<int>的模板函数的定义了。
注
#include <iostream.h>
template<class T>
void swap(T& a,T& b)
{
T t=a; a=b; b=t;
}
void main()
{
int ix=6,iy=7,ia=303,ib=505;
double dx=3.5,dy=5.0;
char *s1=“good”,*s2=“better”;
cout<<“int ix=”<<ix<<“,iy=”<<iy<<endl;
cout<<“double dx=”<<dx<<“,dy=”<<dy<<endl;
cout<<“char * s1=”<<s1<<“,s2”<<s2<<endl;
swap(ix,iy);
swap(dx,dy);
swap(ia,dy);
swap(s1,s2);
cout<<“after swap:\n”;
cout<<“int ix=”<<ix<<“,iy=”<<iy<<endl;
cout<<“double dx=”<<dx<<“,dy=”<<dy<<endl;
cout<<“char * s1=”<<s1<<“,s2”<<s2<<endl;
}
模板函数调用是寻求函数模板的类型参数匹配,类型实参和类型形参的匹配规则与函数的数据实参类型与数据形参类型匹配的规则不同。类型实参和类型形参的匹配规则更为苛刻
template<class T>
void swap(T& a,T& b){
T t=a; a=b; b=t;
}
int add(double a,double b){
return a+b;
}
int fn(){
int ia=3;
double db=5.0;
char s1[]="good",s2[]="better";
int x = add(ia,db); // ok
swap(ia,db); // error
swap(s1,s2); // error
}
add(ia,db)函数的调用是普通的函数调用虽然
ia的类型和 double型不同,但通过数据的 int型隐式转换到 double型
swap(ia,db)函数调用,
由于 ia和 db的类型分别为 int和 double型,不能统一在同一个类型名上,
去模板类型参数没有隐式转换之说。
swap(s1,s2)函数调用,
由于 s1和 s2的类型为字符数组 char[5]和
char[7],是不同类型的
【 20.2 函数模板参数 】
数据形参分,
引用型参数 (提倡用本项 ):传址非引用型参数:传值引用型参数分,
引用型参数:数据形参的改变会波及到数据实参的改变常量引用型参数:在引用型参数前面加上 const,其数据形参的改变会不允许波及到数据实参的改变
1,函数的数据形参 ( Data Arguments )
2、常量引用型形参其数据形参可以是临时对象,所以,通过显式转换可以规定模板的产生形式.但是,不能被隐式转换的数据形参,其显式模板类型指定失效:
template<typename T>
T const& max(T const& a,T const& b){
return a < b? b,a;
}
int main(){
int ia=3;
double db=6.7;
db = max(<double>ia,db);
db = max(<double>&ia,db); //error
}
3、引用型形参其数据形参与数据实参捆绑,故数据实参应为左值表达式,
template<typename T>
void swap(T& a,T& b){
T t=a; a=b; b=t;
}
int main(){
int ia=3;
const int cb=5;
swap(ia,7); // error:7不是左值
swap<int>(ia,7); // error:哪怕类型指定也不济
swap(cb,7); // error:const int会引起操作错误
swap<const int>(ia,7); // error:同上
}
4、函数模板重载参数有不同的形式 (是否引用,是否常量,甚至是否特殊类型 ),不同形式的参数,
其行为不同,这是模板重载的前提.
例如:字串比较很特殊,指针比较与对象比较亦不同,则:
template<typename T>
T const& max(T const& a,T const& b){
return a < b? b,a;
}
template<typename T>
T* const& max(T* const& a,T* const& b){
return *a < *b? b,a;
}
const char* const& max(const char* const& a,const char*
const& b){
return strcmp(a,b) < 0? b,a;
}
int main(){
int ia=3,ib=7;
char *s1=”hello”,*s2=”hell”;
cout<<*max(&ia,&ib)<<”\n”; // match the second template
cout<<max(s1,s2)<<”\n”; // match the max function
cout<<max(ia,ib)<<”\n”; // match the first template
}
数据成员变成另外一种数据类型,或者干脆是一个类的对象,又要重新拷贝,重新进行修改。这样不仅程序的代码数量急剧增加,修改过程中也很容易出现各种疏漏。用一种类似函数模板的机制来结决问题 ——类模板 。
C++语言中的类模板的语法定义如下:
template <类型形参表 >
class <类名 >
{ //类说明体 };
template <类型形参表 >
<返回类型 > <类名 > <类型名表 >::<成员函数 >(形参表)
{ //成员函数定义体 }
当模板参数表中同时包含上述多项内容时,各项内容之间以逗号隔开。
注与函数模板相同,类模板只有使用的时候才被具体化为某一种类型。
使用模板类来产生对象时,按如下形式声明:
模板 <模板参数表 > 对象名 1,......对象名 n;
【 20.3 类模板 】
类模板举例
#include <iostream.h>
template <class T>
class Max //声明类模板 Max
{
private,
T item1,//类型为 T,T在该类的对象生成时具体化
item2,
item3;
public,
Max(){}
Max(T thefirst,T thesecond,T thethird);
T GetMaxItem(); //求得 3个元素中的最大值并按类型 T返回
void SetItem(T thefirst,T thesecond,T thethird); //设置类中的 3个元素的值
}
//类模板的实现
template <class T>
Max<T>::Max(T the first,T thesecond,T thethird):
item1(thefirst),item2(thesecond),item3(thethird)
{
}
例
template <class T>
void Max<T>::SetItem(T thefirst,T thesecond,T thethird)
{
item1=thefirst;
item2=thesecond;
item3=thethird;
}
template <class T>
T Max<T>::GetMaxItem()
{
T maxitem;
maxitem=item1>item2? item1,item2;
maxitem=maxitem>item3? maxitem,item3;
return maxitem;
}
void main() //主程序
{
Max<int> nmyMax(1,2,3);
Max<double> dblmyMax(1.2,1.3,-1.4);
cout<<nmyMax.GetMaxItem()<<endl;
cout<<dblmyMax.GetMaxItem()<<endl;
}
(续)
3
1.3
# include<iostream.h>
template<class T>
class Tclass
{ T x,y;
public,
Tclass(T a,T b){x=a;y=b;}
Tclass(T a){y=(T)0,x=a;}
void pr( )
{
char c;
c=(y>=(T)0?′+′:′ -′);
cout<<x<<c<<(y>(T)0? y:
-y)<<″i″<<endl;
}
};
void main( )
{
Tclass<double>a(10.5,-5.8);
a.pr( );
Tclass<int>b(10);
b.pr( );
}
10.5-5.8i
10+0i
练习:
小结:
1、模板的作用;
2、函数模板的声明和使用;
3、类模板的声明和使用;
作业:
P239 6.7,6.8,6.11,6.13
教学目的与要求:
了解模板的功能和作用 。
掌握函数模板和类模板的声明和使用 。
教学内容提要:
1,函数模板;
2,函数模板参数;
3,类模板;
教学重点,函数模板和类模板的声明和使用 。
教学难点,函数模板的申明和使用 。
教学进度,P222~ P238
教学过程:
所谓模板是一种使用无类型参数来产生一系列函数或类的机制,是 C++的一个重要特性。它的实现,方便了更大规模的软件开发。
模板是以一种完全通用的方法来设计函数或类而不必预先说明将被使用的每个对象的类型 。 通过模板可以产生类或函数的集合,使它们操作不同的数据类型,从而避免需要为每一种数据类型产生一个单独的类或函数 。
例如:设计一个求两参数最大值的函数,不使用模板时,需要定义四个函数:
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;}
若使用模板,则只定义一个函数:
Template<class type>
type max(type a,type b)
{return(a>b)?a,b;}
利用函数模板,可以建立一个具有通用功能的函数,支持不同的函数参数和返回值,达到减少代码书写量的目的。
【 19.1 函数模板 】
C++程序由类和函数组成,模板也分为类模板( class template)
和函数模板( function template)。
1,函数模板的一般说明形式如下:
template <模板形参表 >
<返回值类型 > <函数名 >( 模板函数形参表 )
{
//函数定义体
}
其中,<模板形参表 >可以包含基本数据类型,也可以包含类类型 。 类型形参需要加前缀 class。 如果类型形参多于一个,则每个类型形参都要使用 class。 <模板函数形参表 >中的参数必须是惟一的,而且在 <函数定义体 >中至少出现一次 。
函数模板只是说明,不能直接执行,需要实例化为模板函数后才能执行 。
当编译系统发现有一个函数调用:
<函数名 >( <实参表 >) ;
时,将根据 <实参表 >中的类型生成一个重载函数即模板函数 。 该模板函数的定义体与函数模板的函数定义体相同,而 <形参表 >的类型则以 <
实参表 >的实际类型为依据 。
2,使用函数模板
template <class T>
T abs(T value)
{
return value>0? value,-value;
}
例如重载函数,求绝对值的函数只要声明一个函数模板:
void main()
{
int nValue=-1,nResult;
double dblValue=-1.2,dblResult;
nResult=abs(nValue);
dblResult=abs(dblValue);
cout<<nValue<<″ ″<<nResult<<endl;
cout<<dblValue<<″ ″<<dblResult<<endl;
} -1 1
-1.2 1.2
第一次使用函数模板 abs()时,
实参为整数,由此可以推导出函数模板中的参数类型 T为整数,函数的返回值也是整数。 第二次调用 abs()时实参为双精度型,由此推导出函数模板中的参数类型 T为双精度型,函数的返回值为双精度型。
在主程序中可以这样使用函数模板:
例:编写一个对具有 n个元素的数组 a[ ]求最小值的程序,要求将求最小值的函数设计成函数模板。
#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;
}
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
template<class T>
void swap(T& a,T& b){
T t=a; a=b; b=t;
}
int fn(){
int ix=6,iy=7,ia=3,ib=5;
swap(ix,iy); //产生函数定义体
swap(ia,ib); //不产生函数定义体
//...
}
模板函数定义也是函数定义的一种,必须符合 C++函数的一次定义规则。例:
swap(ia,ib)的函数调用时,由于系统中已经存在 int型的
swap模板函数的定义,所以就不再生成 swap<int>的模板函数的定义了。
注
#include <iostream.h>
template<class T>
void swap(T& a,T& b)
{
T t=a; a=b; b=t;
}
void main()
{
int ix=6,iy=7,ia=303,ib=505;
double dx=3.5,dy=5.0;
char *s1=“good”,*s2=“better”;
cout<<“int ix=”<<ix<<“,iy=”<<iy<<endl;
cout<<“double dx=”<<dx<<“,dy=”<<dy<<endl;
cout<<“char * s1=”<<s1<<“,s2”<<s2<<endl;
swap(ix,iy);
swap(dx,dy);
swap(ia,dy);
swap(s1,s2);
cout<<“after swap:\n”;
cout<<“int ix=”<<ix<<“,iy=”<<iy<<endl;
cout<<“double dx=”<<dx<<“,dy=”<<dy<<endl;
cout<<“char * s1=”<<s1<<“,s2”<<s2<<endl;
}
模板函数调用是寻求函数模板的类型参数匹配,类型实参和类型形参的匹配规则与函数的数据实参类型与数据形参类型匹配的规则不同。类型实参和类型形参的匹配规则更为苛刻
template<class T>
void swap(T& a,T& b){
T t=a; a=b; b=t;
}
int add(double a,double b){
return a+b;
}
int fn(){
int ia=3;
double db=5.0;
char s1[]="good",s2[]="better";
int x = add(ia,db); // ok
swap(ia,db); // error
swap(s1,s2); // error
}
add(ia,db)函数的调用是普通的函数调用虽然
ia的类型和 double型不同,但通过数据的 int型隐式转换到 double型
swap(ia,db)函数调用,
由于 ia和 db的类型分别为 int和 double型,不能统一在同一个类型名上,
去模板类型参数没有隐式转换之说。
swap(s1,s2)函数调用,
由于 s1和 s2的类型为字符数组 char[5]和
char[7],是不同类型的
【 20.2 函数模板参数 】
数据形参分,
引用型参数 (提倡用本项 ):传址非引用型参数:传值引用型参数分,
引用型参数:数据形参的改变会波及到数据实参的改变常量引用型参数:在引用型参数前面加上 const,其数据形参的改变会不允许波及到数据实参的改变
1,函数的数据形参 ( Data Arguments )
2、常量引用型形参其数据形参可以是临时对象,所以,通过显式转换可以规定模板的产生形式.但是,不能被隐式转换的数据形参,其显式模板类型指定失效:
template<typename T>
T const& max(T const& a,T const& b){
return a < b? b,a;
}
int main(){
int ia=3;
double db=6.7;
db = max(<double>ia,db);
db = max(<double>&ia,db); //error
}
3、引用型形参其数据形参与数据实参捆绑,故数据实参应为左值表达式,
template<typename T>
void swap(T& a,T& b){
T t=a; a=b; b=t;
}
int main(){
int ia=3;
const int cb=5;
swap(ia,7); // error:7不是左值
swap<int>(ia,7); // error:哪怕类型指定也不济
swap(cb,7); // error:const int会引起操作错误
swap<const int>(ia,7); // error:同上
}
4、函数模板重载参数有不同的形式 (是否引用,是否常量,甚至是否特殊类型 ),不同形式的参数,
其行为不同,这是模板重载的前提.
例如:字串比较很特殊,指针比较与对象比较亦不同,则:
template<typename T>
T const& max(T const& a,T const& b){
return a < b? b,a;
}
template<typename T>
T* const& max(T* const& a,T* const& b){
return *a < *b? b,a;
}
const char* const& max(const char* const& a,const char*
const& b){
return strcmp(a,b) < 0? b,a;
}
int main(){
int ia=3,ib=7;
char *s1=”hello”,*s2=”hell”;
cout<<*max(&ia,&ib)<<”\n”; // match the second template
cout<<max(s1,s2)<<”\n”; // match the max function
cout<<max(ia,ib)<<”\n”; // match the first template
}
数据成员变成另外一种数据类型,或者干脆是一个类的对象,又要重新拷贝,重新进行修改。这样不仅程序的代码数量急剧增加,修改过程中也很容易出现各种疏漏。用一种类似函数模板的机制来结决问题 ——类模板 。
C++语言中的类模板的语法定义如下:
template <类型形参表 >
class <类名 >
{ //类说明体 };
template <类型形参表 >
<返回类型 > <类名 > <类型名表 >::<成员函数 >(形参表)
{ //成员函数定义体 }
当模板参数表中同时包含上述多项内容时,各项内容之间以逗号隔开。
注与函数模板相同,类模板只有使用的时候才被具体化为某一种类型。
使用模板类来产生对象时,按如下形式声明:
模板 <模板参数表 > 对象名 1,......对象名 n;
【 20.3 类模板 】
类模板举例
#include <iostream.h>
template <class T>
class Max //声明类模板 Max
{
private,
T item1,//类型为 T,T在该类的对象生成时具体化
item2,
item3;
public,
Max(){}
Max(T thefirst,T thesecond,T thethird);
T GetMaxItem(); //求得 3个元素中的最大值并按类型 T返回
void SetItem(T thefirst,T thesecond,T thethird); //设置类中的 3个元素的值
}
//类模板的实现
template <class T>
Max<T>::Max(T the first,T thesecond,T thethird):
item1(thefirst),item2(thesecond),item3(thethird)
{
}
例
template <class T>
void Max<T>::SetItem(T thefirst,T thesecond,T thethird)
{
item1=thefirst;
item2=thesecond;
item3=thethird;
}
template <class T>
T Max<T>::GetMaxItem()
{
T maxitem;
maxitem=item1>item2? item1,item2;
maxitem=maxitem>item3? maxitem,item3;
return maxitem;
}
void main() //主程序
{
Max<int> nmyMax(1,2,3);
Max<double> dblmyMax(1.2,1.3,-1.4);
cout<<nmyMax.GetMaxItem()<<endl;
cout<<dblmyMax.GetMaxItem()<<endl;
}
(续)
3
1.3
# include<iostream.h>
template<class T>
class Tclass
{ T x,y;
public,
Tclass(T a,T b){x=a;y=b;}
Tclass(T a){y=(T)0,x=a;}
void pr( )
{
char c;
c=(y>=(T)0?′+′:′ -′);
cout<<x<<c<<(y>(T)0? y:
-y)<<″i″<<endl;
}
};
void main( )
{
Tclass<double>a(10.5,-5.8);
a.pr( );
Tclass<int>b(10);
b.pr( );
}
10.5-5.8i
10+0i
练习:
小结:
1、模板的作用;
2、函数模板的声明和使用;
3、类模板的声明和使用;
作业:
P239 6.7,6.8,6.11,6.13