模板(Templates)是ANSI-C++ 标准中新引入的概念。
函数模板( Function templates)
模板(Templates)可以生成通用的函数,这些函数能够接受任意数据类型的参数,可返回任意类型的值,而不需要对所有可能的数据类型进行函数重载。
这在一定程度上实现了宏(macro)的作用。它们的原型定义是下面两种中的任何一个:
template <class identifier> function_declaration;
template <typename identifier> function_declaration;
上面两种原型定义的不同之处在关键字class 或 typename的使用。它们实际是完全等价的,因为两种表达的意思和执行都一模一样。
例如,要生成一个模板,返回两个对象中较大的一个,我们可以这样写:
template <class GenericType>
/*生成了一个通用数据类型的模板,叫做GenericType, 成为一个有效的数据类型*/
GenericType GetMax (GenericType a, GenericType b) { return (a>b?a:b); }
/*GenericType 没有代表任何具体的数据类型;当函数 GetMax 被调用的时候,可以使用任何有效的数据类型来调用它*/
例如,要调用GetMax 来比较两个int类型的整数可以这样写:
int x,y;
GetMax <int> (x,y); //调用方式:“function <type> (parameters); ”
见下例:
#include <iostream>
using namespace std;
/*(在这个例子中,将通用数据类型命名为T,因为T短一些,并且它是模板更为通用的标示之一,虽然使用任何有效的标示符都是可以的。)*/
template <class T> T GetMax(T a, T b)
{
//类型T可以用来声明新的对象
return (a > b ? a : b);
}
int main()
{
int i = 5, j = 6, k;
long l = 10, m = 11, n;
/*对同样的函数GetMax()使用了两种参数类型:int 和 long,而只写了一种函数的实现,也就是说一个函数的模板,用了两种不同的pattern来调用它*/
k = GetMax(i, j);
n = GetMax(l, m);
cout << k << endl;
cout << n << endl;
return 0;
}
在这个具体的例子中,通用类型 T 被用作函数GetMax 的参数,不需要说明或 ,编译器也可以自动检测到传入的数据类型,因此也可以这样写这个例子:
int i,j;
GetMax (i,j); /*因为i 和j 都是int 类型,编译器会自动假设我们想要函数按照int进行调用。*/
因为模板函数只包括一种数据类型 (class T), 而且它的两个参数都是同一种类型,不能够用两个不同类型的参数来调用它:
也可以使得模板函数接受两种或两种以上类型的数据,例如:
template <class T>
T GetMin (T a, U b) { return (a<b?a:b); }
//这里的U类型应该是已经存在的。
在这个例子中,我们的模板函数 GetMin() 接受两个不同类型的参数,并返回一个与第一个参数同类型的对象。在这种定义下,我们可以这样调用该函数:
int i,j;
long l;
i = GetMin <int, long> (j,l);
或者,简单的用 i = GetMin (j,l);
类型要匹配
类模板(Class templates)
也可以定义类模板(class templates),使得一个类可以有基于通用类型的成员,而不需要在类生成的时候定义具体的数据类型。
template <class T> class pair {
T values [2];
public:
pair (T first, T second) {
values[0]=first;
values[1]=second;
}
};
上面定义的类可以用来存储两个任意类型的元素。例如,想要定义该类的一个对象,用来存储两个整型数据115 和 36 ,可以这样写:
pair<int> myobject (115, 36);
以及
pair<float> myfloats (3.0, 2.18);
在上面的例子中,类的唯一一个成员函数已经被inline 定义。如果我们要在类之外定义它的一个成员函数,我们必须在每一函数前面加template <… >。
// class templates
#include <iostream.h>
template <class T> class pair {
T value1, value2;
public:
pair (T first, T second) {
value1=first;
value2=second;
}
T getmax ();
};
template <class T> T pair::getmax (){
/*注意写法,所有写 T 的地方都是必需的,这里第二个T表示函数返回值的类型,这个根据需要可能会有变化*/
T retval;
retval = value1>value2? value1 : value2;
return retval;
}
int main () {
pair myobject (100, 75);
cout << myobject.getmax();
return 0;
}
模板特殊化(Template specialization)
模板的特殊化是当模板中的pattern有确定的类型时,模板有一个具体的实现。
// Template specialization
#include <iostream.h>
template <class T> class pair {//模板类
T value1, value2;
public:
pair (T first, T second){
value1=first;
value2=second;
}
T module () {return 0;}
};
/*第一对尖括号<>内必须为空。在类名称后面,我们必须将这个特殊化中使用的具体数据类型写在尖括号<>中。*/
template <> class pair <int> {
//模板类特殊化--格式
/***重新定义类的所有成员的特殊化实现***/
int value1, value2;
public:
pair (int first, int second){
value1=first;
value2=second;
}
int module ();
};
template <> int pair<int>::module() {
//模板特殊化后,内部函数外部定义
return value1%value2;
}
int main () {
pair <int> myints (100,75);
pair <float> myfloats (100.0,75.0);
cout << myints.module() << '\n';
cout << myfloats.module() << '\n';
return 0;
}
特殊化模板的一个数据类型的时候,同时还必须重新定义类的所有成员的特殊化实现(如果你仔细看上面的例子,会发现不得不在特殊化的定义中包含它自己的构造函数 constructor,虽然它与通用模板中的构造函数是一样的)。这样做的原因就是 特殊化不会继承通用模板的任何一个成员。
模板的参数值(Parameter values for templates)
除了模板参数前面跟关键字class 或 typename 表示一个通用类型外,函数模板和类模板还可以包含其它不是代表一个类型的参数,例如代表一个常数,这些通常是基本数据类型的。
// array template
#include <iostream.h>
template <class T, int N> class array {
T memblock [N];
public:
void setmember (int x, T value);
T getmember (int x);
};
template <class T, int N>
void array<T,N>::setmember (int x, T value) {//注意写法
memblock[x]=value;
}
template <class T, int N> T array<T,N>::getmember (int x) {
return memblock[x];
}
int main () {
array <int,5> myints;
array <float,5> myfloats;
myints.setmember (0,100);
myfloats.setmember (3,3.1416);
cout << myints.getmember(0) << '\n';
cout << myfloats.getmember(3) << '\n';
return 0;
}
我们也可以为模板参数设置默认值,就像为函数参数设置默认值一样。
下面是一些模板定义的例子:
template <class T> // 最常用的:一个class 参数。
template <class T, class U> // 两个class 参数。
template <class T, int N> // 一个class 和一个整数。
template <class T = char> // 有一个默认值。
template <int Tfunc (int)> // 参数为一个函数。
模板与多文件工程 (Templates and multiple-file projects)
从编译器的角度来看,模板不同于一般的函数或类。它们在需要时才被编译(compiled on demand),也就是说一个模板的代码直到需要生成一个对象的时候(instantiation)才被编译。当需要instantiation的时候,编译器根据模板为特定的调用数据类型生成一个特殊的函数。
当工程变得越来越大的时候,程序代码通常会被分割为多个源程序文件。在这种情况下,通常接口(interface)和实现(implementation)是分开的。用一个函数库做例子,接口通常包括所有能被调用的函数的原型定义。它们通常被定义在以.h 为扩展名的头文件 (header file) 中;而实现 (函数的定义) 则在独立的C++代码文件中。
模板这种类似宏(macro-like) 的功能,对多文件工程有一定的限制:函数或类模板的实现 (定义) 必须与原型声明在同一个文件中。也就是说我们不能再 将接口(interface)存储在单独的头文件中,而必须将接口和实现放在使用模板的同一个文件中。
回到函数库的例子,如果我们想要建立一个函数模板的库,我们不能再使用头文件(.h) ,取而代之,我们应该生成一个模板文件(template file),将函数模板的接口和实现 都放在这个文件中 (这种文件没有惯用扩展名,除了不要使用.h扩展名或不要不加任何扩展名)。在一个工程中多次包含同时具有声明和实现的模板文件并不会产生链接错误 (linkage errors),因为它们只有在需要时才被编译,而兼容模板的编译器应该已经考虑到这种情况,不会生成重复的代码。