函数模板的重载
前面我们学习过函数的重载,而函数模板可以用来创建一个通用功能的函数,以支持多种不同形参,不同类型的参数调用就产生一系列重载函数。如例5-1中两次调用add()函数模板,编译时会根据传入参数不同实例化出两个函数,如下所示:
int add(int t1, int t2) //int类型参数实例化出的函数
{
return t1 + t2;
}
double add(double t1, double t2) //double类型参数实例化出的函数
{
return t1 + t2;
}
在最终运行的程序中会有这两个函数,而实例化出的这两个具体函数就是重载函数,在调用时时会根据传入的参数类型来调用相应的函数。
除此之外,函数模板本身也可以被重载,即相同函数模板名可以具有不同的函数模板定义,当进行函数调用时,编译器根据实参的类型与个数来决定调用哪个函数模板来实例化一个函数。例如我们要定义一个求两个任意类型数据最大值的函数,还要定义一个求三个任意类型数据最大值的函数,都是求任意类型数据的最大值,那么就可以定义重载的函数模板来实现,代码如例1所示。
例1
1 #include <iostream>
2 using namespace std;
3 int max(const int& a, const int& b) //非模板函数,求两个int类型数据的最大者
4 {
5 return a>b ? a : b;
6 }
7 template<typename T> //定义求两个任意类型数据的最大值
8 T max(const T& t1, const T& t2)
9 {
10 return t1 > t2 ? t1 : t2;
11 }
12 template<typename T> //定义求三个任意类型数据的最大值
13 T max(const T& t1, const T& t2, const T&t3)
14 {
15 return max(max(t1, t2), t3);
16 }
17 int main()
18 {
19 cout << max(1, 2) << endl; //调用非模板函数
20 cout << max(1, 2, 3) << endl; //调用三个参数的函数模板
21 cout << max('a', 'e') << endl; //调用两个参数的函数模板
22 cout << max(6, 3.2) << endl; //调用非模板函数
23
24 system("pause");
25 return 0;
26 }
运行结果如图1所示。
图1 例1运行结果
在例1中,一个非模板函数和两个同名函数模板同时存在,而且函数模板还可以实例化为这个非模板函数。对于非模板函数和同名的函数模板,如果其他条件都相同,那么在调用的时候重载解析过程会调用非模板函数而不会用模板产生出一个实例,如本例中第一个函数调用,传入两个int值很好的匹配了非模板函数。
然而如果函数模板能够更好的实例化出一个匹配的函数,则调用时将选择函数模板,如本例中第三个函数调用,利用函数模板实例化出一个带有char类型参数的函数,而不会调用非模板函数max(int,int)。
需要注意的是,如果有不同类型参数,则只允许使用非模板函数,因为模板是不允许自动类型转化的,但普通函数可以进行自动类型转换,所以最后一个是调用的非模板函数,将3.2转换成了int类型再与6进行比较。
脚下留心:使用函数模板要注意的问题
函数模板虽然可以极大地解决代码重用的问题,但在使用时也有一些问题需要注意,具体如下:
(1)函数模板中的每一个类型参数在函数参数表中必须至少使用一次,例如下面的函数模板声明是不正确的。
template<typename T1, typename T2>
void func(T1 t)
{
//……
}
函数模板声明了两个参数T1与T2,但在使用时只使用了T1,没有使用T2。
(2)在全局域中声明的与模板参数同名的对象、函数或类型,在函数模板中将被隐藏,例如:
int num;
template<typename T>
void func(T t)
{
T num;
}
在函数体内访问num是访问的T类型的num,而不是全局变量num。
(3)函数模板中定义声明的对象或类型不能与模板参数同名,例如:
template<typename T>
void func(T t)
{
typedef float T; //错误,定义的类型与模板参数名相同
//……
}
(4)模板参数名在同一模板参数表中只能使用一次,但可在多个函数模板声明或定义之间重复使用。例如:
template<typename T, typename T> //错误,在同一个模板中重复定义模板参数
void func1(T t1, T t2){}
template<typename T>
void func2(T t1){}
template<typename T> //在不同函数模板中可重复使用相同模板参数名
void func3(T t1){}
(5)模板的定义和多处声明所使用的模板参数名不一定要必须相同,例如:
//模板的前向声明
template<typename T>
void func1(T t1, T t2);
//模板的定义
template<typename U>
void func1(U t1, U t2)
{
//……
}
(6)函数模板如果有多个模板参数,则每个模板类型前都必须使用关键字typename或class修饰,例如:
template<typename T,class U> //两个关键字可以混用
void func(T t, U u){}
template<typename T,U> //错误,每一个模板参数前都要有关键字修饰
void func(T t, U u){}
对于初学者来说,使用函数模板时,这些问题都是经常容易犯的错误,要多加留心。