在一次修改代码过程中踩的坑,下来研究了一下,发现C++中虚函数重载后会产生很多有意思的情况,在这里总结了一下。
C++中有重载(overload)和重写(override)以及重定义这几个概念,1 overload:指的是相同作用域中的两个函数的函数名相同,但参数列表的个数、顺序、类型不同。而override指的是子类中重新定义的父类的虚函数。
2 override:overload要求两个函数的参数列表必须不同,但是不要求这两个函数必须是虚函数。而override要求必须是虚函数且父类的虚函数必须有virtual关键字,函数的参数列表和返回值也必须相同。子类中override的虚函数的访问修饰符可以不同。
3 重定义也是描述分别位于父类与子类中的同名函数的,但返回值可以不同。如果参数列表不同,这时子类中重定义的函数不论是否有virtual关键字,都会隐藏父类的同名函数。
如果参数列表相同,但父类中的同名函数没有virtual关键字修饰,此时父类中的函数仍然被隐藏。
虚函数的典型用法是:
#include <iostream>
using 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,会出现什么情况?
我们先看最简单的情况:
#include <iostream>
using 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()时传入参数来判断:
#include <iostream>
using 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)。
也可以这样修改:
#include <iostream>
using 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()
我们甚至还可以这样验证:
#include <iostream>
using 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
这说明如果通过指向父类的指针,调用虚函数时,如果子类重定义了该虚函数(参数列表发生变化),则实际调用的仍是父类中的虚函数。
上面都是通过指向父类的指针来调用虚函数的,那么如果通过指向子类的指针调用虚函数会发生什么:
#include <iostream>
using 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
这说明,如果通过指向子类的指针调用虚函数,并且子类重定义了父类的虚函数,这时实际调用的就将是子类中的虚函数。