函数模板的实例化
函数模板并不是一个函数,它只相当于一个模子,定义一次即可使用不同类型的参数来调用该函数,这样做可以减小代码的书写,提高代码的复用性。但要注意,使用函数模板不会减少最终可执行程序的大小,因为在调用函数模板时,编译器会根据调用时的参数类型进行相应的实例化。所谓实例化,就是用类型参数去替换模板中的模板参数,生成一个具体类型的真正函数。实例化可分为隐式实例化与显式实例化,接下来我们分别来讲解这两种实例化方式。
1、隐式实例化
隐式实例化是根据函数调用时传入的数据类型确定模板形参T的类型,模板形参的类型是隐式确定的,如例5-1中函数模板add()的调用过程。
在前面案例中第一次调用add()函数模板时,传入的是int型数据add(1,2),此时编译器根据传入的实参推演出模板形参类型是int,就会将函数模板实例化出一个int类型的函数,如下所示:
int add(int t1, int t2)
{
return t1 + t2;
}
编译器生成具体类型函数的这一过程就称为实例化,生成的函数称为模板函数。生成int类型的函数后,再将实参1和2传入进行运算。同理当传入double类型的数据时,编译器先将模板实例化为如下形式的函数:
double add(double t1, double t2)
{
return t1 + t2;
}
这样,每一次调用时都会根据不同的类型实例化出不同类型的函数,所以最终可执行程序的大小并不会减少,它只是提高了程序员对代码的复用。
2、显式实例化
隐式实例化不能为同一个模板形参指定两种不同的类型,如add(1,1.2),这样调用,两种形参类型不一致,编译器便会报错。那么此时可以用另一种实例化方式来解决这个问题——显式实例化。显式实例化就是显式的指定函数模板中的数据类型,其语法格式如下所示:
template 函数返回值类型 函数名<实例化的类型>(参数列表);
注意这是声明语句,要以分号结束,<>中是显式实例的数据类型,即要实例化出一个什么类型的函数,例如,显示实例化为int,则在调用时,不是int类型的数据会转换为int类型进行计算,例如将例5-1中的add()函数模板显式实例化为int类型,代码如下所示:
template int add<int>(int t1, int t2);
接下来我们将前面讲解的函数模板add()显式声明并调用,具体代码如例1所示。
例1
1 #include <iostream>
2 using namespace std;
3 template<typename T>
4 T add(T t1, T t2)
5 {
6 return t1 + t2;
7 }
8 //template int add<int>(int t1, int t2); //显式实例化为int类型
9 int main()
10 {
11 cout << add<int>(10, 'B') << endl; //函数模板调用
12 cout << add(1.2, 3.4) << endl;
13 system("pause");
14 return 0;
15 }
运行结果如图1所示。
图1 例1运行结果
在例1中将add()函数模板显式声明,指定模板形参类型为int。在调用int类型模板函数时,传入了一个字符’B’,则编译器会将字符类型的’B’转换为int类型,然后再与10相加得出结果。实际上就是一个隐式的数据类型转换。
注意:对于给定的函数模板实例,显式实例化声明在一个文件中只能出现一次,并且在这个文件中必须给出函数模板的定义,如果定义不可见就会发生错误。
现在C++编译器也在不断完善,像模板实例化的显式声明有时可以省略,只在调用时用<>显式指定要实例化的类型也可以,如例5-2中add(1.2, 3.4)函数调用如果改为add<int>(1,2, 3.4)调用,则也会得出结果4。
多学一招:显式具体化
函数模板的显式具体化是对函数模板的重新定义,具体格式如下所示:
template< > 函数返回值类型 函数名<实例化类型>(参数列表)
{
//函数体重新定义
}
显式实例化只需要显式声明模板形参的类型而不需要重新定义函数模板的实现,而显式具体化需要重新定义函数模板的实现,例如,定义交换两个数据的函数模板,示例代码如下:
template< typename T>
void swap(T& t1,T& t2)
{
T temp = t1;
t1 = t2;
t2 = temp;
}
但现在有如下结构体定义,示例代码如下:
struct Student
{
int id;
char name[40];
float score;
};
现在要调换两个学生的id编号,但是又不想交换学生的姓名、成绩等其他信息,那么此时就可以用显式具体化解决这个问题,重新定义函数模板只交换结构体的部分数据成员,显式具体化如下所示:
template<> void swap<Student>(Student& st1, Student& st2)
{
int temp = st1.id;
st1.id = st2.id;
st2.id = temp;
}
如果函数有多个原型,则编译器在选择函数调用时,非模板函数优先于模板函数,显式实例化模板优先于函数模板,例如下面三种定义:
void swap(int&, int&); //直接定义
template< typename T> //模板定义
void swap(T& t1, T& t2);
template<> void swap<int>(int&, int&); //显式具体化
对于int a,int b,如果存在swap(a,b)的调用,则优先调用直接定义的函数,如果没有则优先调用显式具体化,如果两者都没有才会调用函数模板。