目次
C++ のテンプレートについての予備的な理解
1 C++ 標準テンプレートでできること
2 テンプレートの分類
2.1 クラステンプレート
2.2 関数テンプレート
2.2.1 例
2.2.2 関数テンプレートの定義と使用方法
C++のテンプレートについての予備的な理解
1 C++標準テンプレートでは何ができるのですか?
プログラミングの過程で、「特定のアルゴリズムはデータ型と無関係であるが、プログラミングの際、さまざまなデータ型に適応させるために、それを解決するために複数のバージョンや複数のオーバーロードされた関数を作成する必要があることがよくあります。また、特定のデータ自体の論理構成は型に依存しないが、同じ異なるオブジェクトでも異なるバージョンを作成する必要がある」といった問題に遭遇することがあります。ですから、私たちはそのような夢を持っています。それは、標準のコンポーネント ウェアハウスを持ち、そこにあるすべてのコンポーネントが優れた、わかりやすい標準インターフェイスを持つことです。自分で繰り返しの作業をするのではなく、賢い人の知恵と経験をフルに活用することができれば、そのときの太陽はどれほど明るいことでしょう。
ついにその日がやって来ました。C++ は私たちの夢の実現に役立ちます。
2 テンプレートの分類
2.1クラステンプレート
場合によっては、次のステートメントがクラスを宣言しているように、機能が同じでデータ型だけが異なるクラスが 2 つ以上存在することがあります。
サンプルコード 1:
クラス Compare_int
{
公共:
Compare_int(inta,int b)
{
x=a;
y=b;
}
int max( )
{
return(x>y)?x:y;
}
int min( )
{
return(x<y)?x:y;
}
プライベート:
int x,y;
};
その機能は 2 つの整数を比較することであり、メンバー関数 max と min を呼び出すことで 2 つの整数のうち大きい方と小さい方を取得できます。2 つの浮動小数点数 (float 型) を比較する場合は、別のクラスを宣言する必要があります。
サンプルコード 2:
クラス Compare_float
{
公共:
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);
}
运行结果为: