C++学习笔记:C++的函数模板

本文最后更新于:2024年3月29日 上午

概述

函数模板是函数的蓝图或处方,编译器使用它生成函数系列的新成员。

新函数在第一次使用时创建。

从函数模板中生成的函数称为该模板的一个实例或模板的实例化。

一个函数模板的定义如下图所示:

函数模板的开头是关键字template,表示这是一个模板。

其后是一对尖括号,它包含了参数列表,本例中只有一个参数 T 。class是一个关键字,它表示 T 是一个类型。

注:尖括号中 class 可换成关键字 typename

创建函数模板的实例

编译器将从使用函数 laeger() 的语句中,按照要求创建出函数的实例。例如:

1
cout << "Larger of 1.5 and 2.5 is " << larger(1.5, 2.5) << endl;

这里,因为 larger() 的参数是 double 类型,所以这个调用会让编译器搜索带有 double 参数的 larger() 版本。如果没有找到,编译器就会从模板中创建这个 larger() 版本,并用类型 double 替换模板定义中的 T。

得到的模板函数接受 double 类型的参数,返回一个 double 值。用 double 替换了 T 后,模板实例就变成:

1
2
3
double larger(double a, double b){
return a > b ? a : b;
}

注:这个模板的实力化只生成一次,如果后续的函数调用需要同一个实例,就会调用已经创建好的实例。

函数模板举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//程序示例1:使用函数模板
#include <iostream>
using std::cout;
using std::endl;

//函数模板声明
template <class T>
T larger(T a, T b);

int main()
{
cout << endl;
cout << "Larger of 1.5 and 2.5 is " << larger(1.5, 2.5) << endl;
cout << "Larger of 3.5 and 4.5 is " << larger(3.5, 4.5) << endl;

int a_int = 35;
int b_int = 45;
cout << "Larger of " << a_int << " and " << b_int << " is "
<< larger(a_int, b_int)
<< endl;

long a_long = 9;
long b_long = 8;
cout << "Larger of " << a_long << " and " << b_long << " is "
<< larger(a_long, b_long)
<< endl;

return 0;
}

//函数模板定义
template <class T>
T larger(T a, T b)
{
return a > b ? a : b;
}

显式指定模板参数

在调用函数时,可以显式指定模板的参数,以控制使用哪个版本的函数。编译器不再推断用于 T 的类型,只是接受指定的版本。适用于下列情况:

  • 函数调用不是很确切,编译失败。此时可以使用该技巧帮助编译器去除不确定性。
  • 在一些情况下,编译器不能推断出模板参数,因此无法选择要使用哪个版本的函数,在这种情况下,必须显式指定模板参数。
  • 为了避免有太多的函数版本(从而避免过多占用内存),可以强迫函数调用使用某个版本的函数。

在上面例子中,int 类型的参数由接受 long 类型参数的 larger() 版本处理(因为总是需要后一个版本)。在调用函数时,指定要使用的模板参数类型,就可以强迫使用该版本的函数:

1
2
3
cout << "Larger of " << a_int << " and " << b_int << " is "
<< larger<long>(a_int, b_int)
<< endl;

模板的说明

假定扩展上述程序示例,使用地址参数调用函数 larger():

1
2
3
cout << "Larger of " << a_long << " and " << b_long << " is "
<< *larger(&a_long, &b_long)
<< endl;

这个语句执行后,编译器会创建一个模板参数类型是 long* 的函数版本,这个函数有如下原型:

1
long* larger (long*, long*);

返回值是一个地址,必须解除对它的引用,才能输出其值。但是,这么做结果常常不正确!这是因为函数体中的比较不正确。生成的函数如下所示:

1
2
3
long* larger(long* a, long* b){
return a > b ? a : b;
}

这是在比较地址,而不是比较值!!!

为了解决这种情况,需要定义模板的说明

对于某个参数值(在有多个参数的模板中,就是一组参数值),模板的说明定义了它不同于标准模板的动作。

定义模板说明

说明的定义以关键字template开头,但要省略参数。所以原声明中模板参数外部的尖括号就是空的。

必须定义说明的参数值,而且必须放在模板函数名后面的尖括号中。例如,long* 的 larger() 函数,其说明如下所示:

1
2
3
template <> long* larger<long*>(long* a, long* b){
return *a > *b ? a : b;
}

函数体的唯一改变是解除参数 a 和 b 的引用,以便比较数值,而不是地址。

使用模板说明示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//程序示例2:使用函数模板说明
#include <iostream>
using std::cout;
using std::endl;

//函数模板声明
template <class T>
T larger(T a, T b);
//函数模板说明
template <>
long *larger<long *>(long *a, long *b);

int main()
{
cout << endl;
cout << "Larger of 1.5 and 2.5 is " << larger(1.5, 2.5) << endl;
cout << "Larger of 3.5 and 4.5 is " << larger(3.5, 4.5) << endl;

int a_int = 35;
int b_int = 45;
cout << "Larger of " << a_int << " and " << b_int << " is "
<< larger(a_int, b_int)
<< endl;

long a_long = 9;
long b_long = 8;
cout << "Larger of " << a_long << " and " << b_long << " is "
<< larger(a_long, b_long)
<< endl;

cout << "Larger of " << a_long << " and " << b_long << " is "
<< *larger(&a_long, &b_long)
<< endl;

return 0;
}

//函数模板定义
template <class T>
T larger(T a, T b)
{
cout << "standard version" << endl;
return a > b ? a : b;
}

//模板说明定义
template <>
long *larger<long *>(long *a, long *b)
{
cout << "specialized version" << endl;
return *a > *b ? a : b;
}

函数模板与重载

重载从函数模板中生成的函数有不同的方式。

  • 用从模板生成的一个函数重载另一个函数。
  • 直接定义同名的其他函数,来重载函数。使用重载方式,可以为特定的情况定义重写版本,这些重写函数在使用时优先于模板。在这种情况下,每个重载的函数都必须有惟一的签名。

同样以传递地址的 larger() 函数为例,这次不使用模板说明,而是显式声明一个重载函数。如果采用这种方法,就要用下面的重载函数原型代替程序示例 2 中的说明原型:

1
long* larger(long* a, long* b);

同样,程序示例 2 中说明定义需要改为如下函数定义:

1
2
3
4
long* larger(long* a, long* b){
cout << "overloaded version for long*" << endl;
return *a > *b ? a : b;
}

还可以使用一个模板重载另一个已有的模板。例如,扩展程序示例 2,添加一个重载的模板,查找包含在一个数组中的最大值。该模板的定义如下所示:

1
2
3
4
5
6
7
8
9
10
template <class T>
T larger(const T array[], int count)
{
cout << "template overload version for arrays" << endl;
T result = array[0];
for (int i = 1; i < count; i++)
if (array[i] > result)
result = array[i];
return result;
}

重载函数模板程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//程序示例3:重载函数模板
#include <iostream>
using std::cout;
using std::endl;

//函数模板声明
template <class T>
T larger(T a, T b);

template <class T>
T larger(const T array[], int count);

int main()
{
cout << endl;
cout << "Larger of 1.5 and 2.5 is " << larger(1.5, 2.5) << endl;
cout << "Larger of 3.5 and 4.5 is " << larger(3.5, 4.5) << endl;

int a_int = 35;
int b_int = 45;
cout << "Larger of " << a_int << " and " << b_int << " is "
<< larger(a_int, b_int)
<< endl;

long a_long = 9;
long b_long = 8;
cout << "Larger of " << a_long << " and " << b_long << " is "
<< larger(a_long, b_long)
<< endl;

cout << "Larger of " << a_long << " and " << b_long << " is "
<< *larger(&a_long, &b_long)
<< endl;

double x[] = {10.5, 12.5, 2.5, 13.5, 5.5};
cout << "Largest element has the value "
<< larger(x, sizeof(x) / sizeof(x[0]))
<< endl;

return 0;
}

//函数模板定义
template <class T>
T larger(T a, T b)
{
cout << "standard version" << endl;
return a > b ? a : b;
}

template <class T>
T larger(const T array[], int count)
{
cout << "template overload version for arrays" << endl;
T result = array[0];
for (int i = 1; i < count; i++)
if (array[i] > result)
result = array[i];
return result;
}

带有多个参数的模板

前面使用了带有一个参数的函数模板,也可以在模板中使用多个参数。

第二个类型参数的典型应用是提供控制函数模板中返回类型的方式。

可以为函数 larger() 定义另一个模板,允许独立于函数参数类型来指定返回类型:

1
2
3
4
template <class TReturn, class TArg>
TReturn larger (TArg a, TArg b){
return a > b ? a : b;
}

注意,因为编译器不能推断返回值的类型 TReturn,所以必须指定该类型。但是,因为编译器可以推断出参数的类型,所以可以只指定返回类型。例如:

1
2
3
cout << "Larger of 1.5 and 2.5 is"
<< larger<int> (1.5, 2.5)
<< endl;

在尖括号中,返回类型指定为 int,参数类型则根据参数推断为 double。该函数调用的结果是 2。还可以指定 TReturn 和 TArg:

1
2
3
cout << "larger of 1.5 and 2.5 is"
<< larger<double, double> (1.5, 2.5)
<< endl;

编译器会创建一个函数,其参数类型是 double,返回类型也是 double。

显然,模板定义中的模板参数的顺序是非常重要的。如果在定义模板时,把返回类型定义为第二个参数,在函数调用中,就必须总是指定两个参数。如果只指定一个参数,该参数就解释为参数的类型,而返回类型则未定义。


资料来源:《C++入门经典(第 3 版)》


C++学习笔记:C++的函数模板
https://summersong.top/post/45748.html
作者
SummerSong
发布于
2022年7月27日
更新于
2024年3月29日
许可协议