定义
函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。
重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。
int Add(int left, int right)
{
return left+right;
}
double Add(double left, double right)
{
return left+right;
}
long Add(long left, long right)
{
return left+right;
}
int main()
{
Add(10, 20);
Add(10.0, 20.0);
return 0;
}
函数重载定义要求:
- 同名函数
- 同名函数的形参列表(参数个数、类型、顺序)必须不同。
- 返回值不做要求
为什么需要函数重载
- 如果没有函数重载机制,如在c中,要printf函数打印不同类型的数据就需要取不同的名字。
printf_int、printf_string...
如果有多个,就需要为实现同一个功能的函数取很个名字。 - 类的构造函数跟类名相同,如果没有函数重载机制,要想实例化不同的对象,就会相当麻烦。
- 操作符重载,本质上就是函数重载,它大大丰富了已有操作符的含义,方便使用。
编译器如何解决命名冲突
名字修饰(Name Mangling)
在c/c++中,一个程序要运行起来,需要经历:预处理、编译、汇编、链接
名字修饰是一种在编译过程中,将函数、变量的名称重新改编的机制,简单地说就是编译器为了区分各个函数,将函数通过一定算法,重新修饰为一个全局唯一的名称。
为什么c语言不支持函数重载
- C语言:只是在函数名字前面添加了下下划线
int Add(int left, int right);
int main()
{
Add(1, 2);
return 0;
}
上述Add函数只给了声明没有给定义,因此在链接时就会报错。
提示:在main函数中引用的Add函数找不到函数体。从报错结果中可以看到,C语言只是在函数名前添加了下划线。因此当工程中存在相同函数名的函数,就会产生冲突。
- C++:映射机制为:返回类型+函数名+参数列表
int Add(int left, int right);
int main()
{
Add(1, 2);
return 0;
}
通过上述错误可以看出,编译器实际在底层使用的不是Add名字,而是被重新修饰过的一个比较复杂的名字,被重新修饰后的名字中包含了:函数的名字以及参数类型。这就是为什么函数重载中几个同名函数要求其参数列表不同的原因。只要参数列表不同,编译器在编译时通过对函数名字进行重新修饰,将参数类型包含在终的名字中,就可保证名字在底层的全局唯一性。
Linux对于函数的名字是如何修饰的---作用域+返回类型+函数名+参数列表
在g++下这种编码方式是:
1、所有编码后的符号都由_Z开头
2、如果有作用域符,则在_Z之后加上N
3、接下来是命名空间名字长度+N和类命长度+C
4、如果有作用域符,则以E结尾
5、后加上函数形参符号,int就是i,float就是f,有几个形参就写几个符号
编译器如何解析重载函数调用
编译器在对重载函数调用进行处理时,由语法分析、C++文法、符号表、抽象语法树交互处理
- 由匹配文法中的函数调用,获取函数名;
- 获得函数各参数表达式类型;
- 语法分析器查找重载函数,符号表内部经过重载解析返回最佳的函数
- 语法分析器创建抽象语法树,将符号表中存储的最佳函数绑定到抽象语法树上
其中,重载函数解析大致可以分为三步:
- 根据函数名确定候选函数集
- 从候选函数集中选择可用函数集合
- 从可用函数集中确定最佳函数,或由于模凌两可返回错误
c++中能否将一个函数按照c的风格编译
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加 extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译。
extern "C" int Add(int left, int right);
int main()
{
Add(1,2);
return 0;
}
c/c++函数调用约定
函数调用约定
常见的函数调用约定[5]:cdecl,stdcall,fastcall,thiscall,naked call
MFC调用约定(VS6:Project Settings->C/C++ Calling convention:)
__cdecl(C调用约定.The C default calling convention)C/C++ 缺省调用方式
1) 压栈顺序:函数参数从右到左
2) 参数栈维护:由调用函数把参数弹出栈,传送参数的内存栈由调用函数来维护
(正因为如此,实现可变参数vararg的函数(如printf)只能使用该调用约定)
3) 函数修饰名约定:VC将函数编译后会在函数名前面加上下划线前缀
4) 每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大__stdcall (Pascal方式清理C方式压栈,通常用于Win32 Api中)
1) 压栈顺序:函数参数从右到左
2) 参数栈维护:被调用函数把参数弹出栈(在退出时清空堆栈)
3) 函数修饰名约定:VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上”@”和参数的字节数__fastcall (快速调用约定,通过寄存器来传送参数)
1) 压栈顺序:用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送
2) 参数栈维护:被调用函数在返回前清理传送参数的内存栈
3) 函数修饰名约定:VC将函数编译后会在函数名前面加上”@”前缀,在函数名后加上”@”和参数的字节数thiscall (本身调用,仅用于“C++”成员函数)
1) 压栈顺序:this指针存放于CX/ECX寄存器中,参数从右到左的压栈顺序
2) thiscall不是关键词,因此不能被程序员指定naked call (裸调)
1) 当采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容
(这些代码称作 prolog and epilog code,一般ebp、esp的保存是必须的)
2) naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用