目录
一. 概念
二. 名字修饰
三.extern "C"
1. C++程序调用静态库里的C语言程序
2. C语言程序调用静态库里的C++程序
一. 概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或参数类型或参数类型的顺序)必须不同,常用来处理实现功能类似数据类型不同的问题
int Add(int left, int right)
{
return left+right;
}
double Add(double left, double right)
{
return left+right;
}
long Add(long left, long right)
{
return left+right;
}
int main()
{
Add(10, 20);
Add(10.0, 20.0);
Add(10L, 20L);
return 0;
}
与返回类型无关,以下这个代码就是典型的不是函数重载!
short Add(short left, short right)
{
return left+right;
}
int Add(short left, short right)
{
return left+right;
}
二. 名字修饰
首先要知道:在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
这里需要知道编译链接的具体过程每个过程做的处理,(建议复习一下再继续往下)这里的具体内容在预处理的那篇博客C语言程序环境与预处理
首先来看C语言程序中如果有函数重载会怎么样(为了更好理解在Linux系统下操作)
使用以下命名行,进行编译查看结果:
(gcc是C语言编译器,-o是编译指令,tc是编译通过后生成的文件名,test.c和f.c是被编译的两个文件)
gcc -o tc test.c f.c
这里发现,编译报错,不通过,说明C语言并不支持函数重载 !
解下来看看C++程序中的函数重载:
以上文件内容是一致的,但是除了头文件的后缀一致以外,其他两个文件后缀不一致,C语言文件后缀是.c,C++文件后缀是.cpp
使用以下命令行进行编译:
(g++是C++编译器,-o是编译指令,tcpp是编译通过以后生成的文件名,test.cpp和f.cpp是需要编译的C++文件)
g++ -o tcpp test.cpp f.cpp
以下是编译结果:
没有报错说明编译通过了!C++支持函数函数重载,并在改目录文件下生成了tcpp文件
通过以下指令,运行该文件,发现可以运行
./tcpp
通过以上程序知道了C++是支持函数重载而C语言不支持函数重载,那么,接下来是两个个很重要的问题!
为什么C++支持函数重载,而C语言不支持函数重载呢?C++又是如何支持的?
我们的项目通常是由多个头文件和多个源文件构成,当test.cpp中调用了f.cpp中定义的f函数时,编译后链接前,test.o的目标文件中没有f的函数地址,因为f是在f.cpp中定义的,所以f的地址在f.o中,但是test.o里有f的声明和调用,所以test.o里的f函数也有自己的地址,但是两者的地址并不一样(无论是编译还是汇编都是一个文件单独进行的)。链接阶段就是专门处理这种问题,链接器看到test.o调用f,但是test.o的符号表里没有f的地址(指的是定义里f函数的地址),就会到f.o的符号表中找f的地址,然后链接到一起。
编译时,C语言中没有对函数名进行处理,两个函数名相同,而符号汇总以后会生成符号表,在符号汇总时两个函数名一致,此时形成符号表是会出现冲突的,编译器报错。所以,由此可以猜想,在C++中支持函数重载的原因是对函数名进行了处理。
虽然没直接查看符号表但是可以通过以下指令在Linux系统上查看指令集
以下是查看C++的指令集:
objdump -S tcpp
使用该指令以后,找到两个函数的指令集:
会发现,两个函数的函数名果然不一致,在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。
以下是查看C语言的指令集:
objdump -S tc
指令集的结果如下:
会发现函数名和我们输入的函数名一致,没有改变,说明在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。
这就证明了,在C语言中,编译前并不会对函数名进行处理,导致了C语言并不支持函数重载,而C++对函数名进行了处理,支持函数重载
由此也能看出:g++编译的函数修饰后变成【_Z+函数名长度+函数名+类型首字母】
(每个编译器都有自己的函数名修饰规则)
如果想知道windows下对函数名的修饰,参考如下图:
可以看得出来非常复杂,想知道更多的C/C++函数调用约定和名字修饰规则:
C++的函数重载
C/C++ 函数调用约定
所以,我们就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区 分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
值得一提的是:也从这里知道了为什么函数重载要求参数不同!而跟返回值没关系。
三.extern "C"
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern "C",意思是告诉编译器,将该函数按照C语言规则来编译。
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern "C",意思是告诉编译器,将该函数按照C语言规则来编译。
首先看这个程序:
其中的f是C++实现的,并且导入到当前这个C语言程序中,运行以后报错
int f(int t1, int t2);
int main()
{
f(1,2);
return 0;
}
链接时报错:error LNK2019: 无法解析的外部符号_Add,该符号在函数 _main 中被引用
我们知道,C语言程序调用C语言程序的库,C++程序调用C++的库是没问题的,那么C语言程序掉i用C++语言的库,C++程序调用C语言的库可以吗?可能会想到他们函数名的修饰都不一样,应该不能调用吧,事实上,是可以调用的。
首先就是C++调用C语言程序是比较简单的,有extern "C",在上述程序中加入这个以后,就不会报错了
extern "C" int f(int t1, int t2);
int main()
{
f(1,2);
return 0;
}
- 静态库的封装
为了更方便查看两者之间的调用关系,先将一个项目封装成静态库,配置方法如下:
最后应用和确定,这就将一个项目配置成静态库
- 导入静态库
以下是使用静态库的方法:
注意:后缀也需要
添加以后的结果如下:
最后点击应用和确定就配置好了
- C语言程序和C++程序的交叉调用
1. C++程序调用静态库里的C语言程序
以上操作做完后,此时运行程序,就会出现以下情况:
这是因为,虽然我们有了函数的声明,但是我们静态库里的程序是C语言程序,而我们引用的头文件是以C++的语法进行编译,我们现在使用的是C++的程序,由于声明是C++里的符号表,而定义却是C语言的符号表,根据上面函数的重载可知两者函数的名是不一样的,所以编译器去符号表里找不到,就导致链接错误
但是由于C++兼容C,所以只要在引用头文件的地方加上extern "C" 就行了,可以让头文件的编译的方式以C语言的方式编译,那声明的符号表里的函数名就和定义的符号表的函数就一致了,这样链接就通过了
(这里需要括号的原因是头文件编译后会展开头文件里的东西,一般会有很多行,我们是要头文件里的东西都以C语言的语法编译)
这是处理当C++的程序要调用C语言的程序的方法
2. C语言程序调用静态库里的C++程序
那么,如果我们要处理C语言调用C++程序怎么办?
这里先按上面的步骤创建再创建一个C++程序的静态库
然后让C语言程序调用C++的静态库,就会出现以下问题
因为我们虽然引用了头文件,但是是以C语言的语法进行编译,而库里的程序是C++的程序,和上面一样,声明和定义的符号表里的函数名不一致,C语言没有进行修饰而C++的函数进行了修饰,导致符号表合并时找不到函数的定义,就出现链接时错误
这个时候之前学的条件编译就派上用场了,可以在以下链接进行复习
C语言之程序环境和预处理
(之前C++程序调用C语言程序在C++程序上动手,这次C++程序调用C语言程序一样在C++程序动手)
对C++的头文件进行修改:
或者采用下面这种也行,但是更推荐上面那种,更方便
如上图,我们使用条件编译,让extern "C" 在C++的程序中得到编译,而C语言程序中不被编译,这样在定义的静态库文件里通过extern "C" 让C++程序以C语言语法编译,而在C语言程序里则不出现extern "C" ,并且C语言还是以C语言的语法编译
(extern "C"关键字是C++里特有的语法,C语言没有,extern "C" 能让C++程序以C语言的语法编译,如果该代码在C语言程序中出现则会报错;__cplusplus关键字是C++语法里特有的,在这里是告诉编译器在C++的程序中编译该条件里的代码,否则不执行该代码)
注意一个非常重要的问题,如果C语言程序想要调用C++的程序,C++的程序里不能出现函数重载,因为无论是C++程序的定义还是C语言的调用,都采用的C语言语法编译的,而C语言不支持函数重载



