下面 swapValues
只支持 int
类型:
void swapValues(int& var1, int& var2)
{
int temp;
temp = var1;
var1 = var2;
var2 = temp;
}
如果我们想将 swapValues
函数用于 char
变量,可添加以下定义来重载函数名:
void swapValues(char& var1, char& var2)
{
char temp;
temp = var1;
var1 = var2;
var2 = temp;
}
但这样做明显效率很低,令人不满意,如果要将 swapValues
函数应用于更多类型,必须一次又一次地重复几乎完全一样的函数定义。这会使得代码充斥着大量几乎完全一样的定义。我们认为,以下函数定义适合任何类型的变量。
// 交换var1和var2的值
template<class T>
void swapValues(T& var1, T& var2)
{
T temp;
temp = var1;
var1 = var2;
var2 = temp;
}
其中 template
这通常称为 模板前缀,它告诉编译器,后续的定义或函数声明是 模板,T
是 类型参数。
下面是这个实例的全部演示代码:
// 该程序用于演示一个函数模板
#include
using namespace std;
// 交换var1和var2的值
template<class T>
void swapValues(T& var1, T& var2)
{
T temp;
temp = var1;
var1 = var2;
var2 = temp;
}
int main()
{
int integer1 = 1, integer2 = 2;
cout<<integer1<<" "<<integer2<<endl; // 1 2
swapValues(integer1, integer2);
cout<<integer1<<" "<<integer2<<endl; // 2 1
char symbol1 = 'A', symbol2 = 'B';
cout<<symbol1<<" "<<symbol2<<endl; // A B
swapValues(symbol1, symbol2);
cout<<symbol1<<" "<<symbol2<<endl; // B A
return 0;
}
编译器不会真的为函数名称 swapValues
生成针对所有可能类型的定义,只是表现得像是生成了所有函数定义。针对用到的每种类型,编译器都生成单独的函数定义,但不会为没用到的任何类型生成定义。另外,无论为一种类型使用多少次模板,都只为那种类型生成一个定义。
此外,函数模板也可能有多个类型参数。例如,具有两个类型参数(T1
和 T2
)的一个函数模板可以像下面这样开头:
template<class T1, class T2>
但大多数函数模板都只需一个类型参数。不能有未使用的模板参数。换言之,指定的每个模板参数都必须在模板函数中用到。
算法抽象是指用一个更常规(泛化)的方式表示算法,忽略完全一致的细节,将重点放在算法本质上。C++设计了许多功能来支持算法抽象,函数模板是其中之一。
下面使用泛型排序函数:
// 该程序用于演示一个函数模板
#include
using namespace std;
// 交换var1和var2的值
template<class T>
void swapValues(T& var1, T& var2)
{
T temp;
temp = var1;
var1 = var2;
var2 = temp;
}
template<class BaseType>
int indexOfSmallest(const BaseType a[], int startIndex, int numberUsed)
{
BaseType min = a[startIndex];
int indexOfMin = startIndex;
for (int index = startIndex + 1; index < numberUsed; index++)
if (a[index] < min)
{
min = a[index];
indexOfMin = index;
// min是[startIndex]到a[index]之间最小的元素
}
return indexOfMin;
}
template<class BaseType>
void sort(BaseType a[], int numberUsed)
{
int indexOfNextSmallest;
for(int index=0; index<numberUsed-1; index++)
{
// 将之前的值防在a[index]中
indexOfNextSmallest = indexOfSmallest(a, index, numberUsed);
swapValues(a[index], a[indexOfNextSmallest]);
}
}
int main()
{
int a[10] = {9,8,7,6,5,1,2,3,0,4};
for(int i=0; i<10; ++i)
cout << a[i] << " ";
cout<<endl;
sort(a, 10);
for(int i=0; i<10; ++i)
cout << a[i] << " ";
cout<<endl;
double b[5] = {5.5, 4.4, 1.1, 3.3, 2.2};
for(int i=0; i<5; ++i)
cout << b[i] << " ";
cout<<endl;
sort(b, 5);
for(int i=0; i<5; ++i)
cout << b[i] << " ";
cout<<endl;
char c[7] = {'G', 'E', 'N', 'E', 'R', 'I', 'C'};
for(int i=0; i<7; ++i)
cout<<c[i]<<" ";
cout<<endl;
sort(c, 7);
for(int i=0; i<7; ++i)
cout<<c[i]<<" ";
cout<<endl;
return 0;
}
如果要使用这个泛型,那么这种类型得可以用 <
比较。比如下面就是未不恰当的类型使用模板:
int a[10], b[10];
<用于填充数组的一些代码>
swapValues(a, b);
上述代码不能工作,因为数组类型不支持赋值。
类模板的语法和函数模板基本一样。下面是放在模板定义之前的语句:
template<class T>
类型参数 T
在类定义中的用法与其他任何类型无异。和函数模板一样,类型参数 T
可以是任何类型;类型参数不一定要替换成类类型。和函数模板一样,可用任何(非关键字)标识符代表 T
。
以下面的类模板为例。该类的对象包含一对 T
类型的值。 如果 T
是 int
,则对象值是一对整数;如果 T
是 char
,则对象值是一对字符,依此类推。
// 该类表示一对T类型的值:
template<class T>
class Pair
{
public:
Pair();
Pair(T firstValue, T secondValue);
void setElement(int position, T value);
// 前条件:position是1或2
// 后条件:指定的 position 被设置成 value
T getElement(int position) const;
// 前条件:position 是 1 或 2
// 返回在指定 position 的值
private:
T first;
T second;
};
定义好类模板之后就可声明该类的对象。声明必须指定要为 T
填充什么类型。例如,以下语句声明 score
对象来记录一对整数。另外还声明 seats
对象来记录一对字符:
Pair<int> score;
Pair<char> seats;
然后可以像使用其他任何对象那样使用上述两个对象。例如,以下语句将第一个球队的 score
设置为3,将第二个球队的 score
设置成0:
score.setElement(1, 3);
score.setElement(2, 0);
类模板的成员函数采用与普通类的成员函数一样的方式来定义。唯一的区别是,成员函数定义本身也是模板。例如,以下代码给出了成员函数 setElement
的一个恰当的定义,共给出了接收两个参数的构造函数的定义:
// 使用iostream和cstdlib
template<class T>
void Pair<T>::setElement(int position, T value)
{
if (positon == 1)
first = value;
else if (position == 2)
second = value;
else
{
cout << "Error: Illegal pair position.\n";
exit(1);
}
}
template<class T>
Pair<T>::Pair(T firstValue, T secondValue)
: first(firstValue), second(secondValue)
{
// 主体有意留空
}
注意,作用域解析操作符之前的类名是 Pair
,而非单单是 Pair
。
类模板名称可用作函数参数类型。例如,以下语句声明一个函数,参数是一对整数:
int addUp(const Pair<int>& thePair);
// 返回thePair中的两个整数的和
注意,其中指定了要填充的类型(本例是 int
),它将取代类型参数 T
。
甚至可以在函数模板中使用类模板。例如,可以不像上面那样定义具体的 addUp
函数,而是定义函数模板,使函数能支持所有数值类型:
template<class T>
T addUp(const Pair<T>& thePair);
// 前条件已经为T类型的值定义了操作符+
// 返回thePair中的两个值之和