C++ 类成员函数的重写、重载和隐藏
重载(overload)
重载的定义为:在同一作用域中,同名函数的形式参数(参数个数、类型或者顺序)不同时,构成函数重载。例如:
class A{ public: int func(int a); void func(int a, int b); void func(int a, int b, int c); int func(char* pstr, int a); };
以上的四个函数均构成重载。
需要注意的是:
1.函数返回值类型与构成重载无任何关系
2.类的静态成员函数与普通成员函数可以形成重载
3.函数重载发生在同一作用域,如类成员函数之间的重载、全局函数之间的重载
这里还需要注意一下 const重载:
class D{ public: void funcA(); //1 void funcA() const; //2 void funcB(int a); //3 void funcB(const int a); //4 };
在类D 中 funcA 与 const funcA是合法的重载,而 两个 funcB 函数是非法的,不能通过编译。
原因是:顶层const不影响重载性,因为值传递的拷贝特性,想函数内传递的值类型形参的变化不会影响实参,所以有无const 对其而言是没有意义的。
调用规则:const对象默认调用const成员函数,非const对象默认调用非const成员函数;
隐藏(hiding)
隐藏定义:指不同作用域中定义的同名函数构成隐藏(不要求函数返回值和函数参数类型相同)。比如派生类成员函数隐藏与其同名的基类成员函数、类成员函数隐藏全局外部函数。
例如:
void hidefunc(char* pstr){ cout << "global function: " << pstr << endl; } class HideA{ public: void hidefunc(){ cout << "HideA function" << endl; } void usehidefunc(){ //隐藏外部函数hidefunc,使用外部函数时要加作用域 hidefunc(); ::hidefunc("lvlv"); } }; class HideB : public HideA{ public: void hidefunc(){ cout << "HideB function" << endl; } void usehidefunc(){ //隐藏基类函数hidefunc,使用外部函数时要加作用域 hidefunc(); HideA::hidefunc(); } };
隐藏的实质是;在函数查找时,名字查找先于类型检查。如果派生类中成员和基类中的成员同名,就隐藏掉。编译器首先在相应作用域中查找函数,如果找到名字一样的则停止查找。
重写/覆盖(override)
重写的定义:派生类中与基类同返回值类型、同名和同参数的虚函数重定义,构成虚函数覆盖,也叫虚函数重写。
需要注意的是,这里有一个特殊情况,即协变返回类型。
定义是:如果虚函数返回指针或者引用时(不包括value语义),子类中重写的函数返回的指针或者引用是父类中被重写函数所返回指针或引用的子类型。看示例代码:
class Base{ public: virtual A& show(){ cout<<"In Base"<<endl; return *(new A); } }; class Derived : public Base{ public: //返回值协变,构成虚函数重写 B& show(){ cout<<"In Derived"<<endl; return *(new B); } };
对比覆盖和隐藏,不难发现函数覆盖其实是函数隐藏的特例。如果派生类中定义了一个与基类虚函数同名但是参数列表不同的非virtual函数,则此函数是一个普通成员函数,并形成对基类中同名虚函数的隐藏,而非虚函数覆盖。
隐藏是一个静态概念,它代表了标识符之间的一种屏蔽现象,而覆盖则是为了实现动态联编,是一个动态概念。
final和override关键字
通过上面的介绍,我们知道派生类可以定义一个函数与基类中虚函数的名字相同但是形参列表不同的函数(隐藏)。编译器将认为新定义的这个函数与基类中原有的函数是相互独立的。
但是我们在写虚函数时,想让派生类中的虚函数覆盖掉基类虚函数,有时我们会不小心写错,造成了隐藏,这不是我们想要看到的结果。所以C++ 11新标准中我们可以使用override关键字来说明派生类中的虚函数。
如果我们使用override标记了某个函数,但该函数并没有覆盖已存在的虚函数,此时编译器将报错。
class B{ virtual void f1( int ) const; virtual void f2(); void f3(); }; class C : B{ void f1( int ) const override; //正确,f1与基类中的f1匹配 void f2( int ) override; //错误:B没有形如f2(int)的函数 void f3() override; //错误:f3不是虚函数 void f4() override; //错误:B中没有名为f4的函数 };
使用override是希望能覆盖基类中的虚函数,如果不符合则编译器报错。
我们还能把某个函数指点为 final ,意味着任何尝试覆盖该函数的操作都将引发错误:
class D : B{ //从B继承 f2() 和 f3(),覆盖 f1( int ) void f1( int ) const final; //不允许后续的其它类覆盖 f1(int) }; class E : D{ void f2(); //正确:覆盖从间接类B继承而来的f2 void f1( int ) const; //错误:D已经将 f2 声明成 final };
final 和 override 说明符出现在形参列表以及尾置返回类型之后。
final 还可以跟在类的后面,意思这个类不能当做其它类的基类。
重写和重载
范围的区别:被重写的和重写的函数在两个类中,而重载和被重载的函数在同一个类中。
参数的区别:被重写函数和重写函数的参数列表一定相同,而被重载函数和重载函数的参数列表一定不同。
virtual的区别:重写的基类中被重写的函数必须要有virtual修饰,而重载函数和被重载函数可以被virtual修饰,也可以没有。
隐藏和重写、重载
“隐藏”是指派生类的函数屏蔽了与其同名的基类函数。
与重载的范围不同:和重写一样,隐藏函数和被隐藏函数不在同一个类中。
参数的区别:隐藏函数和被隐藏的函数的参数列表可以相同,也可不同,但是函数名肯定要相同。当参数不相同时,无论基类中的函数数是否被virtual修饰,基类的函数都是被隐藏,而不是被重写。
注意:虽然重载和覆盖都是实现多态的基础,但是两者实现的技术完全不相同,达到的目的也是完全不同的,覆盖是动态绑定的多态,而重载是静态绑定的多态。
————————————————
版权声明:本文为CSDN博主「YoungYangD」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_39640298/article/details/88725073