运行结果:

可以看到,类Child和类Base分别拥有自己的虚函数表指针vfptr和虚函数表vftable。
下面这段代码,说明了父类和基类拥有不同的虚函数表,同一个类拥有相同的虚函数表,同一个类的不同对象的地址(存放虚函数表指针的地址)不同。
int _tmain(int argc, _TCHAR* argv[])
{
Base b;
Child c1,c2;
cout<<"Base类的虚函数表的地址:"<<(int*)(*(int*)(&b))<<endl;
cout<<"Child类c1的虚函数表的地址:"<<(int*)(*(int*)(&c1))<<endl; //虚函数表指针指向的地址值
cout<<"Child类c2的虚函数表的地址:"<<(int*)(*(int*)(&c2))<<endl;
system("pause");
return 0;
}

在定义该派生类对象时,先调用其基类的构造函数,然后再初始化vfptr,最后再调用派生类的构造函数( 从二进制的视野来看,所谓基类子类是一个大结构体,其中this指针开头的四个字节存放虚函数表头指针。执行子类的构造函数的时候,首先调用基类构造函数,this指针作为参数,在基类构造函数中填入基类的vfptr,然后回到子类的构造函数,填入子类的vfptr,覆盖基类填入的vfptr。如此以来完成vfptr的初始化)。也就是说,vfptr指向vftable发生在构造函数期间完成的。
动态绑定例子:
#include "stdafx.h"
#include<iostream>
using namespace std;
class Base
{
public:
virtual void fun1(){
cout<<"base fun1!n";
}
virtual void fun2(){
cout<<"base fun2!n";
}
virtual void fun3(){
cout<<"base fun3!n";
}
int a;
};
class Child:public Base
{
public:
void fun1(){
cout<<"Child fun1n";
}
void fun2(){
cout<<"Child fun2n";
}
virtual void fun4(){
cout<<"Child fun4n";
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Base* p=new Child;
p->fun1();
p->fun2();
p->fun3();
system("pause");
return 0;
}
运行结果:

结合上面的内存布局:

其实,在new Child时构造了一个子类的对象,子类对象按上面所讲,在构造函数期间完成虚函数表指针vfptr指向Child类的虚函数表,将这个对象的地址赋值给了Base类型的指针p,当调用p->fun1()时,发现是虚函数,调用虚函数指针查找虚函数表中对应虚函数的地址,这里就是&Child::fun1。调用p->fun2()情况相同。调用p->fun3()时,子类并没有重写父类虚函数,但依旧通过调用虚函数指针查找虚函数表,发现对应函数地址是&Base::fun3。所以上面的运行结果如上图所示。










