C++中的函数模板

摘抄自《C++ Primer Plus》

C++中的函数模板_第1张图片

文章目录

  • 前言
  • 基本理解
  • 一个例子
  • 重载的模板
  • 显示具体化
  • 模板函数的发展
  • 总结

前言

函数模板是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<>打头,并通过名称来指出类型。
  • 具体化优先于常规模板,而非模板函数优先于具体化和常规模板

具体化模板函数的原型如下:

template <> void Swap<job>(job &a, job &b);

Swap中的job可以省略。

模板函数的发展

  1. 什么是类型
    在C++98中,编写函数模板时,一个问题并非总能直到应在声明中使用哪种类型。请看下面的示例
template<class T1, class T2>
void ft(T1 x, T2 y)
{
	type xpy = x + y;
}

在这里我们并不知道type应该填什么,因为我们暂时还不知道x和y会是什么。也就是说type是随着x和y的类型而变化的,那么怎么办呢?

  1. C++提供了关键字decltype,可以按照下面的样子使用关键字:
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;
}
  1. 另一种函数声明语法(C++后置返回类型)
    有一个相关的问题是decltype本身无法解决的。请看下面这个不完整的模板函数:
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++特性,这些特性给我的感觉可以用两个词来形容:强大,复杂

C++中的函数模板_第2张图片

你可能感兴趣的:(C++)