一、什么是函数重载
有过C语言学习经验的同学都知道,C语言是不支持同名函数出现的,但这样的设计并是不合理的,为什么这么说呢,看下面的例子:
int AddInt(int a, int b)
{
return a + b;
}
double AddDouble(double a, double b)
{
return a + b;
}
为了分别实现整形和浮点型的加法,我们需要创建两个名字不同的函数,可是如果允许函数重载,我们还可以这样写:
int Add(int a, int b)
{
return a + b;
}
double Add(double a, double b)
{
return a + b;
}
【总结】
通过上面的例子,相信大家也理解了什么是函数重载——C++允许在同一作用域中声明几个功能类似的同名函数
,通俗话将就是一词多义。函数重载的出现,统一了功能类似函数的命名,可以极大提高我们我们书写代码的效率。
既然函数名都 “看似相同了”,那么编译器是如何区分这两个重载的函数的呢?依靠的是参数的不同。所以使用重载函数的时候,同名函数的形参列表(参数个数或类型或类型顺序)必须不同
,值得注意的是,这与函数的返回值无关,所以不能指望通过返回值进行区分。
二、底层原理
①为什么C语言不支持函数重载呢?
我们知道,程序在预编译阶段会经历预处理、编译、汇编、链接、生成可执行程序的过程,值得一提的是,在汇编过程中编译器会收集全局符号并生成全局符号表。
那么什么是符号表呢?将符号和其相应地址一一对应的表格称为符号表
。当然在每个文件里都会生成本文件的符号表,对于暂时找不到地址的函数(比如只是一个函数的声明),它的地址是一个没有意义的填充值:
在汇编的过程中我们生成了多个符号表,但最后我们只能有一个符号表,所以在链接过程中要对符号表进行合并。在合并的过程中发现Add函数出现了两次,这就涉及重定位了——Add函数有效的地址值就会作为Add函数最终的地址值。
想必大家理解了为什么C语言中不允许函数重载,因为两个重名函数的地址都是有效值,所以在重定位的时候就会产生冲突和歧义。
②C++是如何支持函数重载的?
上面提到,C语言不支持函数重载的原因是符号表中的出现了两个具有有效地址的同名函数名,所以发生了冲突,那么只要我们能解决函数名冲突的问题,相应的就可以实现函数重载的效果。C++对写入符号表的函数具有一个修正的过程:
void f(int a, double b)
{
printf("%d %lld", a, b)
}
在linux下观察反汇编的效果,发现函数名’f’已经被修正为 ‘_Z1fid’,我们可对Linux下的命名规则做如下总结:
_Z + 函数名长度 + 函数名 + 各个形参类型首字母的小写
(补充:int* 会修正为Pi)
void f(double a, int b)
{
printf("%lld %d", a, b)
}
举一反三,上面函数的函数名就会被修正为 _Z1fdi。既然函数名可以区分开来了,那么实现函数重载就不是问题了。
三、如何保证.c代码和.cpp代码之间的可移植性
毫无疑问,C程序可以调用C的静态/动态库,C++程序可以调用C++的静态/动态库,那么能不能实现交叉调用呢?
这里最大的鸿沟在于C符号表的函数名就是原来的函数名,而C++中的则是经过修饰过的函数名,如果直接用C++程序去调用C的静态/动态库中的函数,是找不到对应函数的。所以在这里,extern C
就派上用场。
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译链接
①C++程序调用C静态/动态库
现在我们用C语言写了一个静态库mylib,用C++程序调用时出现了无法识别的错误,错误的原因在于C++会对符号表中的函数名进行“修正”,从而在链接合并符号表的时候,C++程序中的函数找不到C库中与之同名的函数。
为了解决这个问题,我们在C++程序下,使用 extern C ,它的作用是将它所包含的函数采用C语言的规则进行编译链接,从而不会对函数名进行修正。
extern C 可以包含头文件,也可以包含多个函数声明,实现的功能是一样的
②C程序调用C++静态/动态库
我们仍然可以使用extern “C”吗?很遗憾,这是只有C++才有的语法:
我们仍然想着对.cpp文件动刀——此时我们在即将调用的C++库中加入这样一段条件编译指令:
#ifdef __cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C
#endif
EXTERN_C int add(int a, int b);
代码剖析:
- __cpluscplus是C++项目默认设置的宏定义标识符,我们用它来判断是不是C++程序
- 在C++库中,EXTERN_C被解释成 extern “C” ,也就意味着C++库中函数名不会被修正
- 在C程序中,EXTERN_C被解释为空,不起到任何作用
- 在链接的时候,C程序和C++库中的函数名相同,在合并符号表时就不会出错
- 如果没有条件编译指令,只有 #define EXTERN_C extern “C” ,那么会因为C程序识别不了 extern “C” 而报错
上面的代码还可以这样写:适合函数较多的情况:
#ifdef __cplusplus
extern "C"
{
#endif
int add(int a, int b);
int sub(int a, int b);
int div(int a, int b);
int mul(int a, int b);
#ifdef __cplusplus
}
#endif
- 因为要保证展开的时候 “{” 匹配,所以有两处条件编译指令