在C++中,模板分为两种:
本节主要讲解函数模板。
所谓模板,就是为了节省代码,对于那些相似的代码,我们可以重复使用。
假设我们要写一个比较两个 int 类型变量谁大的函数,那么可以这样写:
- int &my_max(int &a, int &b)
- {
- return ((a < b) ? b : a);
- }
比较两个 double 型:
- double &my_max(double &a, double &b)
- {
- return ((a < b) ? b : a);
- }
两个 float 型:
- float &my_max(float &a, float &b)
- {
- return ((a < b) ? b : a);
- }
可以看到,后面两个double和float的比较函数,其实都是对第一个函数的重载。
这些函数这么相似,是否有什么办法可以简化代码,不用定义这么多函数呢?
答:可以的,使用函数模板。
函数模板的格式如下:
- template<类型参数表>
- 返回值 函数名(数据参数表)
- {
- 函数模板定义体
- }
代码如下,将输入参数的类型和返回值的类型都用T代替,同时使用__PRETTY_FUNCTION__宏,增加一些调试信息:
- template
- T &my_max(T &a, T &b)
- {
- cout << __PRETTY_FUNCTION__ << endl;
- return ((a < b) ? b : a);
- }
修改main函数。
- int main(int argc, char **argv)
- {
- int ia = 1, ib = 2;
-
- cout << my_max(ia, ib) << endl;
-
- return 0;
- }
编译测试,可以看到,调用了模板定义的my_max函数,并且成功返回了对比的两数中较大的值:

那么,函数模板的内部机制是怎么样呢?
先来看一下刚刚生成的可执行文件max,可以看到大小是9440B。

修改一下main函数,增加double,float型的比较。
- int main(int argc, char **argv)
- {
- int ia = 1, ib = 2;
- double da = 1, db = 2;
- float fa = 1, fb = 2;
-
- cout << my_max(ia, ib) << endl;
- cout << my_max(da, db) << endl;
- cout << my_max(fa, fb) << endl;
-
- return 0;
- }
重新编译测试:

此时,再查看一下max文件的大小:

文件大小从9440B增加到了13880B,增加了 13880 - 9440 = 4440B,只是增加了几个变量和函数调用,为什么文件大小会有这么大的变化呢?
这就和函数模板的内部机制有关了。
编译器在编译代码时,实际上不会编译函数模板(函数模板不是函数,而是编译指令),而是根据代码的需要,选择使用哪个函数模板生成对应的函数。
当main函数中只需要比较 int 型的my_max函数时,编译器根据函数模板在代码中生成 int 型的my_max函数;同理,需要double型和float型的比较函数时,也会对应生成这两个类型的比较函数。
也就是说,表面上看是多定义了几个变量,加了几个函数调用,实际上编译器编译时会多出两个函数定义。
之前定义的函数模板中,要求传入的两个参数a和b的类型相同,都为T。
- template
- T &my_max(T &a, T &b)
- {
- cout << __PRETTY_FUNCTION__ << endl;
- return ((a < b) ? b : a);
- }
那么,如果传入的参数类型不同会怎么样?
将b的类型修改为const int,a的类型为int。
- int main(int argc, char **argv)
- {
- int ia = 1;
- const int ib = 2;
-
- cout << my_max(ia, ib) << endl;
-
- return 0;
- }
此时编译会有报错,报错原因编译器找不到声明为my_max(int&, const int&)的函数声明,也就是无法从函数模板中生成对应的函数。

将a的类型也改为const int。
- int main(int argc, char **argv)
- {
- const int ia = 1;
- const int ib = 2;
-
- cout << my_max(ia, ib) << endl;
-
- return 0;
- }
此时编译测试可以成功。

也就是说,函数模板对于模板类型的选择是有严格要求的,必须要与模板定义中保持一致。
对于普通的函数调用,编译器支持很多种隐式类型转换,但是对于函数模板,则只支持两种隐式类型转换:
修改代码,将函数模板传参和返回值的类型都改为const。

传入两个int类型的参数。

此时编译执行正常。

也就是说,非const类型的参数可以转换为const类型的参数。
相当于权限从可读可写变为了只读。
注意:const 类型的参数不能转换为非 const 类型,即只读的变量不能转换为可读可写的变量。
修改代码,传入两个字符数组。

编译执行,可以看到T = char[3]。

增加一个my_max2函数,将传参和返回值的类型从引用改为指针。

编译执行,可以看到 T 被转换成了char。

继续修改main函数,将a改为“abc”。

此时编译会报错,因为T不能同时等于char [4]和char [3]。

将my_max函数的调用屏蔽,则可以编译成功。

因为my_max2中T是指针,推导的到的是char类型的指针。

修改代码,重新创建一个函数模板。

增加一个测试函数func1。

然后修改main函数。

编译测试,发现无论是传入函数名,还是函数的地址,T的类型都是一样的。

也就是说,传入函数名字时,会隐式的转化为函数的指针。