初识C++ 函数重载以及背后的原理

盛年不重来,一日难再晨,及时当勉励,岁月不待人。这篇文章主要讲述初识C++ 函数重载以及背后的原理相关的知识,希望能为你提供帮助。
写在前面函数重载我们可能对函数很是熟悉,但是重载又是什么意思呢?我们先来用一个具体的场景来分享.
一天,张三的老板要你写一个两位数相加的函数,张三心想这不很简单吗?手指一动,结果就出来了,挺简单的嘛.
现在老板看张三的代码立马火了,你是怎么想的,要是我想12,10.9相加呢?你这个就只能两个整型相加,回去修改!!!张三听到老板的话不由得反驳道:这怎么改,总不能再写一个add1,add2...吧.老板听到了张三的嘟囔,生气道,你没有学过函数重载吗?看看下面的代码,回去好好学习学习,基础都不扎实.
张三看到代码,不由大吃一惊,C++还可以这么写?好神奇啊,我要好好看看书.
我们可不希望张三这种事发生在我们身上,先来看看函数重载的定义
可能大家不喜欢看定义,我这里给一个总结.
函数重载要满足下面的要求.

  • 函数名相同
  • 参数的类型,个数,顺序有一个不同就可以了
  • 返回类型不做要求
函数重载的原理一般情况下,我们知道了函数重载到会应用就可以了,但是对于我们来说需要我们看看他们的原理,为什么C语言不支持重载,C++支持重载?这些都是问题.
为何C++可以支持重载我们先用C++的编译器简单的看看如何执行程序,下面是我在Linux环境下使用g++来完成的,大家要是不太懂,可以先不管,直接理解C++的原理.
我们先来看看现象,发现C++可以精准的找到需要匹配的函数,这是我们所疑惑的.
// test.h #pragma once #include < iostream> #include < stdio.h> using std::cout; void func(int a, double b); void func(double a, int b); //test.cpp#include "test.h" //写两个函数函数形成重载 void func(int a, double b)printf("%d %lf", a, b); void func(double a, int b)printf("%lf %d", a, b); //Mian.cpp#include "test.h" int main()func(10, 2.20); return 0;

初识C++ 函数重载以及背后的原理

文章图片

初识C++ 函数重载以及背后的原理

文章图片

程序的编译链接关于这一点,我们先简单的说说,之前我们详细的谈过.一个文件变成一个可执行程序需要经过下面4个步骤.
  1. 预处理宏替换头文件展开注释替换main.cpp -> main.itest.cpp-> test.i
  2. 编译检查语法 ,代码变换成汇编语言main.i -> main.stest.i -> test.s
  3. 汇编汇编语言变成二进制语言,各个文件变成目标文件main.s -> main.otest.s -> test.o
  4. 链接多个目标文件+链接库发生链接
链接就仅仅只是目标文件的合并吗?不是的,它要完成的任务很多,其中最重要的就是< font color= red> 找到函数的地址,链接对应上,合并到一起< /font>
当我们进行过头文件的展开后,Main.cpp中有func函数的声明和调用.在编译和汇编的过程中存在一个符号表,这个符号表记录了函数的定义以及相应的映射.这是很重要的.符号表里面包含了函数名和函数的地址.
每一个目标文件(.o)都包含一个符号表和一系列指令,我们看看入和完成函数链接.
初识C++ 函数重载以及背后的原理

文章图片

现在到mian.o的指令这里了,前面的一些列指令都正常经行,直到它遇到了func这个点,要是看过C语言的汇编语言的朋友们可能对下面的比较熟悉.
C语言为何不支持重载到这里就可以明白了,当我们拿函数名去碰的时候,符号表里面存在多个相同的函数名,编译器就不会识别该用哪个.更何况存在相同函数名的.c文件有时都不可能编译过.
gcc对函数名都不会做任何处理,这也是C语言不支持函数重载的原因.
初识C++ 函数重载以及背后的原理

文章图片

C++为何可以支持函数重载到这里我们就可以得到了结果,既然在链接的时候无效的函数会拿函数名去其他的符号表里面去碰,那么只要我们看看重载的函数名像不像同就可以了,大家可能会有些疑惑,重载的函数名不是相同的吗?是的,但是C++编译器会做一定的处理.这里每个编译器都有自己的函数名修饰规则这就是C++ 支持重载的原理.
这就是C++可以支持重载的原因,g++的函数修饰后变成【_Z+函数名长度+函数名+类型首字母1+类型首字母2...】,也是我们只对参数列表做了要求,对返回值不做要求的原因.
初识C++ 函数重载以及背后的原理

文章图片

C++和C语言相互调用我们都知道C++支持C语言的大部分语法,C++和C语言可以相互调用吗?实际上是可以的,在一个大型程序中,有的部门可能使用的是C写的的函数,有的部门可能用的C++,要是他们不能相互使用那就打脸了.
创建静态库我们可以把自己写的代码编译成一个静态库或者动态库,这里我以静态库举例,看看如何在VS中中创建一个静态库.
初识C++ 函数重载以及背后的原理

文章图片

C++调用C我们已经有了一个C语言的静态库,现在有一个C++的项目需要使用这个静态库,我们该如何使用呢?需要分为下面几个步骤
下面这两张图片都是修改环境的设置,我使用的是VS2013,其他的大概应该差不多,大家依次来修改就可以了.
初识C++ 函数重载以及背后的原理

文章图片

初识C++ 函数重载以及背后的原理

文章图片

到这里我们就可以调用C语言的静态库了,让我们来看看结果吧.
#include "../../Heap/Heap/heap.h"//相对路径int main()MyHeap myHeap; InitMyHeap(& myHeap); HeapPush(& myHeap, 1); HeapPush(& myHeap, 2); HeapPush(& myHeap, 3); Display(& myHeap); return 0;

初识C++ 函数重载以及背后的原理

文章图片

extern " C"
既然编译器不能自动识别C语言的函数名,我们告诉编译器一下不就可以了吗.extern " C" 就是这种作用.
有时候在C++工程中可能需要将某些函数按照 C 的风格来编译,在函数前加 extern " C" ,意思是告诉编译器,
将该函数按照 C 语言规则来编译。比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()和tcfree
两个接口来使用,但如果是C项目就没办法使用,那么他就使用extern “C”来解决
extern "C"// 告知这是C语言的函数声明#include "../../Heap/Heap/heap.h"int main()MyHeap myHeap; InitMyHeap(& myHeap); HeapPush(& myHeap, 1); HeapPush(& myHeap, 2); HeapPush(& myHeap, 3); Display(& myHeap); return 0;

初识C++ 函数重载以及背后的原理

文章图片

extern " C" 原理我们需要来看看extern " C" 的原理,使用了extern " C" 后,在C++在进行编译的时候函数名字就依据C语言的方法来修改了,不在变成C++ 的规则.extern " C" 可以单独修饰函数,也可以修饰一系列函数,使用代码块.
// test.h #pragma once #include < iostream> #include < stdio.h> extern "C" void func(int a, double b); //test.cpp#include "test.h" //写两个函数函数形成重载 void func(int a, double b)printf("%d %lf", a, b); //Mian.cpp#include "test.h" int main()func(10, 2.20); return 0;

初识C++ 函数重载以及背后的原理

文章图片

C语言调用C++那么C语言可以调用C++ 的吗?可以了,不过也需要一些段来完成.如何让C语言去识别C++的规则呢?这是我们需要考虑的.
我们已经把库改成的了C++的静态库了.
#include "../../Heap/Heap/heap.h"int main()MyHeap myHeap; InitMyHeap(& myHeap); HeapPush(& myHeap, 1); HeapPush(& myHeap, 2); HeapPush(& myHeap, 3); Display(& myHeap); return 0;

初识C++ 函数重载以及背后的原理

文章图片

我们无法让C语言的编译器去识别C++ 的函数的命名,那么我们是不是可以在函数一编译的时候就完成函数名依照C语言来说.这就很简单了.
初识C++ 函数重载以及背后的原理

文章图片

但是即使是这样,C语言仍旧会报错,原因在于在头文件展开的时候,C语言根本不识别extern " C" ,所以我们就需要条件编译了.
使用条件编译来修改的静态库的方法如下,需要再次编译.
//方法一 #ifdef __cplusplus// C++独有的 #define EXTERNC extern "C" #else #define EXTERNC #endifEXTERNC extern void InitMyHeap(MyHeap * pHeap); EXTERNC extern void HeapPush(MyHeap* pHeap, HPDataType x); EXTERNC extern bool IsFull(MyHeap* pHeap); EXTERNC extern bool IsEmpty(MyHeap* pHeap); EXTERNC extern int HeapSize(MyHeap* pHeap); EXTERNC extern void adjustDown(MyHeap* pHeap); EXTERNC extern void adjustUp(MyHeap* pHeap); EXTERNC extern void Display(MyHeap* pHeap); EXTERNC extern HPDataType HeapTop(MyHeap* pHeap); EXTERNC extern void HeapPop(MyHeap* pHeap); //方法 二 #ifdef __cplusplus extern "C"#endifextern void InitMyHeap(MyHeap * pHeap); extern void HeapPush(MyHeap* pHeap, HPDataType x); extern bool IsFull(MyHeap* pHeap); extern bool IsEmpty(MyHeap* pHeap); extern int HeapSize(MyHeap* pHeap); extern void adjustDown(MyHeap* pHeap); extern void adjustUp(MyHeap* pHeap); extern void Display(MyHeap* pHeap); extern HPDataType HeapTop(MyHeap* pHeap); extern void HeapPop(MyHeap* pHeap); #ifdef __cplusplus#endif

这样就解决了.
初识C++ 函数重载以及背后的原理

文章图片

C++ 注意事项这个注意事项主要是依据extern " C" 来谈的,有些比较偏僻的内容需要关注下.
extern " C" 修饰的函数和一个函数完全一样在extern " C" 修饰的函数模块外面存在了一个完全一摸一样的的函数,这个编译器不会给通过的.
#ifdef __cplusplus extern "C"#endif void func(int a, int b)printf("C : %d %d\\n", a, b); #ifdef __cplusplus#endif //完全一样 void func(int a, int b)printf("C : %d %d\\n", a, b);

初识C++ 函数重载以及背后的原理

文章图片

extern " C" 修饰的函数和一个函数构成重载在extern " C" 修饰的函数模块外面一个函数构成重载这种编译器可以通过的,但是extern " C" 修饰的命名方法仍旧还是按照C语言的方式,构成重载的是C++的方式.
#include < iostream> using namespace std; #ifdef __cplusplus extern "C"#endif void func(int a, int b)printf("C : %d %d\\n", a, b); #ifdef __cplusplus#endif void func(double a, int b)printf("C++: %lf %d\\n", a, b); int main()func(1, 2); func(1.11, 2); return 0;

初识C++ 函数重载以及背后的原理

文章图片

【初识C++ 函数重载以及背后的原理】
初识C++ 函数重载以及背后的原理

文章图片


    推荐阅读