函数重载

函数重载本质上是一种多态性的简单体现(静态绑定)

c语言中对不同数据类型的数据进行功能相似的操作时必须编写很多个不同的函数

如:对两个整数,两个实数,一个整数一个实数,三个整数求和
->此时需要四个功能相似但名称不同的函数

而c++中可以让四个函数名称相同,即函数重载

对两个以上参数的函数具有相同的函数名,但形参个数或类型不同,编译器可以通过实参个数或者类型的最佳匹配自动决定调用哪个函数,例:

int add(int a,int b){return a+b;}
float add(float a,float b){return a+b;}
int add(int a,int b,int c){return a+b;}
float add(int a,float b){return a+b;}
int main{
    int a,b,c;float x,y;
    add(a,b);      //调用int add(int a,int b)
    add(x,y);      //调用float add(float a,float b)
    add(a,b,c);    //调用int add(int a,int b,int c)
    add(a,y);      //调用float add(int a,float b)
}

注意,编译器只会根据形参个数或类型来匹配函数重载,不会根据形参名和返回值(函数类型)匹配,例:
(主要是一般调用的时候并不能指定返回值)

int add(int a,int b);
int add(int x,int y);                        //编译器不能通过形参名区分函数,并且这两个函数在实际调用时没有任何区别
int add(int a,int b){return a+b;}
float add(int x,int y){return (float)(x+y);};//理由同上,实际调用时没区别

不应该把不同功能的函数定义为重载函数,否则使用函数时容易混淆,如:

int add(int a,int b){return a+b;}
float add(float x,float y){return x-y;}

函数重载示例程序:自定义Swap函数(不用模板的话)

#include <iostream>
using namespace std;
void Swap(int *a,int *b){
    int temp=*a;
    *a=*b;
    *b=temp;
}
void Swap(float *a,float *b){
    float temp=*a;
    *a=*b;
    *b=temp;
}
void Swap(char *a,char *b){
    char temp=*a;
    *a=*b;
    *b=temp;
}
void Swap(bool *a,bool *b){
    char temp=*a;
    *a=*b;
    *b=temp;
}
int main(){
    int n1=100,n2=200;
    Swap(&n1,&n2);
    cout<<n1<<", "<<n2<<endl;
    float f1=12.5,f2=56.93;
    Swap(&f1,&f2);
    cout<<f1<<", "<<f2<<endl;
    char c1='A',c2='B';
    Swap(&c1,&c2);
    cout<<c1<<", "<<c2<<endl;
    bool b1=false,b2=true;
    Swap(&b1,&b2);
    cout<<b1<<", "<<b2<<endl;
    return 0;
}

实际上,c++代码在编译时编译器会根据参数列表对函数进行重命名,如:

void Swap(int a, int b)会被重命名为_Swap_int_int,
void Swap(float x, float y)会被重命名为_Swap_float_float

从这个角度讲,函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样
如果对类中的成员函数进行重载的话存在两种情况:

  • 同一个类中重载,重载函数是以参数的特征进行区分的
    • 将成员函数定义为const也算重载
  • 派生类重载基类的成员函数:由于重载函数处于不同的类中,因此它们的原型可以完全相同,调用时使用作用域符可以加以区分(实际上并不能算重载,因为作用域不同)
  • 这两种重载的匹配都是在编译时静态完成的(也就是在编译时命名为不同函数或处于不同符号表中)

关于const修饰函数参数算不算重载的问题大概分几种情况

  • const修饰参数为值传递,也就是不用引用、指针参数
    • 此时实参实际上是传入参数的一个副本(而且是在栈上的局部变量),所以怎么修改也不会影响传入参数(尽管在函数内也是禁止修改该局部变量的),因此加不加const对调用者是没影响的
    • 因此这种情况不算重载,如果定义同名函数其中一个参数为int,另一个是const int,此时还是会报重定义错误
  • const修饰参数为指针,这种情况函数是可能产生副作用的,此时也分两种情况
    • const修饰的是指针所指的基类型,比如:const int* ,这种情况是保护指针所指内容在函数内不能被修改,这种情况和int* 是算重载
    • const修饰指针变量,比如:int* const,这种情况是指传入的指针变量值在函数不能修改,但指针所指的基类型变量值是可修改的,因此这种情况就和值传递是相通的了,属于重定义
  • const修饰参数为引用,如const int&,此时和const int* 的作用类似,保护引用变量内容在函数内不能被修改,因此算重载
    • 由于&本身就是就是一种常量(从可执行程序的反汇编代码可以看出引用是一种指针常量),因此实际上不存在int&const的用法

默认形参(缺省形参)

在定义函数时预先声明默认的形参值,即缺省参数。调用时如果给出实参,则用实参初始化形参,否则采用预先声明的默认形参值,例:

int add(int a=1,int b=2){return a+b;}
int main(){
    using namespace std;
    cout<<add(10,20)<<" "<<add(10)<<" "<<add();
}   //输出结果:30 12 3

默认形参值可以在函数原型中给出,

如:int add(int x,int y=1,int z=2);

默认形参值必须按照从右向左的顺序声明,即在有默认值的形参右边不能出现无默认值的形参,如:

int add(int x,int y=1,int z=2); 正确
int add(int x=1,int y,int z=2); 错误
int add(int x=1,int y=2,int z); 错误

原因在于输入实参时是从左向右传递
如果在有默认值的形参右边存在无默认值的形参时,输入的数据会覆盖有默认值的形参,默认值失去了意义,并且后面没有默认值的形参可能传不到值

在相同作用域内,默认形参值应保持唯一,但在不同作用域内,允许声明不同的默认形参值,例:

int add(int a=1,int b=2){return a+b;}      //全局的add函数
int func(){return add();}                  //返回全局的add函数值
int main()
{
    using namespace std;
    int add(int a=3,int b=4);              //main()内的局部add函数
    cout<<add()<<" "<<func()               //输出结果:7 3
}

注意,函数重载和默认形参同时存在时也可能会出错:函数不能既作为重载函数,又作为有默认参数的函数,例:

int add(int x,int y);
int add(int x,int y,int z=3);
int main(){
    int a,b;
    add(1,2);   // 此时两个重载都是匹配的,编译器无法决定调用哪个函数 
}