函数重载

定义

函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。

重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。

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++中,一个程序要运行起来,需要经历:预处理、编译、汇编、链接
函数重载_第1张图片
名字修饰是一种在编译过程中,将函数、变量的名称重新改编的机制,简单地说就是编译器为了区分各个函数,将函数通过一定算法,重新修饰为一个全局唯一的名称。

为什么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; 
}

函数重载_第2张图片
通过上述错误可以看出,编译器实际在底层使用的不是Add名字,而是被重新修饰过的一个比较复杂的名字,被重新修饰后的名字中包含了:函数的名字以及参数类型。这就是为什么函数重载中几个同名函数要求其参数列表不同的原因。只要参数列表不同,编译器在编译时通过对函数名字进行重新修饰,将参数类型包含在终的名字中,就可保证名字在底层的全局唯一性。
函数重载_第3张图片

   Linux对于函数的名字是如何修饰的---作用域+返回类型+函数名+参数列表
   在g++下这种编码方式是:
   1、所有编码后的符号都由_Z开头
   2、如果有作用域符,则在_Z之后加上N
   3、接下来是命名空间名字长度+N和类命长度+C
   4、如果有作用域符,则以E结尾
   5、后加上函数形参符号,int就是i,float就是f,有几个形参就写几个符号

编译器如何解析重载函数调用

编译器在对重载函数调用进行处理时,由语法分析、C++文法、符号表、抽象语法树交互处理
函数重载_第4张图片

  1. 由匹配文法中的函数调用,获取函数名;
  2. 获得函数各参数表达式类型;
  3. 语法分析器查找重载函数,符号表内部经过重载解析返回最佳的函数
  4. 语法分析器创建抽象语法树,将符号表中存储的最佳函数绑定到抽象语法树上

其中,重载函数解析大致可以分为三步:

  1. 根据函数名确定候选函数集
  2. 从候选函数集中选择可用函数集合
  3. 从可用函数集中确定最佳函数,或由于模凌两可返回错误

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共同使用

你可能感兴趣的:(c++)