目录
对C++的模板初步认识
1 C++ 标准模板能干什么
2 模板的分类
2.1 类模板
2.2 函数模板
2.2.1 举例
2.2.2 函数模板的定义与使
对C++ 的模板初步认识
1 C++ 标准模板能干什么
在编程过程中,有时候我们会遇到类似这样的问题:某个算法它是跟数据类型无关的,但是在编程的时候,为了适应各种不同的数据类型,往往要写多个版本或多个重载函数来解决,另外又有一种情况,某个数据的逻辑组织方式,本身是跟类型无关的,但是同样不同的对象要写不同的版本。于是我们就产生这样的梦想:拥有一个标准的组件仓库,其中的组件都有着良好的、易于理解的标准接口。如果能够充分利用聪明人的智慧和经验,而不再自己去做大量重复工作,那时的阳光该是多么灿烂!
这一天终于来到了。C++ 能帮我们美梦成真。
2 模板的分类
2.1 类模板
有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:
示例代码1:
class Compare_int
{
public:
Compare_int(inta,int b)
{
x=a;
y=b;
}
int max( )
{
return(x>y)?x:y;
}
int min( )
{
return(x<y)?x:y;
}
private:
int x,y;
};
其作用是对两个整数作比较,可以通过调用成员函数max和min得到两个整数中的大者和小者。如果想对两个浮点数(float型)作比较,需要另外声明一个类:
示例代码2:
class Compare_float
{
public:
Compare_float(floata,float b)
{
x=a;
y=b;
}
float max( )
{
return(x>y)?x:y;
}
float min( )
{
return(x<y)?x:y;
}
private:
float x,y;
};
如上两个类,唯一不同的是数据类型不同,组织结构完全相同。类似于这样的问题在我们的编程中经常遇到,有人可能想到C++不是有函数重载么,利用函数重载可以避免重写两个类,但重复的代码量也是很多的。在C++中还有一种解决方式就是利用模板,利用模板类写出一个如下的类:
template<class numtype> //声明一个模板,虚拟类型名为numtype
class Compare //类模板名为Compare
{
public:
Compare( numtypea, numtype b)
{
x=a;
y=b;
}
numtype max()
{
return (x>y)?x:y;
}
numtype min()
{
return (x<y)?x:y;
}
private:
numtype x,y;
};
从上述代码可以看出:
(1) 声明类模板时要增加一行:
template <class 类型参数名>
(2) 原有的类型名int全都换成了虚拟类型参数名numtype。当建立类对象时,如将实际类型指定为int型,编译器就会用int取代所有的numtype,如果指定为float型,就用float取代所有的numtype。这样就实现一类多用。
由于类模板包含类型参数,因此又称为参数化的类。如果说类是对象的抽象,对象是类的实例,则类模板是类的抽象,类是类模板的实例。利用类模板可以建立含各种数据类型的类。
那么在声明一个类模板后,怎样使用它?怎样使它变成一个实际的类呢? 现在回顾一下类定义对象的方法:
Compare_int cmp1(4,7);//Compare_int是已声明的类
用类模板定义对象的方法与此相似,但是不能直接写成:
Compare cmp(4,7); // Compare是类模板名
具体的做法是:
Compare <int> cmp(4,7);
即在类模板名之后在尖括号内指定实际的类型名,在进行编译时,编译系统就用int取代类模板中的类型参数numtype,这样就把类模板具体化了,或者说实例化了。这时Compare<int>就相当于前面介绍的Compare_int类。
Compare是类模板名,而不是一个具体的类,类模板体中的类型numtype并不是一个实际的类型,只是一个虚拟的类型,无法用它去定义对象。必须用实际类型名去取代虚拟的类型。
还有一个问题要说明:上面列出的类模板中的成员函数是在类模板内定义的。如果改为在类模板外定义,不能用一般定义类成员函数的形式:
numtype Compare::max( )
{
return (x>y)?x:y;
}
而应当写成类模板的形式:
template<class numtype>
numtype Compare<numtype>::max( )
{
return (x>y)?x:y;}
}
归纳以上的介绍,可以这样声明和使用类模板:
(1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
(2) 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
(3) 在类声明前面加入一行,格式为
template<class 虚拟类型参数>
如:
template<class numtype> //注意本行末尾无分号
class Compare
{
…//类体
};
(4) 用类模板定义对象时用以下形式:
类模板名<实际类型名> 对象名;
类模板名<实际类型名> 对象名(实参表列);
如:
Compare<int>cmp;
Compare<int>cmp(3,7);
(5) 如果在类模板外定义成员函数,应写成类模板形式:
template<class 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>∷成员函数名(函数形参表列)
{
…
}
说明:
(1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:
template<classT1,class T2>
class someclass
{…};
在定义对象时分别代入实际的类型名,如:
someclass<int,double>obj;
(2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
(3) 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。
2.2 函数模板
2.2.1 举例
我们先来以一个求一个数的绝对值的例子开始。通常情况下一个数有各种不同的类型,所以在C++中可以使用函数重载的方法如下:
重载方法一:
#include<iostream.h>
#include<math.h>
int f(int i){return i>0? i: -i; } //函数重载
long int f(long inti){ return i>0? i: -i; } //函数重载
double f(float i){ return i>0? i: -i; } //函数重载
void main()
{
int a; long int b; float c; //声明变量
cin>>a>>b>>c;
cout<<"整型:"<<f(a)<<" 长整型:"<<f(b)<<" 浮点型:"<<f(c)<<endl;
}
重载方法二:
#include<iostream.h>
#include<math.h>
class ABS //求绝对值
{
public:
int f(int i){ return abs(i); } //函数重载
long int f(long int i){ return labs(i);} //函数重载
double f(float i){ return fabs(i); } //函数重载
};
void main()
{
ABS n; //声明对象
int a; long int b; float c; //声明变量
cin>>a>>b>>c;
cout<<"整型:"<<n.f(a)<<" 长整型:"<<n.f(b)<<" 浮点型:"<<n.f(c);
}
如下我们使用函数模板来完成求一个数的绝对值
方法三:
template <typenameT> T abs(T a)
{
returna>0?a:-a;
}
int main()
{
int x=-1;
inty=-0.99;
cout<<"abs(x)="<<abs(x)<<endl<<"abs(y)="<<abs(y)<<endl;
Sleep(5000);
return 0;
}
结果为:
第一次使用函数模板abs()时,实参为整数,由此可以推导出函数模板中的参数类型T为整型,函数的返回值也是整型。
第二次调用abs()时实参为长整型,由此推导出函数模板中的参数类型T为长整型,函数的返回值为长整型。
2.2.2 函数模板的定义与使用
利用函数模板,可以建立一个具有通用功能的函数,支持不同的函数参数和返回值,达到减少代码书写量的目的。
函数模板的定义形式如下:
template < class(typename) T >
返回值类型 函数名(参数表)
{ 函数体 }
其中:
Template,class,typename为关键字;
class或typename与T一起说明用户定义的数据类型,当函数不以类而存在时,使用关键字typename, 当函数要在某个类中声明时使用class;当然C++并没有规定必须得这样,而两个关键字都可以,这样做为了区分方便。
类型形参T代表在函数模板中要使用的通用类型,在该函数的调用过程中,T被实参的类型具体化。
函数模板实际上是定义了一类函数,对有些参数或数据成员的数据类型并不具体指定,而是作为模板的参数。等到使用模板时再根据实参的数据类型来确定模板参数的类型,得到模板的实例,称为模板的实例化。
例1:
template <typename T>
T abs(T value)
{
returnvalue>0? value: -value;
}
若使用模板时:
abs(5); //将模板参数的类型实例化为整型
abs(-3.8); //将模板参数的类型实例化为实型
函数模板实例化后生成的函数称为模板函数。在程序中可用不同类型的实参来调用同一个函数,减少了代码的书写。
例2 编写选择排序的函数模板并实现排序
#include "stdafx.h"
#include <iostream>
#include <windows.h>
using namespace std;
//函数模板(两数交换)
template<typenameT> void Swap(T &x,T &y)
{
T temp=x; x=y; y=temp;
}
//函数模板(选择排序)
template<typenameT> voidSort(T *v, int n) //参数中有参数化类型的参数,也有普通类型的参数
{
for(int i=0;i<n;i++)
{
for(int j=i;j<n;j++)
{
if(v[i]>v[j])
{
Swap(v[j],v[j+1]); //调用函数模板实例化后生成的模板函数
}
}
}
}
//print函数模板(显示输出)
template<typenameT> voidPrint(T *v,int n)
{
for(int i=0;i<n;i++)
cout<<""<<v[i];
cout<<endl;
}
void main()
{
shorta[10]={22,11,44,66,88,99,0,33,55,77};
cout<<"排序前:";
Print(a,10);
Sort(a,10); //对整型数据排序
cout<<"排序后:";
Print(a,10);
char s[]="zcpkvrehaq";
cout<<"排序前:";
Print(s,10);
Sort(s,10); //对字符型数据排序
cout<<"排序后:";
Print(s,10);
Sleep(5000);
}
运行结果为: