当执行pBase->show()时,要观察show在Base基类中声明的是虚函数还是非虚函数。若为虚函数将使用动态联编(使用虚函数表决定如何调用函数),若为非虚函数则使用静态联编(根据调用指针pBase的类型来确定调用哪个类的成员函数)。此处假设show为虚函数,首先:由于检查到pBase指针类型所指的类Base中show定义为虚函数,因此找到pBase所指的对象(有可能是Base类型也可能是Extend类型。),访问对象得到该对象所属类的虚函数表地址。其次:查找show在Base类中声明的位置在Base类中所有虚函数声明中的位序。然后到pBase所指对象的所属类(有可能是Extend哦,多态)的虚函数表中访问该位序的函数指针,从而得到要执行的函数。
例如:
基类Base::virtualvoid show(); (1)
子类Extend::virtualvoid show(); (2)
Externext;
Base*pBase=&ext;
pBase->show();
当执行pBase->show();时首先到Base中查看show(),发现其为虚函数,然后访问pBase指向的ext对象,在对象中得到Extend类的虚函数表,在Base类声明中找到show()声明的位序0,访问Extend类的虚函数表的位置0,得到show的函数地址。注意若只有基类定义了virtual void show();而子类未重写virtual void show();即上面的函数(2),则Extend虚函数表中的位序0中存放的地址仍然是Base类中定义的virtual void show()函数,而若Extend类中重写了Base类中的virtual void show()方法,则Extend的虚函数表中位序0的函数地址将被更新为Extend中新重写的函数地址。从而调用pBase->show()时将产生多态的现象。
总结:当调用pBase->show();时,执行的步骤:
1, 判断Base类中show是否为虚函数。
2, 若不是虚函数则找到pBase所指向的对象所属类Base。执行Base::show()。若是虚函数则执行步骤3.
3, 访问pBase所指对象的虚函数表指针得到pBase所指对象所在类的虚函数表。
4, 查找Base中show()在声明时的位序为x,到步骤3得到的虚函数表中找到位序x,从而得到要执行的show的函数地址。
5, 根据函数地址和Base中声明的show的函数类型(形参和返回值)访问地址所指向的函数。
以上为虚函数的工作机制。
注意只有用virtual修饰的成员方法才会放到虚函数表中去。
子类对父类函数的隐藏将导致无法通过子类对象访问基类的成员方法。
因此给出以下建议:
1、 若要在子类中重新定义父类的方法(有virtual为重写,无virtual为隐藏),则应确保子类中的函数声明和父类函数声明中的形参完全一样。但返回值类型是基类引用/指针的成员函数在重新定义时可以返回子类的引用/指针(返回值协变),这是由于子类的对象可以赋给基类引用/指针。
2、 若基类中声明了函数的重载版本,则在派生类中重新定义时应该重新定义所有基类的重载版本。这是因为,重新定义一个函数,其他的基类重载版本将被隐藏,导致子类无法使用这些基类的成员方法。所以需要每个都重新定义。若一些父类的重载版本,子类确实不需要修改,则由于重新定义了一个重载版本,即使有些重载版本不需要修改也要重新定义,在定义体中直接调用基类的成员方法(使用作用于限定符访问)。
3、 从虚函数的实现机制可以看到要想在子类中实现多态需要满足三个重要的条件。(1)在基类中函数声明为虚函数。(2)在子类中,对基类的虚函数进行了重写。(3)基类的指针指向了子类的对象。
隐藏测试:
情况一:子类中只定义了show(),那么子类对象将不能访问父类的show(int)
#include<iostream> using namespace std; class Parent{ public: void show() { cout<<"Parent::show()"<<endl; } void show(int t) { cout<<"Parent::show()"<<t<<endl; } }; class Child: public Parent{ public: void show() { cout<<"Child::show()"<<endl; } }; int main() { Child c; c.show(); getchar(); return 0; }
Child::show()
情况二:多态 父类中没有定义虚函数show,则使用静态联编,根据指针p的类型来决定调用哪个类的函数
#include<iostream> using namespace std; class Parent{ public: void show() { cout<<"Parent::show()"<<endl; } void show(int t) { cout<<"Parent::show()"<<t<<endl; } }; class Child: public Parent{ public: void show() { cout<<"Child::show()"<<endl; } }; int main() { Child c; Parent *p=&c; p->show(); p->show(2); getchar(); return 0; }
Parent::show()
Parent::show(2)
情况三:多态 父类中定义了虚函数show,查找show在Base类中声明的位置在父类中所有虚函数声明中的位序。然后找到p所指对象的所属类的虚函数表中访问该位序的函数指针,从而得到要执行的函数。
#include<iostream> using namespace std; class Parent{ public: virtual void show() { cout<<"Parent::show()"<<endl; } void show(int t) { cout<<"Parent::show()"<<t<<endl; } }; class Child: public Parent{ public: void show() { cout<<"Child::show()"<<endl; } }; int main() { Child c; Parent *p=&c; p->show(); p->show(2); getchar(); return 0; }
Child::show()
Parent::show(2)
情况四:多态 如果子类中没有重新父类方法,那么复制父类的虚函数表
#include<iostream> using namespace std; class Parent{ public: virtual void show() { cout<<"Parent::show()"<<endl; } void show(int t) { cout<<"Parent::show()"<<t<<endl; } }; class Child: public Parent{ public: void show() { cout<<"Child::show()"<<endl; } }; class GrandChild:public Child{ }; int main() { GrandChild c; Parent *p=&c; p->show(); p->show(2); getchar(); return 0; }
Child::show()
Parent::show(2)
情况五:多态 如果子类重写了父类的虚函数,那么通过p指针所在类的该虚函数的位序访问子类中的对应的函数,如果子类重写的是非虚函数,那么通过指针p只能访问p所在类的该函数
#include<iostream> using namespace std; class Parent{ public: virtual void show() { cout<<"Parent::show()"<<endl; } void show(int t) { cout<<"Parent::show()"<<t<<endl; } }; class Child: public Parent{ public: void show() { cout<<"Child::show()"<<endl; } void show(int t) { cout<<"Child::show()"<<t<<endl; } }; class GrandChild:public Child{ void show() { cout<<"GrandChild::show()"<<endl; } void show(int t) { cout<<"GrandChild::show()"<<t<<endl; } }; int main() { GrandChild c; Parent *p=&c; p->show(); p->show(2); getchar(); return 0; }
GrandChild::show()
Parent::show(2)