摘抄自《C++ Primer Plus》
函数模板是C++的又一项新特性。函数模板是通用的函数描述,也就是说,它们使用泛型
来定义函数,其中的泛型可用具体的类型(如 int 或 double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型(而不是具体类型)的方式编写程序,因此有时也被称为通用编程。由于类型是用参数表示的,因此模板特性有时也被称为参数化类型(parameterized types)。可能看到这里,感觉有点云里雾里的,没关系,继续往下看,看完之后再回来,应该就能理解了
。下面介绍为何需要这种特性以及其工作原理。
假设我们有一个函数是用来交换两个int值的:
void Swap(int &a, int &b)
{
int tmep;
temp = a;
a = b;
b = temp;
}
我们现在想要交换两个double值,我们需要重载这个函数
void Swap(double &a, double &b)
{
double temp;
temp = a;
a = b;
b = temp;
}
C++中的函数模板功能可以帮我们省去这一步,可以节省时间,而且更可靠。
函数模板允许以任意的方式来定义函数。例如,可以这样建立一个交换模板
template <typename AnyType>
void Swap(AnyType &a, AnyType &b)
{
AnyType tmep;
temp = a;
a = b;
b = tmep;
}
第一行指出,要建立一个模板,并将类型命名为AnyType。关键字template和typename是必需的
,除非可以使用关键字class代替typename。另外,必须使用尖括号
。类型名可以任意选择(这里为AnyType),只要遵守C++命名规则即可;许多程序员都使用简单的名称,如T
。余下的代码描述了交换两个AnyType值的算法。模板并不创建任何函数,而只是告诉编译器如何定义函数
。需要交换 int 的函数时,编译器将按模板模式创建这样的函数,并用int代替AnyType。同样,需要交换double的函数时,编译器将按模板模式创建这样的函数,并用double代替AnyType。
typename关键字使得参数AnyType表示类型这一点更为明显;然而,有大量代码库是使用关键字class开发的。在这种上下文中,这两个关键字是等价的。本书使用了这两种形式,旨在让您在其他地方遇到它们时不会感到陌生。
提示:如果需要多个将同一种算法用于不同类型的函数,请使用模板。如果不考虑向后兼容的问题,并愿意键入较长的单词,则声明类型参数时,应使用关键字typename而不使用class。
经过下面的例子之后,我们应该会有更深刻的理解
#include
//函数声明
template <class T>
void Swap(T &a, T &b);
int main()
{
using namespace std;
int i = 10;
int j = 20;
cout << "i, j = " << i << ", " << j << ".\n";
Swap(i, j);
cout << "Now i, j = " << i << ", " << j << ".\n";
double x = 24.5;
double y = 81.7;
cout << "x, y = " << x << ", " << y << ".\n";
Swap(x, y)
cout << "Now x, y = " << x << ", " << y << ".\n";
return 0;
}
//函数实现
template <class T>
void Swap(T &a, T &b)
{
T tmep = a;
a = b;
b = temp;
}
下面是程序输出结果
i, j = 10, 20;
Now i, j = 20, 10;
x, y = 24.5, 81.7;
Now x, y = 81.7, 24.5;
需要多个对不同类型使用同一种算法的函数时
,可使用模板,如程序清单8.11所示。然而,并非所有的类型都使用相同的算法
。为满足这种需求,可以像重载常规函数定义那样重载模板定义。和常规重载一样,被重载的模板的函数特征标必须不同。另外,并非所有的模板参数都必须是模板参数类型。
下面是一个例子
template <class T>
void Swap(T &a, T &b);
template <class T>
void Swap(T *a, T *b, int n);
我这里只是写了它们的声明,并没有写实现,如果前面的重载和模板理解好了,那么重载的模板应该很好理解。
假设定义了如下结构:
struct job
{
char name[40];
double salary;
int floor;
};
另外,假设希望能够交换这两种结构的内容。原来的模板使用下面的内容来完成:
temp = a;
a = b;
b = temp;
由于C++允许将一个结构赋给另一个结构,因此即使T是一个job结构,上述代码也适用。然而,假设只想交换salary和floor成员,而不交换name成员,则需要使用不同的代码,但Swap( )的参数将保持不变(两个job结构的引用),因此无法使用模板重载来提供其他的代码(这句话要稍微动一下脑子)
。
然而,可以提供一个具体化函数定义—称为显式具体化(explicit specialization),其中包含所需的代码。当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。具体化机制随着C++的演变而不断变化。下面介绍C++标准定义的形式。下面是第三代具体化(ISO/ANSI C++标准):
具体化
优先于常规模板
,而非模板函数
优先于具体化和常规模板
。具体化模板函数的原型如下:
template <> void Swap<job>(job &a, job &b);
Swap中的job可以省略。
template<class T1, class T2>
void ft(T1 x, T2 y)
{
type xpy = x + y;
}
在这里我们并不知道type应该填什么,因为我们暂时还不知道x和y会是什么。也就是说type是随着x和y的类型而变化的,那么怎么办呢?
int x;
decltype(x) y; //make y the same type as x
因此,前面的模板函数可以修改成下面这样
template <class T1, class T2>
void ft(T1 x, T2 y)
{
decltype(x + y) xpy = x + y;
}
template<class T1, class T2>
?type? gt(T1 x, T2 y)
{
...
return x + y;
}
同样,无法预先知道将x和y相加得到的类型。好像可以将返回类型设置为decltype ( x + y),但不幸的是,此时还未声明参数x和y,它们不在作用域内(编译器看不到它们,也无法使用它们)
。必须在声明参数后使用decltype。为此,C++新增了一种声明和定义函数的语法。下面使用内置类型来说明这种语法的工作原理。对于下面的原型:
double h(int x, float y);
按照新增的语法可以编写成这样
auto h(int x, float y) -> double
这将返回类型移动到了参数声明后面。->double被称为后置返回类型。
通过结合这种语法和decltype,便可给gt()指定返回值类型,如下所示:
template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x + y)
{
...
return x + y;
}
这一篇博客讲了很多的C++特性,这些特性给我的感觉可以用两个词来形容:强大,复杂
。