目录
一、函数重载
????????1、函数重载概念
????????2、函数重载注意点
????????3、问题:为何C语言不支持函数重载,反倒C++可以?
??????????????Linux环境下演示函数重载
??????????????回顾程序的编译链接
??????????????采用C语言编译器编译后结果
??????????????????????gcc的函数名修饰规则
??????????????采用C++编译器编译后结果
??????????????????????g++的函数名修饰规则
??????????????结论
二、extern"C"
????????1、C++如何调用C的静态库
??????????????建立C的静态库
??????????????在C++工程里配置链接C的静态库目录
??????????????调用过程
????????2、C如何调C++的静态库
??????????????建立C++的静态库
??????????????在C工程里配置链接C++的静态库目录
??????????????调用过程
一、函数重载
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!”
1、函数重载概念
函数重载:是函数的一种特殊情况。C语言不支持函数重载,而C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题
2、函数重载注意点
仅仅修改函数返回类型不是函数重载
因为无法区分你要调用的是谁
3、问题:为何C语言不支持函数重载,反倒C++可以?
有了函数重载,确实要比不支持函数重载的C语言上方便了许多,这难免会有人提问到:
- 既然函数重载这么好,为何C语言就不行呢?
- C++又是如何支持函数重载的呢?
接下来,我就展开来讨论下:首先,为了更好的显现出其具体操作过程,我将在Linux的环境下向大家展示其具体过程。其次,解释其原理需要借助我们之前讲解过的程序的编译链接,所以下文我也会带着再简要讲解下。所以,正文开始:
Linux环境下演示函数重载
首先,我们在Linux环境下创建3个目录:f.h、f.c、test.c。来分别进行声明、定义、实现。
注意后缀,都是以.c命名,这说明以下操作是在C语言的情况下进行的
并且对上述代码编译运行后没有错误,接下来用gcc编译对它生成tc可执行程序,用g++编译对它生成tcpp可执行程序,并且两个文件编译运行均没错误
接下来,我们在原有文件的基础上再写一个函数来确保其是函数重载?
?此时我们用gcc对它进行编译:
很明显发生错误,再强调下,上述操作是在C语言的基础上完成的。这就足矣说明,C语言是不支持函数重载的,想要搞清楚原因前,就要先明白程序的编译链接,看下文:
回顾程序的编译链接
程序的编译链接我在曾经的一篇博文中已详细讲解过,这里直接给出链接:
程序的环境
针对上述的三个目录文件:f.h、f.c、test.c,接下来展开讨论:
程序的编译链接分为四大过程:
- 预处理 -- 头文件展开、宏替换、条件编译、去掉注释。预处理后生成f.i和test.i文件
- 编译 -- 检查语法,生成汇编代码。编译后生成f.s和test.s文件
- 汇编 -- 把汇编代码转换成二进制的机器码。汇编后生成f.o和test.o文件
- 链接 --?合并段表、符号表的合并和符号表的重定位。通俗讲就是找调用函数的地址,链接对应上,合并到一起
看图:
- ?其次编译,会生成符号表(记录的是函数定义和函数地址的映射)以及函数调用指令
这里生成了main函数的指令,其中有f,因为还不知道确切的地址,只是有声明,所以先用?表示,接着进入链接,找调用函数的地址,链接对应上,并合并到一起?。随后就在编译形成的符号表里寻找与main函数指令相同的函数名,并找到其地址
采用C语言编译器编译后结果
我们采用如下指令进行编译:
结果如下:
gcc的函数名修饰规则
有没有发现直接以函数名命名,没有任何其它的修饰,这么做也就注定造成了出现多个相同函数名的时候,在链接时call不知道链接哪个,因为函数名都是一样的,找不到其地址,这也就说明了C语言不支持函数重载,其链接过程的图示和上述图示一样:
采用C++编译器编译后结果
我们采用如下指令编译:
结果如下:
?
g++的函数名修饰规则
仔细观察C++版本的汇编指令,观察两个不同函数的函数名修饰样式:
- 一个是<_Z1fid>:
- 另一个是<_Z1fdi>:
有没有发现它把参数类型的首字母带进去了,那也就意味着你的参数的类型不同,个数不同,参数顺序不同都会导致函数名不同
这个时候,C++编译后生成的符号表里以及链接时函数调用指令应该是这个样子:
这个时候,C++在链接的过程中,call找的就是其修饰后的函数名,函数名不同,自然不会出错,这就是C++支持函数重载的核心所在,而C语言的函数命名规则是根据函数名设定的,函数名相同的话,链接就会出错,找不到确切地址,自然不会支持函数重载
_Z 函数名长度 函数名 类型首字母
而返回值的不同并不会影响到函数名的修饰规则,这也就是为什么前面强调的函数返回类型不同不支持函数重载
结论
C++支持函数重载而C语言不支持是因为函数在内存中的存储方式不相同,C语言是直接以函数名修饰,而C++是_Z 函数名长度 函数名 类型首字母,导致C++支持重载,而C语言不支持重载。
二、extern"C"
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern "C",意思是告诉编译器,将该函数按照C语言规则来编译。比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()和tcfree两个接口来使用,但如果是C项目就没办法使用,那么他就使用extern “C”来解决。
接下来,我就向大家展示下如何在C++和C之间互相调用:
1、C++如何调用C的静态库
我们以之前写过的队列为例,来演示:
建立C的静态库
首先,把我们之前写好的栈拷贝到新项目来:
此时编译运行是不通过的(没有调用main函数接口),接下来将其改成静态库试试:
此时编译运行,生成.lib后缀的文件
现在有一个C++的项目,想要调用刚才C语言的静态库,如下:
以括号匹配这道题为例,解决这道题需要用到栈的思想。
?现在C++的项目已经创建完毕,该到了调用的时刻了,看下文:
在C++工程里配置链接C的静态库目录
首先,最基本的我要先包头文件,但是自己创建的这个项目工程里并没有栈.h文件,所以要在文件目录的上层寻找:
此时编译之后就会看到,编译没有问题,但是运行会报一堆错误:
为什么会出现运行错误呢?
就是因为我们对C配置了静态库,现在对这个C++工程也要配置下,如下:
首先:
其次:
随后,把附加库目录生成的.lib文件名字放到如图所示位置:
此时链接器链接就会链接到它的静态库
此时再编译运行,发现依旧会出错
?但是当我把Stack.c的后缀改为cpp时
此时再运行看看:
此时编译运行通过了,为什么把Stack_C的后缀改为.cpp就可以通过呢?
这里就牵扯到上文谈到的C++和C在汇编中不同的函数名修饰规则了,在C语言中,只有函数名,可是C++有函数类型个数什么的,用原先.c后缀的话就会导致链接出错,改为后缀.cpp就实现了C++调C++,就没有问题了。
见下文:
调用过程
此时就要用到我们的extern"C"。要知道C++是兼容C的,它认识C语言的命名规则,
加上extern"C"后,我Stack.h声明的这些东西,都会展开在extern"C"这个括号里面,核心作用就是告诉编译器,extern "C" 声明的函数,是C库,要用C的方式链接调用
此时我再运行下看看:
此时发现就没有任何问题了,这就成功实现了C++调用C。?
2、C如何调C++的静态库
实现了C++调C,接下来实现下C调C++。
建立C++的静态库
老样子,依旧是新建一个空工程,还是以栈为例,把原先写的栈拷贝到新工程,不过此工程是封给C++的,这里我们要先把此工程配置为静态库。
首先?
其次,改为静态库:
此时编译运行:
可以发现在如下的目录下生成.lib后缀的文件
?此时配置静态库就完成了。
在C工程里配置链接C++的静态库目录
接着,创建一个C的项目:
第一步依旧是把头文件包上,依然是去上层目录寻找:
此时你会发现,编译没有错误,其实这里的编译是有问题的,这里链接的其实是C的库,这个时候是C调C,这里我们需要重新配置下链接库目录。
随后,再把Stack_CPP.lib拷贝到附加依赖项的前面:
此时编译运行依旧会报错
此时就是链接错误
依旧是跟C++的函数名修饰规则有关,C语言是没有修饰的,而C++是修饰过的,这里当然会出现链接错误。
这里的解决方案也并不是像C++那样仅仅加个extern"C"就可以解决的,因为extern"C"只是在C++支持,C语言不支持。
具体如何操作呢?只需要在extern"C"的基础上加上条件编译即可解决,具体过程见下文:
调用过程
这里我们针对Stack_CPP静态库进行修改,这里有两种方法:
- 法一:在Stack.h文件声明的所有函数前加上extern"C"
在C++这个工程里面对每一个声明加上extern "C"是为了告诉C++这些函数要用C的方式去编译,此时我到C工程项目里面去编译运行:
发现又出错了,这又是为什么呢?(头文件展开出错)
很简单:在这个C的项目前面我们包了头文件Stack.h,包上的这个头文件中里面就加上了我们先前的extern "C",此时出错不理所应当,因为C语言不支持extern"C"
此时我们针对C++工程巧用条件编译:
解释下此图红框框里的意思:
如若满足C++的标准,那么就把EXTERN_C替换成extern"C",让其在C++工程中将这些函数用C语言的标准去访问,如若不满足C++标准,那么就把EXTERN_C看为空,啥也没有,这样在C项目工程那链接的时候,根本不会出现EXTERN_C,又满足了链接要求
我们在C项目工程里编译运行看看:
编译运行成功了,是不是非常神奇。 不得不在这感慨一句条件编译学到现在没想到是在C调C++的时候体现出来了。
当然其实这里还有一种更为简洁的写法,看下文:
为了避免重复写EXTERN_C,我们可以这样:
先在C++项目工程里面把声明的函数用extern"C"整体包起来:
其次,加上条件编译:
再对C项目工程编译运行:
此时编译运行同样是没有任何错误。
此时C调C++就结束了,不得不再次感慨条件编译牛皮!!
总结
本篇博文旨在强调了函数重载以及其内部原理,详解了为何C++支持函数重载,而C不支持。
此博文另一核心知识点旨在讲述了C++和C之间如何实现互相调用
创作不易、还望给个三连哈~
|