什么是函数重载
函数重载是函数的一种特殊情况,这是C++支持的一种功能,
在C语言中,如果函数出现重名,这时编译的话编译器是会报错的,但是C++支持函数的重载。
C++允许在同一作用域中声明几个功能类似的同名函数,常用来处理功能类似,但是数据类型不同的问题。
构成函数重载的条件
函数的重载类型有三种,满足这三种条件之一就可以构成函数的重载。
函数参数个数不同
//函数参数个数不同
void Print(int x)
{
cout << x << endl;
}
void Print()
{
cout << "hello world" << endl;
}
函数参数类型不同
//函数参数类型不同
void Print(int x)
{
cout << x << endl;
}
void Print(double x)
{
cout << x << endl;
}
函数参数顺序不同
//函数参数顺序不同
void Print(int x,double y)
{
cout << x << endl;
cout << y << endl;
}
void Print(double y,int x)
{
cout << y << endl;
cout << x << endl;
}
可以构成函数重载的只有这三种情况,或者同时满足这三种情况中的几种。
比如有的同学可能会问返回值不同能不能构成重载,答案是不可以的。
函数调用的时候是不能区分返回值的。还有缺省值不同,也不能构成重载
//不构成重载
void Print(int x)//没有缺省
{
cout << x << endl;
}
void Print(int x = 10)//缺省
{
cout << x << endl;
}
谈到了缺省值,这里有一个点需要注意,这里的场景下,虽然可以构成重载,但是也会出现问题。
函数重载原理
我们了解了函数构成重载的条件之后,不免会有疑问,
为什么C语言不支持函数重载,但是C++却可以支持呢?
下面来分析一下函数重载的原理
要想比较清晰的了解函数重载的原理,我们就要先回顾一下
编译器编译程序的过程,以及每个过程进行的工作
预处理 | 编译 | 汇编 | 链接 |
---|---|---|---|
头文件展开、宏替换、条件编译、去掉注释 | 检查语法、生成语法树、词义分析、符号汇总生成汇编代码 等 | 汇编代码转换成二进制机器码 ,生成符号表 | 合并段表、符号表的合并和符号表的重定位 |
.i文件 | .s文件 | .o文件 | .out文件 |
编译的每一个过程都会产生对应的文件,不同的编译器会有一些差别,
上面列举的是Linux环境下编译过程中产生的对应的文件。
我们知道,在C语言中如果函数重名,编译器报错的信息显示函数已有主体,
根据编译的过程,函数是链接过程出现问题。
void test()
{
printf("hehe\n");
}
int main()
{
test();
return 0;
}
调试一下这段代码,看一下汇编代码
调用test函数的时候,会有一条call指令,然后根据给出的地址,会跳转到函数执行的指令行。
函数编译的时候,在编译到函数时,如果是函数的声明,这时是不会给出地址的,
如果当前文件有函数的定义,那么编译时就会给出地址,这样编译器就可以根据地址调用函数了。
到这里我们就知道了,编译器是根据函数名+地址来进行调用的,
C语言在编译时对函数名的处理就是简单的使用给定的函数名,
所以导致在编译链接的过程中,造成歧义,对于相同函数名的函数,不知道调用哪一个,
所以C语言是不支持函数重载的。那么C++是怎么支持的呢?
C++的目标文件不是直接简单的用函数名来标识和查找函数。
C++引入了函数名修饰规则,但是这个函数名修饰规则在不同的编译器下面是不一样的。
有了函数名修饰规则,所以只要参数不同,
在汇编的时候产生的符号表中的函数就不存在二义性和冲突了。
链接的时候,main函数去调用两个重载函数,查找地址时,就非常明确了。
这里举一个函数名修饰规则的例子
在Linux环境下,g++的编译器函数名修饰规则
_Z + 函数名长度 + 函数名 + 函数参数类型首字母
比如这个函数
void test(int x = 10)
{
cout << x << endl;
}
编译后生成的函数名修饰为
_Z4testi
总结
到这里我们就明白了为什么C语言不支持函数重载,因为同名函数没有办法区分。
而C++是通过函数名修饰规则来区分,只要函数的参数满足构成重载的三个条件,
修饰出来的名字就会不一样,就可以支持重载了。
还有,上面之所以用g++编译器来举例是因为windows环境下,函数名修饰的规则很复杂,不利于理解。
所以,我们也明白了,函数重载是要求参数不同,而返回值不同不能构成重载,因为编译链接的时候与返回值没有关系。