在一次修改代码过程中踩的坑,下来研究了一下,发现C++中虚函数重载后会产生很多有意思的情况,在这里总结了一下。
C++中有重载(overload)和重写(override)以及重定义这几个概念,1 overload:指的是相同作用域中的两个函数的函数名相同,但参数列表的个数、顺序、类型不同。而override指的是子类中重新定义的父类的虚函数。
2 override:overload要求两个函数的参数列表必须不同,但是不要求这两个函数必须是虚函数。而override要求必须是虚函数且父类的虚函数必须有virtual关键字,函数的参数列表和返回值也必须相同。子类中override的虚函数的访问修饰符可以不同。
3 重定义也是描述分别位于父类与子类中的同名函数的,但返回值可以不同。如果参数列表不同,这时子类中重定义的函数不论是否有virtual关键字,都会隐藏父类的同名函数。
如果参数列表相同,但父类中的同名函数没有virtual关键字修饰,此时父类中的函数仍然被隐藏。
虚函数的典型用法是:
#includeusing namespace std; class Base{ public: virtual void f(); }; void Base::f(){ cout << "Base::f()" << endl; } class Derived:public Base{ public: virtual void f(); }; void Derived::f(){ cout << "Derived::f()" << endl; } int main() { Base *p1 = new Base; Base *p2 = new Derived; p1->f(); p2->f(); delete p1; delete p2; return 0; }
程序的输出是:
Base::f() Derived::f()
那么如果虚函数之间又发生了overload,会出现什么情况?
我们先看最简单的情况:
#includeusing namespace std; class Base{ public: virtual void f(int); }; void Base::f(int a){ cout << "Base::f(int) " << a << endl; } class Derived:public Base{ public: virtual void f(); }; void Derived::f(){ cout << "Derived::f()" << endl; } int main() { Base *p1 = new Base; p1->f(1); Base *p2 = new Derived; p2->f(); delete p1; delete p2; return 0; }
编译上面的代码,会发生如下错误:
test.cpp: In function ‘int main()’: test.cpp:28:11: error: no matching function for call to ‘Base::f()’ p2->f(); ^ test.cpp:10:6: note: candidate: virtual void Base::f(int) void Base::f(int a){ ^ test.cpp:10:6: note: candidate expects 1 argument, 0 provided
这就是因为父类中虚函数的参数列表已经发生变化,这时不论子类中重定义的函数不论是否有virtual关键字,都会隐藏父类的同名函数。这时子类中只是重定义了一个自己的函数virtual void f(),而并没有override父类中对应的虚函数。
p2是一个指向Base类型的指针,根据虚函数的特性,对p2→f();的处理取决于是否override了父类的虚函数,如果没有,仍然会调用调用父类中被override的虚函数,但是现在父类中的函数已经成为了virtual void f(int),因此在执行p2→f()时会由于缺少输入参数而出现上述错误。
为了证明上述论断,可以在执行p2→f()时传入参数来判断:
#includeusing namespace std; class Base{ public: virtual void f(int); }; void Base::f(int a){ cout << "Base::f(int) " << a << endl; } class Derived:public Base{ public: virtual void f(); }; void Derived::f(){ cout << "Derived::f()" << endl; } int main() { Base *p1 = new Base; p1->f(1); Base *p2 = new Derived; p2->f(2); delete p1; delete p2; return 0; }
这时可以通过编译,执行结果为
Base::f(int) 1 Base::f(int) 2
可以看到,子类自己定义的virtual void f()其实是父类的virtual void f(int)的一个重定义的函数,这时尽管p2实际指向了一个Derived对象,但由于没有override父类对应的虚函数,在执行 p2→f(2)时将执行父类的virtual void f(int)。
也可以这样修改:
#includeusing namespace std; class Base{ public: virtual void f(int); virtual void f(); }; void Base::f(int a){ cout << "Base::f(int) " << a << endl; } void Base::f(){ cout << "Base::f() " << endl; } class Derived:public Base{ public: virtual void f(); }; void Derived::f(){ cout << "Derived::f()" << endl; } int main() { Base *p1 = new Base; p1->f(1); Base *p2 = new Derived; p2->f(); delete p1; delete p2; return 0; }
因为父类中定义了可被子类override的函数,所以这时执行p2→f()又会重新执行子类的virtual void f():
Base::f(int) 1 Derived::f()
我们甚至还可以这样验证:
#includeusing namespace std; class Base { public: virtual void f(int); virtual void f(); }; void Base::f(int a) { cout << "Base::f(int) " << a << endl; } void Base::f() { cout << "Base::f() " << endl; } class Derived:public Base { public: virtual void f(float); }; void Derived::f(float a) { cout << "Derived::f(float)" << showpoint << a << endl; } int main() { Base *p1 = new Base; p1->f(1); Base *p2 = new Derived; p2->f(**2.0**); delete p1; delete p2; return 0; }
这时输出仍然为
Base::f(int) 1 Base::f(int) 2
这说明如果通过指向父类的指针,调用虚函数时,如果子类重定义了该虚函数(参数列表发生变化),则实际调用的仍是父类中的虚函数。
上面都是通过指向父类的指针来调用虚函数的,那么如果通过指向子类的指针调用虚函数会发生什么:
#includeusing namespace std; class Base { public: virtual void f(int); virtual void f(); }; void Base::f(int a) { cout << "Base::f(int) " << a << endl; } void Base::f() { cout << "Base::f() " << endl; } class Derived:public Base { public: virtual void f(float); }; void Derived::f(float a) { cout << "Derived::f(float)" << showpoint << a << endl; } int main() { Base *p1 = new Base; p1->f(1); Derived *p2 = new Derived; p2->f(2.0); p2->f(3); delete p1; delete p2; return 0; }
这时输出就变为了:
Base::f(int) 1 Derived::f(float)2.00000 Derived::f(float)3.00000
这说明,如果通过指向子类的指针调用虚函数,并且子类重定义了父类的虚函数,这时实际调用的就将是子类中的虚函数。