本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、什么是函数重载
有过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 + 函数名长度 + 函数名 + 类型首字母的小写
void f(double a, int b)
{
printf("%lld %d", a, b)
}
举一反三,上面函数的函数名就会被修正为 _Z3fdi。既然函数名可以区分开来了,那么函数重载就不是问题了。
三、如何保证.c代码和.cpp代码之间的可移植性
毫无疑问,C程序可以调用C的静态/动态库,C++程序可以调用C++的静态/动态库,那么能不能实现交叉调用呢?这里最大的鸿沟在于C中符号表的函数名是直接的函数名,而C++中则是经过修饰过的函数名,如果直接用C++去调用C的静态/动态库中的函数,是找不到对应函数的。所以在这里,extern C就派上用场。
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern "C",意思是告诉编译器,将该函数按照C语言规则来编译链接
①C++程序调用C静态/动态库
现在我们用C语言写了一个静态库mylib,用C++程序调用时出现了无法识别的错误,错误的原因在于C++会对符号表中的函数进行“修正”,所以就。我们用extern C来告诉编译器,将该函数按照C语言规则来编译链接,就不会出错。
②C程序调用C++静态/动态库
我们仍然可以使用extern “C”吗?很遗憾,这是只有C++才有的语法:
我们仍然想着对.cpp文件动刀——此时的一个骚操作是条件编译指令:
#ifdef __cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C
#endif
EXTERN_C int add(int a, int b);
代码剖析:
- __cpluscplus是C++项目默认设置的宏定义标识符,我们用它来判断是不是C++程序
- 用C++所写的add函数,在条件编译时,EXTERN_C 展开为extern "C",将代码按照C的风格进行编译链接,所以不会对函数名进行修正
- 我们使用的C程序中,在条件编译时,EXTERN_C 展开为空,所以不会有影响
- 通过条件编译指令,使得C++程序中的函数按照C的方式编译链接,C程序仍然按照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
③我掉过的坑点
在这里不要忘记包含头文件,在将头文件展开的时候会引入extern “C”,将按照C的方式编译链接,否则就起不到相应作用了。