前往我的博客阅读体验更佳:本文链接
概述
函数模板是函数的蓝图或处方,编译器使用它生成函数系列的新成员。
新函数在第一次使用时创建。
从函数模板中生成的函数称为该模板的一个实例或模板的实例化。
一个函数模板的定义如下图所示:
函数模板的开头是关键字template,表示这是一个模板。
其后是一对尖括号,它包含了参数列表,本例中只有一个参数 T 。class是一个关键字,它表示 T 是一个类型。
注:尖括号中 class 可换成关键字 typename。
创建函数模板的实例
编译器将从使用函数 laeger() 的语句中,按照要求创建出函数的实例。例如:
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 后,模板实例就变成:
double larger(double a, double b){
return a > b ? a : b;
}
注:这个模板的实力化只生成一次,如果后续的函数调用需要同一个实例,就会调用已经创建好的实例。
函数模板举例
//程序示例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() 版本处理(因为总是需要后一个版本)。在调用函数时,指定要使用的模板参数类型,就可以强迫使用该版本的函数:
cout << "Larger of " << a_int << " and " << b_int << " is "
<< larger<long>(a_int, b_int)
<< endl;
模板的说明
假定扩展上述程序示例,使用地址参数调用函数 larger():
cout << "Larger of " << a_long << " and " << b_long << " is "
<< *larger(&a_long, &b_long)
<< endl;
这个语句执行后,编译器会创建一个模板参数类型是 long* 的函数版本,这个函数有如下原型:
long* larger (long*, long*);
返回值是一个地址,必须解除对它的引用,才能输出其值。*但是,这么做结果常常不正确!*这是因为函数体中的比较不正确。生成的函数如下所示:
long* larger(long* a, long* b){
return a > b ? a : b;
}
这是在比较地址,而不是比较值!!!
为了解决这种情况,需要定义模板的说明。
对于某个参数值(在有多个参数的模板中,就是一组参数值),模板的说明定义了它不同于标准模板的动作。
定义模板说明
说明的定义以关键字template开头,但要省略参数。所以原声明中模板参数外部的尖括号就是空的。
必须定义说明的参数值,而且必须放在模板函数名后面的尖括号中。例如,long* 的 larger() 函数,其说明如下所示:
template <> long* larger<long*>(long* a, long* b){
return *a > *b ? a : b;
}
函数体的唯一改变是解除参数 a 和 b 的引用,以便比较数值,而不是地址。
使用模板说明示例
//程序示例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 中的说明原型:
long* larger(long* a, long* b);
同样,程序示例 2 中说明定义需要改为如下函数定义:
long* larger(long* a, long* b){
cout << "overloaded version for long*" << endl;
return *a > *b ? a : b;
}
还可以使用一个模板重载另一个已有的模板。例如,扩展程序示例 2,添加一个重载的模板,查找包含在一个数组中的最大值。该模板的定义如下所示:
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;
}
重载函数模板程序示例
//程序示例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() 定义另一个模板,允许独立于函数参数类型来指定返回类型:
template <class TReturn, class TArg>
TReturn larger (TArg a, TArg b){
return a > b ? a : b;
}
注意,因为编译器不能推断返回值的类型 TReturn,所以必须指定该类型。但是,因为编译器可以推断出参数的类型,所以可以只指定返回类型。例如:
cout << "Larger of 1.5 and 2.5 is"
<< larger<int> (1.5, 2.5)
<< endl;
在尖括号中,返回类型指定为 int,参数类型则根据参数推断为 double。该函数调用的结果是 2。还可以指定 TReturn 和 TArg:
cout << "larger of 1.5 and 2.5 is"
<< larger<double, double> (1.5, 2.5)
<< endl;
编译器会创建一个函数,其参数类型是 double,返回类型也是 double。
显然,模板定义中的模板参数的顺序是非常重要的。如果在定义模板时,把返回类型定义为第二个参数,在函数调用中,就必须总是指定两个参数。如果只指定一个参数,该参数就解释为参数的类型,而返回类型则未定义。
资料来源:《C++入门经典(第 3 版)》