函数重载是C++的重要特性,他允许我们在参数列表不同的前提下定义同名函数。在函数调用时将编译器将根据函数名与调用时的参数类型决定调用的具体函数。
/*实现一个add()函数能够同时对整数和字符串做加法。*/ using namespace std; //版本一:整数加法 int add(int l, int r) { return l+r; } //版本二:字符串拼接 const string& add(const string& l, const string& r) { string ret(l); ret.append(r); return ret; }无论是overwrite还是override都是在面向对象的继承过程中出现的现象。关于他们的关系和区别很多资料可以查找,这是一篇不错的参考博文。此处不做赘述。 需要特别强调一点!如果派生类override或者overwrite了一个基类的函数,那么基类中该函数的所有重载版本在派生类中均不可见。即使我们的目的只是在派生类中重新实现基类成员函数的一个重载版本,我们也必须重新定义所有的重载函数(当然其实现可以是直接调用基类对应的成员函数)。
/*基类中同名函数各个重载版本在派生类中的可见性总是一致的*/ class Base { public: //版本一 void Func(int i); //版本二 virtual void Func(const char * p_chr); }; class Derived : public Base { //override基类Func()函数的第二个重载版本 void Func(const char *p_chr) override; }; int i; const char *p = "hello"; Base().Func(i); //正确。调用基类版本一 Base().Func(p); //正确。调用基类版本二 Derived().Func(i); //正确。调用派生类override版本 Derived().Func(p); //错误。派生类中没有参数列表对应的函数声明。参数仅常量性不同是否可以重载? 不一定!成员函数的常量性不同可以重载;参数为引用时常量性不同可以重载;指针所指对象的常量性不同可以重载;只有传值参数以及指针本身的常量性不同时是不可以重载的。 以下情况,仅常量性不同的重载是合法的。
//成员函数的常量性不同,可以重载 class C { public: void FuncA(int i); void FuncA(int i) const; }; //参数类型为引用时,常量性不同可以重载 void FuncB(int& i); void FuncB(const int& i); //指针所指对象的常量性不同,可以重载 void FuncC(int *p); void FuncC(const int *p);成员函数的常量性可以认为是指针所指对象的常量性,当成员函数为const类型则说明调用该方法的类实例为const,相当于this指针为指向常量的指针。总结下来,引用和指针所指对象的常量性不同可以重载。(有些人将引用理解为指向对象的常量指针,这样一来剩下两种情况还可以进一步理解为:只有指针所指对象的常量性不同时可以重载。) 以下情况,仅常量性不同不可以重载。
//传值调用形参常量性不同,不可以重载 void FuncD(int i); void FuncD(const int i); //指针本身的常量性不同,不可以重载 void FuncE(int *p); void FuncE(int * const p);实际上以上两种情况也可以归为一种。当我们使用传址调用(传指针)时,可以被看做是传递指针值得传值调用,指针自身的常量性不同不能重载。总结来看即为:非引用类型的参数本身的常量性不同不能重载。
那么,为什么非引用类型参数的常量性不同不能重载呢?C++的这样设计的道理何在?这还得从C语言说起,C++出于与C语言保持兼容的考虑,保留了C语言中的传值调用(参考Python、Java这些面向对象语言,他们根本没有这个概念,从C++的角度来看它们的函数调用都是引用传参)。而在传值调用过程中,由于函数内部总会在执行函数体之前复制一个实参副本,并在函数体内使用副本参与运算。这就导致实参本身(实参为指针时,指针所指对象就不一定不改变了。)在函数内部永远不会改变,函数返回时实参的值总是调用时实参的值。既然如此,那么实参是否为const类型根本无关紧要,即使不是const类型函数返回时也一定不会变。
相反地,引用和指针所指对象的常量性则显得很重要了!因为这些对象在函数内部是有可能被改变的。
引用和非引用是否可以重载? 可以,但是(一般“但是”后面的才是重点)这很容易出现二义性错误。一般来说也没有这样重载的必要。
//定义两个函数,一个是引用,一个是传值 //版本一:非引用 void FuncV(double d) { //do something } //版本二:引用 void FuncV(double & d) { //do something else } //目前为止,函数的声明和定义没有错误 int i = 1; double d = 1.1; /*以下调用正确。没有完全匹配的重载版本,但通过自动类型转化生成了临时 *的double对象。这是一个右值对象,只能匹配版本一和版本二无法匹配。*/ FuncV(i); /*错误,同名函数的两个重载版本均完全匹配。编译器无法决定使用哪一个*/ FuncV(d);诚然,错误出现在函数调用者。但事实上这是函数设计者在“坑队友”,因为上述的重载函数设计几乎没有办法在实参为double时被正常的调用,总是会出现二义性。当然,“几乎没有”的意思就是方法还是有的。
强制类型转化可以让上一段代码中的最后一个调用生效。(来自Stack Overflow)
//调用版本一 static_cast<void (*)(double)>(FuncV)(d); //调用版本二 static_cast<void (*)(double &)>(funcV)(d);另外左值引用和右值引用也是可以重载的。不过右值引用目前还没弄透彻就不展开了。此处先打卡。
【参考资料】 1. Stanley B.Lippman, Josee Lajoie, Barbara E.Moo 著, 王刚 杨巨峰 译, C++ Primer(第五版)中文版, 电子工业出版社。 2. jszhangyili的博客,C++中 overload 、override、overwrite 之间的区别 3. Scott Meyers 著, 侯捷 译,Effective C++(第三版)中文版,电子工业出版社。