class Shape { public: virtual void draw(){} virtual void color(){} private: int m_data1; int m_data2; }; class Circle:public Shape { public: void draw() { cout << "画圆" << endl; } private: int m_data3; }; class Ellipse :public Circle { public: void draw() { cout << "画椭圆" << endl; } private: int m_data1; //与shape中的数据同名 int m_data4; };
在开发人员命令提示工具中输入命名: cd 源代码所在的文件地址 dir 回车 cl /d1 reportSingleClassLayout类名 对象.cpp可以查看虚函数指针和虚函数表。
class Shape size(12): +--- 0 | {vfptr} 4 | m_data1 8 | m_data2 +--- Shape::$vftable@: | &Shape_meta | 0 0 | &Shape::draw 1 | &Shape::color Shape::draw this adjustor: 0 Shape::color this adjustor: 0 class Circle size(16): +--- | +--- (base class Shape) 0 | | {vfptr} 4 | | m_data1 8 | | m_data2 | +--- 12 | m_data3 +--- Circle::$vftable@: | &Circle_meta | 0 0 | &Circle::draw 1 | &Shape::color Circle::draw this adjustor: 0 class Ellipse size(24): +--- | +--- (base class Circle) | | +--- (base class Shape) 0 | | | {vfptr} 4 | | | m_data1 8 | | | m_data2 | | +--- 12 | | m_data3 | +--- 16 | m_data1 20 | m_data4 +--- Ellipse::$vftable@: | &Ellipse_meta | 0 0 | &Ellipse::draw 1 | &Shape::color Ellipse::draw this adjustor: 0
画成图表示
由此可见:父类中写了一个虚函数,类的内部发生了改变,多了一个指针(4B)vptr(虚函数指针),指向一个虚函数表,表内写的是虚函数的入口地址,当子类继承父类的时候,子类的虚函数表和父类的虚函数表一样,当子类重写父类虚函数时候,会把父类的函数入口替换成自己的函数入口。
动态绑定
1 Circle circle; 2 Shape shape = (Shape)circle; 3 shape.draw(); //shape是一个对象不是指针,所以是静态绑定 4 5 Shape *pa = new Circle(); //向上转型 6 pa->draw(); //动态绑定
之所以是动态绑定,是因为编译器会把第六行代码转换成(*p->vptr[n])(p),通过指针p(p指向Circle类型)找到Circle的虚函数指针,通过虚函数指针找到虚函数表,取出表中第n个函数地址,通过函数指针来调用,再p当成this指针传入函数中。