一、C++之函数模板
1.1、何为函数模板
在引出这个概念之前,我们先来看一段例子:
#include <iostream>
using namespace std;
void my_swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
void my_swap(double &a, double &b)
{
double temp = a;
a = b;
b = temp;
}
int main(void)
{
int a = 10;
int b = 20;
double da = 1.34;
double db = 6.9;
my_swap(a,b);
cout << "a = " << a << " b = " << b << endl;
my_swap(da, db);
cout << "da = " << da << " db = " << db << endl;
return 0;
}
这个例子很简单,主要是对函数my_swap的重载来实现不同类型的交换。
运行结果:
经过这个例子我不禁在想,如果我出现一百个这种类型的函数重载,但是到最后我发现这种实现方法某个地方出现问题,那么我岂不是要将这一百个的函数全部修改?
那工作量就不是那么简单了,那有没有什么办法可以解决这个问题?
我打个比方,我们制作手机外壳,先是有手机外壳模子,然后根据模子的形状来生产最终的手机壳,哪怕我最终发现手机壳有问题,我只需要修改手机壳的模子,后面生产就没必要对每个手机壳进行修改。
同理函数是否会有一个模板,后面的函数实现是根据这个模板来实现。
这就是我今天想要引出来的函数模板概念。
格式如下:
所以我将上面的那个例子修改一下。
#include <iostream>
using namespace std;
template<class T>
void my_swap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
int main(void)
{
int a = 10;
int b = 20;
double da = 1.34;
double db = 6.9;
my_swap(a, b);
cout << "a = " << a << " b = " << b << endl;
my_swap(da, db);
cout << "da = " << da << " db = " << db << endl;
return 0;
}
其中最终要得是这个模板函数:
template<class T>
void my_swap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
以下几点注意点:
1、格式使用:
template<class T>
也可以改成这样:
template<typename T>
这两种那种都可以,根据自己喜欢来即可。
2、模板作用区域
当我们使用template定义后,紧跟随的第一个函数才是模板函数,后面将不在是。
除非你在前面再加上模板的定义。
所以总结一下模板的概念:
1、template是语义是模板的意思,尖括号中先写关键字typename或是class,后面跟一个类型T,此类即是虚拟的类型。至于为什么用T,用的人多了,也就是T了。
2、调用过程是这样的,先将函数模板实再化为函数,然后再发生函数调用,所以会出现两次编译。
1.2、函数模板的显示和自动类型转换
前面的那个例子中,我们并未指明T的类型,但是系统却能正确调用,所以这里存在一个自动转换的过程,也就是编译器会根据你传进来的数据类型进行处理。
当我们需要指明的时候怎么定义呢?
my_swap<int>(a, b);
cout << "a = " << a << " b = " << b << endl;
my_swap<double>(da, db);
只需要加个<>里面填充你想要将T转换的类型。
1.3、函数模板和函数重载
在函数模板和函数重载定义在一起的时候,我们需要特别注意:
普通函数会进行隐士的数据类型转换, 函数模板不提供隐式的数据类型转换必须是严格的匹配,所以你想要执行函数模板生成的函数的话,那么必须严格的匹配,否则他会优先调用普通函数。
我们看一下这部分代码:
普通函数是可以进行隐式转换的,但是函数模板直接报错了,因为他报错无法匹配和模板函数相同的参数列表,所以直接报错。
我们调试一下,发现在重载函数和模板函数参数列表都满足的情况下,他会优先调用普通函数,而不是函数模板。
这时候我们修改重载函数的里面的参数列表由之前的int类型变为char,这时候,唯一能够满足参数列表的只有函数模板了,但是发现他还是调用普通函数,也就是说,不管是隐式的转换(重载),还是显示的,只要能满足调取普通函数的,就优先调取普通函数,而不是函数模板。
这时候我将重载函数的参数列表修改为隐式也无法转换,这时候才会发现他调用函数模板了,所以在使用函数模板和函数重载时候需要注意一下他们调用的顺序。
1.4、函数模板的编译原理
1、浅析编译过程原理
这是在linux下的编译过程:
g++ -E hello.c -o hello.i(预处理)
g++ -S hello.i -o hello.s(编译)
g++ -c hello.s -o hello.o(汇编)
g++ hello.o -o hello(链接)
当然,以上四个步骤,可合成一个步骤:
g++ hello.c -o hello(直接编译链接成可执行目标文件)
打开我们电脑的虚拟机里面的Ubuntu(没有的同学可以自己安装哈),写下面的代码:
然后我们将他编译成汇编文件,直接使用这个命令:
g++ -S test.cpp -o test.s
然后查看一下里面关于函数模板的使用。
我们找到main函数的地方,然后看到调取函数的两个地方,我不是很懂汇编,但是看到call就是调取的意思,然后我们看一下这两个函数,他们唯一区别的就是一个是i,一个是d,其实也好理解,一个是整形,一个是double型。
然后我们在增加一对int的来看一下他是否会创建第三个函数:
我们发现他们是一样的,所以可以得到一个结论,只有形参列表不同才会创建新的函数专门处理,而不是传递一个参数就是用一个函数。
最后我们来看一张对比图:
这时候我们回到刚开是的那个问题,如果我有一百个函数使用该函数模板,如果那天我发现出现问题了,只需要修改函数模板即可,而不是去修改一百个函数,这只是函数模板的一个好处,还有别的好处,遇到在总结了。