章节目录

函数模板

本节阅读量:

假设您想编写一个函数来计算两个数字的最大值。您可以这样做:

1
2
3
4
int max(int x, int y)
{
    return (x < y) ? y : x;
}

虽然调用方可以将不同的值传递到函数中,但参数的类型是固定的,因此调用方只能传入int值。这意味着该函数实际上只适用于整数(以及可以提升为int的类型)。

那么,当您想找到两个double值的最大值时,会发生什么呢?由于C++要求我们指定所有函数参数的类型,因此解决方案是创建一个新的重载版本的max,参数类型为double:

1
2
3
4
double max(double x, double y)
{
    return (x < y) ? y: x;
}

注意,这个版本max的实现代码与int版本max完全相同!事实上,这个实现适用于许多不同的类型:包括int、double、long、long double,甚至是您自己创建的新类型(我们将在以后的课程中介绍如何做)。

为想要支持的每一组参数类型创建具有相同实现的重载函数,这是一个维护难题,是错误的方案,并且明显违反了DRY(不要重复自己)原则。这里还有一个不太明显的挑战:希望使用max函数的程序员可能以max函数编写者没有预料到的参数类型来调用它(因此没有为此编写重载函数)。

我们真正缺少的是某种编写max的单个版本的方法,该版本可以与任何类型的参数一起工作(即使是在编写max代码时可能没有预料到的类型)。正常的函数根本不能胜任这里的任务。幸运的是,C++支持另一个专门为解决这种问题而设计的功能。

欢迎来到C++模板的世界。


C++模板简介

在C++中,模板系统旨在简化创建能够处理不同数据类型的函数(或类)的过程。

我们不是手动创建一组基本相同的函数或类(每组不同类型一个),而是创建一个模板。就像普通定义一样,模板描述函数或类的外观。与普通定义(其中必须指定所有类型)不同,在模板中,我们可以使用一个或多个占位符类型。占位符类型表示在编写模板时未知的某种类型,但稍后将提供。

一旦定义了模板,编译器就可以使用该模板,根据需要生成任意多个重载函数(或类),每个函数使用不同的实际类型!

最终的结果是相同的——我们最终得到了一组基本相同的函数或类(每个函数或类对应一组不同的类型)。但我们只需要创建和维护一个模板,编译器为我们做所有的重复和艰苦的工作。

由于直到模板在程序中使用(而不是在编写模板时)才确定实际类型,因此模板的作者不必尝试预测可能使用的所有实际类型。这意味着模板代码可以与编写模板时甚至不存在的类型一起使用!稍后,当我们开始探索C++标准库时,我们将看到这是如何派上用场的,该库绝对充满了模板代码!

在本节的其余部分中,我们将介绍和探索如何为函数创建模板,并更详细地描述它们的工作方式。直到我们介绍了类是什么,我们才会探索如何为类创建模版。


函数模板

函数模板是一种类似于函数的定义,用于生成一个或多个重载函数,每个函数具有一组不同的实际类型。这将允许我们创建可以与许多不同类型一起工作的函数。

创建函数模板时,我们将类型占位符(也称为模板类型参数,type template parameters)用于希望稍后指定的函数中使用的任何参数类型、返回类型或函数体中变量的类型。

函数模板,最好通过示例来教授,因此让我们将上面示例中的int max(int, int)函数转换为函数模板。这出人意料的简单,我们将解释一路上发生的事情。


创建模板化的max函数

这里是max的int版本:

1
2
3
4
int max(int x, int y)
{
    return (x < y) ? y : x;
}

注意,我们在这个函数中三次使用int类型:一次用于参数x,一次用于参数y,一次用作函数的返回类型。

要创建函数模板,我们要做两件事。首先,我们将用模板类型参数替换我们的特定类型。在这种情况下,因为我们只有一个需要替换的类型(int),所以我们只需要一个模板类型参数(这里将其称为T):

下面是使用单个模板类型的新函数:

1
2
3
4
T max(T x, T y) // 无法编译通过,因为没有定义 T
{
    return (x < y) ? y : x;
}

这是一个好的开始——然而,它无法编译,因为编译器不知道T是什么!这仍然是一个普通函数,不是函数模板。

因此,我们要告诉编译器,这是一个函数模板,T是一个模板类型参数,它是任何类型的占位符。这是使用所谓的模板参数声明来完成的。模板参数声明的范围仅限于后面的函数模板(或类模板)。因此,每个函数模板(或类模版)都需要自己的模板参数声明。

1
2
3
4
5
template <typename T> // 这是模版参数声明
T max(T x, T y) // 这是函数模版 max<T> 的定义
{
    return (x < y) ? y : x;
}

在模板参数声明中,我们从关键字template开始,它告诉编译器我们正在创建模板。接下来,我们指定所有模板参数,这些都在尖括号(<>)内。对于每个模板类型参数,我们使用关键字typename或class,后跟模板类型参数的名称(例如T)。

信不信由你,我们完工了!我们已经创建了max函数的模板版本,现在可以接受不同类型的参数。

因为该函数模板有一个名为T的模板类型,所以我们将其称为max<T>。在下一课中,我们将看看如何使用max<T>函数模板来生成一个或多个具有不同类型参数的max()函数。


命名模板参数

就像我们经常使用单个字母来表示在琐碎情况下使用的变量名(例如x)一样,当类型的含义琐碎或明显时,通常使用单个大写字母(以T开头)。例如,在max函数模板中:

1
2
3
4
5
template <typename T>
T max(T x, T y)
{
    return (x < y) ? y : x;
}

我们不需要给T一个复杂的名称,因为它显然只是要比较的值的占位符类型。

我们的函数模板通常使用这种命名约定。

如果模板类型参数的用法或含义不明显,则需要更具描述性的名称。此类名称有两种常见的约定:

  1. 以大写字母开头(例如,Allocator)。标准库使用此命名约定。
  2. 前缀为T,然后以大写字母开头(例如TAllocator)。这使得更容易看到类型是模板类型参数。

你选择哪一个是个人偏好的问题。


11.4 默认参数

上一节

11.6 函数模板实例化

下一节