C++函数模板的用法(非常详细)
C++ 中的函数可以比作专用的箱子,例如一个装衣服的箱子只能用来装衣服。函数通常也是专用的,例如获取两个 int 类型数中较大值的 max() 函数只能处理 int 类型数据。
然而,我们不需要为处理成千上万种数据类型编写成千上万个 max() 函数,因为 C++ 中提供了模板机制。
利用模板机制,我们可以创建一种万能的箱子,也就是函数模板,它可以接受一个或多个数据类型作为参数,从而处理各种类型的数据。当编译器遇到函数模板的调用时,它会根据提供的类型参数自动生成相应的实例化函数。
这样,一个函数模板可以根据参数类型自动变成多种不同的重载函数,实现对各种数据类型的处理。
在 C++ 中,许多算法需要能够处理不同数据类型的数据。例如,一个获取两个数中较大值的算法,不仅要能处理整型数,也要能处理字符串。尽管这些算法处理的数据类型不同,但算法本身的逻辑是相同的。这就像写请帖,虽然收件人不同,但内容模板相同。
函数模板的使用简化了重载函数的设计和实现,允许我们创建一个通用的函数来支持各种不同的数据类型。
函数模板的定义非常简单,其语法格式如下:
例如,为了使 max() 函数能处理 int、float、string 等数据类型,我们可以将其定义为函数模板:
与调用普通函数时需要指定它的实际参数一样,在调用函数模板时,我们也需要在函数名之后使用尖括号“<>”指定类型参数的实际类型,这样就得到了一个针对特定类型的重载函数,然后可以直接调用这个特定版本的重载函数。
例如,我们可以使用刚刚定义的 mymax() 函数模板来处理两个 int 类型数据和两个 string 类型数据:
对于函数模板可以根据调用时的模板类型参数动态生成相应的模板函数,读者一定会觉得非常神奇,那么它背后到底是如何运作的呢?
实际上,编译器在编译 mymax() 函数调用时,会以函数名之后尖括号“<>”内的数据类型作为函数模板定义时模板类型参数的实际类型。编译器会根据函数模板中的定义为样板,用实际的数据类型替换函数模板中的类型参数,从而生成针对特定类型的重载函数(实际函数)。
例如,在编译 mymax<int>(a,b) 时,编译器会用实际的类型 int 替换掉函数模板中的类型参数 T,自动为 mymax() 函数调用生成一个整型数的版本:
特别地,如果调用函数模板时,编译器能够根据实际参数的类型推断出函数模板的类型参数,那么函数调用中“<>”内的类型参数也可以省略。函数模板生成重载函数的过程如下图所示。

图 1 函数模板生成重载函数
从上述代码的输出结果中可以发现,当使用 mymax() 函数模板从“Good”和“Afternoon”这两个字符串中选取较大值时,得到的结果是字符串“Good”。这是因为字符串的比较是基于字符的 ASCII 值进行的,而我们实际上期望的结果是字符串“Afternoon”,也就是基于字符串长度比较的结果。这就意味着,虽然函数模板的意义是为不同的数据类型提供通用的算法,但这些算法有时可能无法完全满足所有需求。
例如,mymax() 函数使用“>”运算符来比较两个参数的大小。在大多数情况下,例如参数类型是 int、float 等数值类型时,这种比较都是合理的。但是,当参数是 string 类型时,它会逐个比较字符串中字符的 ASCII 值来决定两个 string 类型参数的大小,而这并不是我们所期望的结果。在这种情况下,就需要对函数模板进行特化,以实现特定类型的模板函数。
通过这种方式,可以使函数模板既能适应大多数情况,又能满足个性化的特殊需求。例如:
有了某个特定类型的模板特化之后,当使用这一特定类型的参数调用函数模板时,编译器将使用特化后的函数模板。如果使用其他类型的参数,则仍然会使用函数模板的普通版本。例如:
然而,我们不需要为处理成千上万种数据类型编写成千上万个 max() 函数,因为 C++ 中提供了模板机制。
利用模板机制,我们可以创建一种万能的箱子,也就是函数模板,它可以接受一个或多个数据类型作为参数,从而处理各种类型的数据。当编译器遇到函数模板的调用时,它会根据提供的类型参数自动生成相应的实例化函数。
这样,一个函数模板可以根据参数类型自动变成多种不同的重载函数,实现对各种数据类型的处理。
在 C++ 中,许多算法需要能够处理不同数据类型的数据。例如,一个获取两个数中较大值的算法,不仅要能处理整型数,也要能处理字符串。尽管这些算法处理的数据类型不同,但算法本身的逻辑是相同的。这就像写请帖,虽然收件人不同,但内容模板相同。
函数模板的使用简化了重载函数的设计和实现,允许我们创建一个通用的函数来支持各种不同的数据类型。
函数模板的定义非常简单,其语法格式如下:
template <typename 标识符1, typename 标识符2...> 返回值类型 函数名(形参表) { // 函数体 }从这里可以看到,函数模板的定义与普通函数非常相似,都需要指定返回值类型、函数名和参数列表。不同之处在于,定义函数模板时,使用 template 关键字来标识,并在其后的尖括号中使用 typename 定义一个或多个类型参数,这些代表抽象数据类型。在函数模板中,这些类型参数作为占位符,而在模板函数被调用时,它们将被具体数据类型替换,生成特定类型的函数实例。
例如,为了使 max() 函数能处理 int、float、string 等数据类型,我们可以将其定义为函数模板:
// 获取两个数中的较大值 // T 就是函数模板的类型参数 // 为了与标准库中的 max() 函数进行区分,用 mymax 作为函数名 // 为了防止数据被修改,使用 const 声明参数和返回值 template <typename T> const T& mymax(const T& a, const T& b) { return a > b ? a : b; }在上面的函数模板定义中,我们首先在尖括号“<>”中用 typename 关键字定义了一个类型参数 T。接下来,我们可以将 T 视为实际的数据类型来使用,可以用于函数返回值和参数的类型,也可以在函数内用它来定义变量等。
与调用普通函数时需要指定它的实际参数一样,在调用函数模板时,我们也需要在函数名之后使用尖括号“<>”指定类型参数的实际类型,这样就得到了一个针对特定类型的重载函数,然后可以直接调用这个特定版本的重载函数。
例如,我们可以使用刚刚定义的 mymax() 函数模板来处理两个 int 类型数据和两个 string 类型数据:
// ... int main() { // 两个 int 类型数据 int a = 4; int b = 5; cout << a << "和" << b << "之间较大的是" << mymax<int>(a, b) << endl; // 调用 int 版本的 mymax() 处理 int 类型数据 // 两个 string 类型数据 string strA = "Good"; string strB = "Afternoon"; cout << strA << "和" << strB << "之间较大的是" << mymax<string>(strA, strB) << endl; // 调用 string 版本的 mymax() 处理 string 类型数据 return 0; }在这段程序中,我们使用了 int 和 string 作为 mymax() 函数模板的模板类型参数。函数模板根据不同的类型参数生成不同版本的模板函数,以实现对相应类型数据处理。这种方法减少了函数的重载,提高了代码的复用性,并提高了开发效率。否则,如果针对每种数据类型都需要编写一个特定的重载函数,整个程序将陷入一片同质化的重载函数的汪洋大海中。
对于函数模板可以根据调用时的模板类型参数动态生成相应的模板函数,读者一定会觉得非常神奇,那么它背后到底是如何运作的呢?
实际上,编译器在编译 mymax() 函数调用时,会以函数名之后尖括号“<>”内的数据类型作为函数模板定义时模板类型参数的实际类型。编译器会根据函数模板中的定义为样板,用实际的数据类型替换函数模板中的类型参数,从而生成针对特定类型的重载函数(实际函数)。
例如,在编译 mymax<int>(a,b) 时,编译器会用实际的类型 int 替换掉函数模板中的类型参数 T,自动为 mymax() 函数调用生成一个整型数的版本:
// 整型数版本的 mymax() 模板函数 const int& mymax(const int& a, const int& b) { return a > b ? a : b; }在主函数中,当我们以 int 作为类型参数调用 mymax() 函数模板时,实际上执行的是为 int 类型生成的 mymax() 函数的整型数版本。同理,当以 string 作为类型参数调用 mymax() 函数时,编译器会为这个函数调用生成 mymax() 函数的 string 版本,进而调用该函数版本来处理两个 string 类型的数据。
特别地,如果调用函数模板时,编译器能够根据实际参数的类型推断出函数模板的类型参数,那么函数调用中“<>”内的类型参数也可以省略。函数模板生成重载函数的过程如下图所示。

图 1 函数模板生成重载函数
从上述代码的输出结果中可以发现,当使用 mymax() 函数模板从“Good”和“Afternoon”这两个字符串中选取较大值时,得到的结果是字符串“Good”。这是因为字符串的比较是基于字符的 ASCII 值进行的,而我们实际上期望的结果是字符串“Afternoon”,也就是基于字符串长度比较的结果。这就意味着,虽然函数模板的意义是为不同的数据类型提供通用的算法,但这些算法有时可能无法完全满足所有需求。
例如,mymax() 函数使用“>”运算符来比较两个参数的大小。在大多数情况下,例如参数类型是 int、float 等数值类型时,这种比较都是合理的。但是,当参数是 string 类型时,它会逐个比较字符串中字符的 ASCII 值来决定两个 string 类型参数的大小,而这并不是我们所期望的结果。在这种情况下,就需要对函数模板进行特化,以实现特定类型的模板函数。
通过这种方式,可以使函数模板既能适应大多数情况,又能满足个性化的特殊需求。例如:
// 利用模板特化,实现特定的 string 类型的模板函数 template <> // 类型参数留空 // 使用实际类型 string 代替类型参数 T String& mymax<String>(const string& a, const string& b) { // 通过长度比较决定字符串大小 return a.length() > b.length() ? a : b; }
有了某个特定类型的模板特化之后,当使用这一特定类型的参数调用函数模板时,编译器将使用特化后的函数模板。如果使用其他类型的参数,则仍然会使用函数模板的普通版本。例如:
// 未特化的类型,依然使用 ">" 比较大小 cout << a << "和" << b << "之间较大的是" << mymax<int>(a, b) << endl; // ... // 特化后的类型,使用特化的模板函数,通过字符串长度比较大小 cout << "使用 string 类型特化版本:" << strA << "和" << strB << "之间较大的是" << mymax<string>(strA, strB) << endl; // 返回结果是 afternoon