目录
C角度引入:
首先在进行C++讲解之前我们照例先来谈谈C语言是如何做的?
在C语言中是没有函数重载存在的,每一个函数名对应的函数只允许有一种具体的实现,这就可能会导致某些功能极其相似的函数存在命名重复的问题,给程序员增加起名字,记名字的负担。
既然有上述如此明显的弊端,为什么C语言不支持函数重载呢?
答案是C语言是一门极致追求效率的语言,支持函数重载需要编译器对函数名称执行名称修改以包含有关参数类型的信息。但其实这一点点时间的耗损是可以忽略不计的,也因此在C++中正式引入了函数重载。
函数重载:
1. 什么是函数重载:
在同一个作用域中,如果有多个函数的名字相同,但是形参列表不同(参数类型不同或参数个数不同),返回值类型可同也可不同,我们称之为函数重载。
最简单的对于int add(int,int) int add(float,float)就是两个因参数不同而函数重载最典型的例子。
2.函数重载的优点:
- 函数重载的主要优点是它提高了代码的可读性并允许代码重用。
- 使用函数重载是为了节省内存空间、一致性和可读性。
- 它加快了程序的执行速度
- 代码维护也变得容易。
- 函数重载为代码带来了灵活性。
- 该函数可以执行不同的操作,因此无需为同一种操作使用不同的函数名称。
3.函数重载的特性:
1.仅返回值不同的函数不能发生函数重载
2.带缺省参数的部分函数不能发生函数重载(仅在函数调用时会发生二义性情况下)
剖析函数重载:
简单介绍完函数重载的概念,优点及特性后,才正式进入到今天这篇博客的核心。
函数重载是如何实现的?为什么C语言底层不支持函数重载?
在回答这个问题之前,我们首先需要对符号表有一个大致的了解。
在计算机科学中,符号表是一种用于语言翻译器(例如编译器和解释器)中的数据结构 。 在符号表中, 程序源代码中的每个标识符都和它的声明或使用信息绑定在一起 ,比如其数据类型、作用域以及内存地址。
目标文件中通常会有一个包含了所有外部可见标识符的符号表,在链接不同的目标文件时,链接器会使用这些文件中的符号表来解析所有未解析的符号引用。
形象化的来说,实际项目通常是由多个头文件和多个源文件构成,我们可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?
所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起。那么链接时,面对Add函数,链接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则。由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们将使用g++演示了这个修饰后的名字。
言归正传, 那么为什么会发生函数重载的答案也就显而易见。
答案是在符号表中记录的不同。
那么究竟是如何记录的呢?
gcc 的函数修饰后名字不变,而 g++ 的函数修饰后变成[ _Z+ 函数长度 + 函数名 + 类型首字母 ]采用C语言编译器编译的结果:
采用C++编译器编译的结果:
因此我们发现:
在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。
在 linux 下,采用 g++ 编译完成后,函数名字的修饰发生改变,编译器将函数参 数类型信息添加到修改后的名字中。通过这里我们就可以理解 C 语言为什么不支持重载,因为同名函数没办法区分;而 C++ 是通过函数修 饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
最后做一个小补充(大厂面试题):
我们既然说因为函数名修饰不同而导致重载的发生,如果在g++在修饰规则中加入对返回类型的修饰,是不是可以因返回值类型发生重载呢?
答案是不可以!! !因为仍旧会发生匹配重复,你在调用的时候编译器是不知道你需要调用哪一个函数,尽管这些函数名并不相同。形象化的来说,现在你需要你同学的一本书,有两个不同的同学,一个高,一个矮,同时叫小明,高个字拥有书的名称为《超等数学》,矮个子为《高等数学》。如果说需要小明的书?这就是C语言中的不支持函数重载。如果说需要名称为小明且高个子的书?这就是C++中的函数重载。但如果说需要小明的《超等数学》?这就无法发生重载,尽管两个小明并不相同,但你不会知道哪个小明有这本书,不知道该向谁去借。