函数重载
c语言函数缺陷
在c语言中,我们要想实现一个计算器,必须要写多个Add函数。
因为在c语言中函数名严格不能相同,这样子我们就需要针对不同的参数类型多次定义,并且在调用中还要分类去调用。但是逻辑需求上这些都是同一个函数。于是为了弥补这个缺点,c++提出了函数重载这一概念
重载
重载就是同一个函数名,有着不同的运行结果。可以理解为在不同情况下调用同一个函数会有不同的结果。
输出结果分别是,3和3.5。从这里可以看出,c++是支持重载的。
重载的规则
重载有3条规则,分别是
- 函数名必须相同
- 函数的形参必须不同
- 函数的返回值类型不要求
函数名必须相同
函数名相同是重载的大前提,我们要想让一个函数在不同情景下有多种结果,那他的必要条件就是这些结果的函数名一定要相同。不然起不到“看情况出结果”这种作用。
形参必须不同
如果函数调用的形参相同的话,我们在传入参数后,编译器在根据参数寻找应该调用哪个函数时,就会出现迷路的情况,两个函数长得一模一样编译器就会报错。
当然如果缺省参数存在的话,他也是不行的。
因为缺省参数本质上是函数传入参数后的一种情况,如果b为空,就把b赋值为a,这两个函数本质上形参还是相同,因此会出现迷路的情况。
这里的形参不同指的是形参的种类和数目至少一个不同即可。
这样子也是可以得到结果的
函数的返回值不要求
函数的返回值不要求指的是重载的函数只看形参和函数名,对于返回值的类型不要求。
违反了形参规则,尽管返回值不同还是报错了。
为什么c语言不支持重载而c++支持(重点)
要弄明白这一点,需要先明白编译链接的过程
编译链接过程
正常情况下一个project里有 f.h, f.cpp,text.cpp两种文件
- 预处理
头文件展开–》宏替换–》条件编译–》去掉注释
生成f.i 和text.i两种文件 - 编译
检查语法,生成汇编代码。当语法没问题的时候,就把他们翻译成cpu能理解的汇编语言(指令)。
生成f.s和text.s
语法检查是在编译的时候进行的
编译后得到的各个.s文件间是不互通的,链接后他们才互通 - 汇编
把汇编代码机器语言(二进制语言)
生成f.o text.o。 - 链接
找到调用函数的地址,链接对应上,合并到一起
生成可执行文件a.out
以这样三个文件为例。
在预处理阶段实际上完成了头文件的展开,相当于把头文件放在cpp文件里。
相当于得到了这么两个文件。f.i和 text.i
之后开始编译,在编译阶段检查语法。 如果语法没有问题就生成f.s和text.s
也就是指令。
注意:!!
前面我们讲过缺省参数要定义要在声明时定义,这时因为在编译时,我们在对main函数检查时,会优先从声明里寻找,看看声明中能不能找到。
例如如下代码,
//在text.i中
int add(int a, int b) ;
int main() {
cout<<add(1)<<endl;
}
//在f.i中
int add(int a,int b = 10)
{
return a+b;
}
在编译的时候main函数出现add,会先从声明找,声明里只有一个add(int a,int b);这样编译器就会认为add(1)少传了一个参数,就会报错。
汇编语言call
在c++中,对函数的调用从汇编层面上看是通过call + 函数地址来实现的。
在调用函数的时候,本质上是通过函数的地址,来进行调用的。而很关键的是:函数的声明不创建栈帧,函数的实现才创建栈帧生成地址
运行如上函数
call指令本质上是跳到函数的第一条指令的地址,但是在没有定义只有声明的话,会显示无法移动。
这时因为add这个函数本质上是在f.s里面的,而且还没有执行链接,因此他们是独立的两部分,我的f.h在展开的时候,相当于给了text.cpp一个承诺,承诺有这个函数但是这个承诺要在链接的时候才会交付。
那么链接时如何找到对应的函数呢?这就引入了符号表。
符号表
在编译的过程中会生成函数调用指令,包括栈帧的建立等。同时会生成一个符号表,符号表就是函数的函数名和函数地址的映射。
进入链接操作的前提是,编译没有错误,也就是前面的声明承诺实现了,但是在文件表交付的过程中,声明的函数在链接时找不到。于是会报连链接错误。
那么函数表有了,我们要如何根据确定函数名呢?于是我们引入了编译中的函数名生成原则。
编译中函数名的生成
c++编译器命名规则
可以看到,函数在编译时的命名规则。
_Z + 函数名长度 + 函数名+ 参数类型
这个_Z是Linux下的,原则上不影响。
_Z1表示函数名是1位,f表示是函数名,di表示第一个参数是double,第二个参数是int,这样子可以区分所有同名不同形参类型的重载函数,也从编译的角度证明了重载的机理。
在得出编译角度下的函数名后,这个函数名就和函数的第一条指令的地址(称为函数地址)一起被写在了函数表里。
所以这种结合了函数名与函数参数的函数表项的构成,是c++能够实现重载的根本原因。
c语言编译器命名原则
在c语言的编译器中,函数的名字与函数的参数无关。因此如果出现同名的函数,函数表会直接错误,无法找到对应的函数地址,因此c语言不支持重载。
c和c++相互调用库的问题
实际开发中,我们用c写了一个静态库,现在我们想在c++工程里,调用这个静态库。
前面我们讲过,c和c++在编译的时候构成的函数表不同,也就是说原则上c++不能调c的库。
随便举一个例子,c++调用c的静态库时,出现链接错误,就是函数表项的函数名不同导致的。
那么我们真的想调用要如何实现呢?
总结
要回答c++能重载、c语言不能重载这个问题,要从编译时生成的函数表讲起,函数表是在链接的时候实现调用函数地址合并时根据函数名匹配地址的。又因为c++在编译的时候所生成的函数名与函数的形参有关,而c编译时与函数形参无关,如果c重载的话会出现相同名的函数表项,因此c语言不能重载。