文章链接:http://blog.csdn.net/haoel/article/details/1948051
虚函数表
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。
对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其内容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中。
C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置。
代码:
class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } virtual ~Base() {} }; typedef void(*Fun) (void); int main() { Base b; Fun pFun = NULL; cout << "虚函数表地址" << (int *)(&b) << endl; cout << "虚函数表--第一个虚函数地址" << (int*)*(int*)(&b)<< endl; pFun = (Fun)*(int *)*(int*)(&b); pFun(); return 0; }
输出:
虚函数表地址:0x22ff48 虚函数表--第一个虚函数地址:0x4041d0 Base::f
对*(int*)*(int*)(&b)的理解:
如上图所示:实际就是一个指向指针的指针,需要两次解引用才能得到存储在虚函数表中的函虚数指针。
B:Base类对象
&b: Base类对象的首地址,同时也是指向虚函数指针表的指针的首地址,它们只是值相同
(int*)(&b):其值仍为Base类对象首地址,同时也是指向虚函数指针表的指针的首地址,对其进行强制类型转换成(int
*)的目的是当对其执行间接访问操作时,可以访问到以该地址开始的连续四个字节的内存空间,因为指针总是要占四个字节的。
*(int*)(&b):第一次解引用。本例中作为右值使用,其值为虚函数指针表的首地址,同时也是虚函数指针表中第一个函数指针的地址。当作左值使用时为指向虚函数指针表的指针本身。
(int *)*(int*)(&b):其值仍为虚函数指针表的首地址,同时也是虚函数指针表中第一个函数指针的地址,对其进行强制类型转换成(int
*)的目的是当对其执行间接访问操作时,可以访问到以该地址开始的连续四个字节的内存空间,因为指针总是要占四个字节的。
*(int*)*(int *)(&b):第二次解引用。作右值使用时为虚函数指针表中第一个函数指针,可以调用虚函数。
一般继承(无虚函数覆盖)
代码如下:
using namespace std; typedef void(*Fun) (void); class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } virtual ~Base() {} }; class Derived: public Base { public: virtual void f1() { cout << "Derived::f1" << endl; } virtual void g1() { cout << "Derived::g1" << endl; } virtual void h1() { cout << "Derived::h1" << endl; } virtual ~Derived() {} }; int main() { Base b; Derived d; Fun pFun = NULL; int **pVtab = (int**)(&d); for(int i = 0; NULL != (Fun)pVtab[0][i] && i<3; i++) { pFun = (Fun)pVtab[0][i]; pFun(); } return 0; }
按文章所述,派生类对象d的虚函数表如下:
但未得到验证。但代码中for循环的条件为"i<3"时,可以输出继承自Base类的3个虚函数。
但将for循环的条件改为”i<6”时,执行时会报读内存错误,在gcc和VS2010下都是如此。下图是在VS2010下得到的虚函数表:
一般继承(有虚函数覆盖)
代码如下:
#include <iostream> using namespace std; typedef void(*Fun) (void); class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } virtual ~Base() {} }; class Derived: public Base { public: virtual void f1() { cout << "Derived::f1" << endl; } virtual void g1() { cout << "Derived::g1" << endl; } virtual void h1() { cout << "Derived::h1" << endl; } virtual void f() { cout << "Derived::f" << endl; } virtual void g() { cout << "Derived::g" << endl; } virtual void h() { cout << "Derived::h" << endl; } virtual ~Derived() {} }; int main() { Base b; Derived d; Fun pFun = NULL; int **pVtab = (int**)(&d); for(int i = 0; NULL != (Fun)pVtab[0][i] && i<6; i++) { pFun = (Fun)pVtab[0][i]; pFun(); } return 0; }
按文章所述,派生类对象d的虚函数表如下:
代码中for循环的条件为"i<3"时,可以输出派生类d重载的三个虚函数,说明派生类的f(), g(), h()函数将继承自Base类的这个三个函数覆盖。
但将for循环的条件改为”i<6”时,执行时仍会报读内存错误。下图是在VS2010下得到的虚函数表:
多重继承(无虚函数覆盖)
代码如下:
#include <iostream> using namespace std; typedef void(*Fun) (void); class Base1 { public: virtual void f() { cout << "Base1::f" << endl; } virtual void g() { cout << "Base1::g" << endl; } virtual void h() { cout << "Base1::h" << endl; } virtual ~Base1() {} }; class Base2 { public: virtual void f() { cout << "Base2::f" << endl; } virtual void g() { cout << "Base2::g" << endl; } virtual void h() { cout << "Base2::h" << endl; } virtual ~Base2() {} }; class Base3 { public: virtual void f() { cout << "Base3::f" << endl; } virtual void g() { cout << "Base3::g" << endl; } virtual ~Base3() {} }; class Derived: public Base1, public Base2, public Base3 { public: virtual void f1() { cout << "Derived::f1" << endl; } virtual void g1() { cout << "Derived::g1" << endl; } virtual void h1() { cout << "Derived::h1" << endl; } virtual ~Derived() {} }; int main() { Derived d; Fun pFun = NULL; int **pVtab = (int**)(&d); for(int i = 0; i<3; i++) { for(int j = 0; NULL != (Fun)pVtab[i][j] && i<6; j++) { pFun = (Fun)pVtab[i][j]; pFun(); } } return 0; }
按文章所述,派生类对象d的虚函数表如下: