【 C++入门 】函数重载、extern“C“

目录

一、函数重载

        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++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数类型 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题

  • 参数类型不同:

【 C++入门 】函数重载、extern“C“_第1张图片

  • 参数个数不同:

【 C++入门 】函数重载、extern“C“_第2张图片

  • 顺序不同 

【 C++入门 】函数重载、extern“C“_第3张图片

2、函数重载注意点

仅仅修改函数返回类型不是函数重载

因为无法区分你要调用的是谁

【 C++入门 】函数重载、extern“C“_第4张图片

3、问题:为何C语言不支持函数重载,反倒C++可以?

有了函数重载,确实要比不支持函数重载的C语言上方便了许多,这难免会有人提问到:

  1. 既然函数重载这么好,为何C语言就不行呢?
  2. C++又是如何支持函数重载的呢?

接下来,我就展开来讨论下:首先,为了更好的显现出其具体操作过程,我将在Linux的环境下向大家展示其具体过程。其次,解释其原理需要借助我们之前讲解过的程序的编译链接,所以下文我也会带着再简要讲解下。所以,正文开始:

Linux环境下演示函数重载

  • gcc编译 -- C语言版本

首先,我们在Linux环境下创建3个目录:f.hf.ctest.c。来分别进行声明、定义、实现。

注意后缀,都是以.c命名,这说明以下操作是在C语言的情况下进行的

【 C++入门 】函数重载、extern“C“_第5张图片

并且对上述代码编译运行后没有错误,接下来用gcc编译对它生成tc可执行程序,用g++编译对它生成tcpp可执行程序,并且两个文件编译运行均没错误

【 C++入门 】函数重载、extern“C“_第6张图片

接下来,我们在原有文件的基础上再写一个函数来确保其是函数重载 

【 C++入门 】函数重载、extern“C“_第7张图片

 此时我们用gcc对它进行编译:

【 C++入门 】函数重载、extern“C“_第8张图片

很明显发生错误,再强调下,上述操作是在C语言的基础上完成的。这就足矣说明,C语言是不支持函数重载的,想要搞清楚原因前,就要先明白程序的编译链接,看下文:

回顾程序的编译链接

程序的编译链接我在曾经的一篇博文中已详细讲解过,这里直接给出链接:

程序的环境

针对上述的三个目录文件:f.hf.ctest.c,接下来展开讨论:

程序的编译链接分为四大过程:

  1. 预处理 -- 头文件展开、宏替换、条件编译、去掉注释。预处理后生成f.itest.i文件
  2. 编译 -- 检查语法,生成汇编代码。编译后生成f.stest.s文件
  3. 汇编 -- 把汇编代码转换成二进制的机器码。汇编后生成f.otest.o文件
  4. 链接 -- 合并段表、符号表的合并和符号表的重定位。通俗讲就是找调用函数的地址,链接对应上,合并到一起

看图:

  • 首先预处理:

【 C++入门 】函数重载、extern“C“_第9张图片

  •  其次编译,会生成符号表(记录的是函数定义和函数地址的映射)以及函数调用指令

【 C++入门 】函数重载、extern“C“_第10张图片

这里生成了main函数的指令,其中有f,因为还不知道确切的地址,只是有声明,所以先用?表示,接着进入链接,找调用函数的地址,链接对应上,并合并到一起 。随后就在编译形成的符号表里寻找与main函数指令相同的函数名,并找到其地址

采用C语言编译器编译后结果

我们采用如下指令进行编译:

【 C++入门 】函数重载、extern“C“_第11张图片

结果如下:

【 C++入门 】函数重载、extern“C“_第12张图片

gcc的函数名修饰规则

有没有发现直接以函数名命名,没有任何其它的修饰,这么做也就注定造成了出现多个相同函数名的时候,在链接时call不知道链接哪个,因为函数名都是一样的,找不到其地址,这也就说明了C语言不支持函数重载,其链接过程的图示和上述图示一样:

【 C++入门 】函数重载、extern“C“_第13张图片

采用C++编译器编译后结果

我们采用如下指令编译:

【 C++入门 】函数重载、extern“C“_第14张图片

结果如下:

  • 函数一:

【 C++入门 】函数重载、extern“C“_第15张图片

  • 函数二:

 【 C++入门 】函数重载、extern“C“_第16张图片

g++的函数名修饰规则

仔细观察C++版本的汇编指令,观察两个不同函数的函数名修饰样式:

  • 一个是<_Z1fid>:
  • 另一个是<_Z1fdi>:

有没有发现它把参数类型的首字母带进去了,那也就意味着你的参数的类型不同,个数不同,参数顺序不同都会导致函数名不同

这个时候,C++编译后生成的符号表里以及链接时函数调用指令应该是这个样子:

【 C++入门 】函数重载、extern“C“_第17张图片

这个时候,C++在链接的过程中,call找的就是其修饰后的函数名,函数名不同,自然不会出错,这就是C++支持函数重载的核心所在而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的静态库

首先,把我们之前写好的栈拷贝到新项目来:

【 C++入门 】函数重载、extern“C“_第18张图片

此时编译运行是不通过的(没有调用main函数接口),接下来将其改成静态库试试:

  • 1、右键属性

【 C++入门 】函数重载、extern“C“_第19张图片

  • 单击配置类型更改为静态库

【 C++入门 】函数重载、extern“C“_第20张图片此时编译运行,生成.lib后缀的文件

【 C++入门 】函数重载、extern“C“_第21张图片

【 C++入门 】函数重载、extern“C“_第22张图片现在有一个C++的项目,想要调用刚才C语言的静态库,如下:

以括号匹配这道题为例,解决这道题需要用到栈的思想。

【 C++入门 】函数重载、extern“C“_第23张图片

 现在C++的项目已经创建完毕,该到了调用的时刻了,看下文:

在C++工程里配置链接C的静态库目录

首先,最基本的我要先包头文件,但是自己创建的这个项目工程里并没有栈.h文件,所以要在文件目录的上层寻找:

【 C++入门 】函数重载、extern“C“_第24张图片

此时编译之后就会看到,编译没有问题,但是运行会报一堆错误:

【 C++入门 】函数重载、extern“C“_第25张图片

为什么会出现运行错误呢?

就是因为我们对C配置了静态库,现在对这个C++工程也要配置下,如下:

首先:

【 C++入门 】函数重载、extern“C“_第26张图片

其次:

【 C++入门 】函数重载、extern“C“_第27张图片

随后,把附加库目录生成的.lib文件名字放到如图所示位置:

【 C++入门 】函数重载、extern“C“_第28张图片

此时链接器链接就会链接到它的静态库

此时再编译运行,发现依旧会出错

【 C++入门 】函数重载、extern“C“_第29张图片

 但是当我把Stack.c的后缀改为cpp时

【 C++入门 】函数重载、extern“C“_第30张图片

此时再运行看看:

【 C++入门 】函数重载、extern“C“_第31张图片

此时编译运行通过了,为什么把Stack_C的后缀改为.cpp就可以通过呢?

这里就牵扯到上文谈到的C++和C在汇编中不同的函数名修饰规则了,在C语言中,只有函数名,可是C++有函数类型个数什么的,用原先.c后缀的话就会导致链接出错,改为后缀.cpp就实现了C++调C++,就没有问题了。

  • 可现在明确指出要用C++调用C,该如何操作呢?

见下文:

调用过程

此时就要用到我们的extern"C"。要知道C++是兼容C的,它认识C语言的命名规则,

加上extern"C"后,我Stack.h声明的这些东西,都会展开在extern"C"这个括号里面,核心作用就是告诉编译器,extern "C" 声明的函数,是C库,要用C的方式链接调用

【 C++入门 】函数重载、extern“C“_第32张图片

此时我再运行下看看:

【 C++入门 】函数重载、extern“C“_第33张图片

此时发现就没有任何问题了,这就成功实现了C++调用C。 

2、C如何调C++的静态库

实现了C++调C,接下来实现下C调C++。

建立C++的静态库

老样子,依旧是新建一个空工程,还是以栈为例,把原先写的栈拷贝到新工程,不过此工程是封给C++的,这里我们要先把此工程配置为静态库。

【 C++入门 】函数重载、extern“C“_第34张图片首先 

【 C++入门 】函数重载、extern“C“_第35张图片

其次,改为静态库:

【 C++入门 】函数重载、extern“C“_第36张图片

此时编译运行:

【 C++入门 】函数重载、extern“C“_第37张图片

可以发现在如下的目录下生成.lib后缀的文件

【 C++入门 】函数重载、extern“C“_第38张图片

 此时配置静态库就完成了。

在C工程里配置链接C++的静态库目录

接着,创建一个C的项目:

【 C++入门 】函数重载、extern“C“_第39张图片

第一步依旧是把头文件包上,依然是去上层目录寻找:

【 C++入门 】函数重载、extern“C“_第40张图片

此时你会发现,编译没有错误,其实这里的编译是有问题的,这里链接的其实是C的库,这个时候是C调C,这里我们需要重新配置下链接库目录。

【 C++入门 】函数重载、extern“C“_第41张图片

随后,再把Stack_CPP.lib拷贝到附加依赖项的前面:

【 C++入门 】函数重载、extern“C“_第42张图片

此时编译运行依旧会报错

【 C++入门 】函数重载、extern“C“_第43张图片

此时就是链接错误

依旧是跟C++的函数名修饰规则有关,C语言是没有修饰的,而C++是修饰过的,这里当然会出现链接错误。

这里的解决方案也并不是像C++那样仅仅加个extern"C"就可以解决的,因为extern"C"只是在C++支持,C语言不支持。

具体如何操作呢?只需要在extern"C"的基础上加上条件编译即可解决,具体过程见下文:

调用过程

这里我们针对Stack_CPP静态库进行修改,这里有两种方法:

  • 法一:在Stack.h文件声明的所有函数前加上extern"C"

【 C++入门 】函数重载、extern“C“_第44张图片

在C++这个工程里面对每一个声明加上extern "C"是为了告诉C++这些函数要用C的方式去编译,此时我到C工程项目里面去编译运行:

【 C++入门 】函数重载、extern“C“_第45张图片

发现又出错了,这又是为什么呢?(头文件展开出错

很简单:在这个C的项目前面我们包了头文件Stack.h,包上的这个头文件中里面就加上了我们先前的extern "C",此时出错不理所应当,因为C语言不支持extern"C"

此时我们针对C++工程巧用条件编译

【 C++入门 】函数重载、extern“C“_第46张图片

解释下此图红框框里的意思:

如若满足C++的标准,那么就把EXTERN_C替换成extern"C",让其在C++工程中将这些函数用C语言的标准去访问,如若不满足C++标准,那么就把EXTERN_C看为空,啥也没有,这样在C项目工程那链接的时候,根本不会出现EXTERN_C,又满足了链接要求

我们在C项目工程里编译运行看看:

【 C++入门 】函数重载、extern“C“_第47张图片

编译运行成功了,是不是非常神奇。 不得不在这感慨一句条件编译学到现在没想到是在C调C++的时候体现出来了。

当然其实这里还有一种更为简洁的写法,看下文:

  • 法二:

为了避免重复写EXTERN_C,我们可以这样:

先在C++项目工程里面把声明的函数用extern"C"整体包起来:

【 C++入门 】函数重载、extern“C“_第48张图片

其次,加上条件编译:

【 C++入门 】函数重载、extern“C“_第49张图片

再对C项目工程编译运行:

【 C++入门 】函数重载、extern“C“_第50张图片

此时编译运行同样是没有任何错误。

此时C调C++就结束了,不得不再次感慨条件编译牛皮!!


总结

本篇博文旨在强调了函数重载以及其内部原理,详解了为何C++支持函数重载,而C不支持。

此博文另一核心知识点旨在讲述了C++和C之间如何实现互相调用

创作不易、还望给个三连哈~

你可能感兴趣的:(C,plus,plus,c++,函数重载,extern,c)