c++虚函数表之我见解

    技术2022-05-19  19

          最近复习一些基础,发现很多非常细节的东西虽然以前基本都有接触过,但是都忘记了,主要原因就是以前都是被动式地浏览,只能达到可以看懂的程度,而不能消化甚至很好地灵活使用,所以,我就想通过写博文的形式来加深自己的理解和记忆。废话少说,今天要记录的是我对虚函数表的理解。

          虚函数是在类中被声明为virtual的成员函数,当编译器看到通过指针或引用调用此类函数时,对其执行晚绑定,即通过指针(或引用)指向的类的类型信息来决定该函数是哪个类的。通常此类指针或引用都声明为基类的,它可以指向基类或派生类的对象。虚函数是c++实现动态多态的手段。下面就下来感性地理解一下C++是怎么实现虚函数的动态绑定的。下面是我的例程(编译器是VS2005,系统是windows 7 32位):

    #include<stdio.h> typedef void (*pFun)(void); class A { public: A(){ v=4; printf("A::Constructor/n"); } void f(){ printf("A/n"); } virtual ~A(){ printf("A::Destructor/n"); } virtual void print(){ printf("A::print/n"); } virtual void setVA(){ printf("A::setVA/n"); printf("A::v=%d/n",v); } int v; }; class B { public: B(){ v=5; printf("B::Constructor/n"); } virtual ~B(){ printf("B::Destructor/n"); } void f(){ printf("A/n"); } virtual void print(){ printf("B::print/n"); } virtual void setVB(){ printf("B::setVB/n"); } int v; }; class C { public: C(){ v=6; printf("C::Constructor/n"); } virtual ~C(){ printf("C::Destructor/n"); } void f(){ printf("A/n"); } virtual void print(){ printf("C::print/n"); } virtual void setVC(){ printf("C::setVC/n"); } int v; }; class D:public A,B, C { public: D(){ printf("D::Constructor/n"); } ~D(){ printf("D::Destructor/n"); } virtual void print(){ printf("D::print/n"); } virtual void setVD(){ printf("D::setVD/n"); } C c; }; int _tmain(int argc, _TCHAR* argv[]) { D d; printf("sizeof(D)=%d/n",sizeof(d)); printf("A virtual pointer address=%d/n",&d); //输出各个基类的成员变量v的地址 printf("A.v address=%d/n",&(((A *)&d)->v)); printf("B.v address=%d/n",&(((B *)&d)->v)); printf("C.v address=%d/n",&(((C *)&d)->v)); printf("D.c address=%d/n",&(d.c)); //输出各个基类的成员变量v的值 printf("A::v=%d/n",*((int *)((int *)&d + 1))); printf("B::v=%d/n",*((int *)((int *)&d + 3))); printf("C::v=%d/n",*((int *)((int *)&d + 5))); //根据虚函数表的指针,调用表中的各个虚函数 pFun fun; fun = (pFun)*((int *)(*(int *)&d)+1); fun(); fun = (pFun)*((int *)(*(int *)&d) + 2); fun(); fun = (pFun)*((int *)(*(int *)&d) + 3); fun(); fun = (pFun)*((int *)((int *)(*(int *)((int *)&d + 2)) + 1)); fun(); fun = (pFun)*((int *)((int *)(*(int *)((int *)&d + 2)) + 2)); fun(); fun = (pFun)*((int *)((int *)(*(int *)((int *)&d + 4)) + 1)); fun(); fun = (pFun)*((int *)((int *)(*(int *)((int *)&d + 4)) + 2)); fun(); return 0; }

    上例的输出结果如下所示:

    A::ConstructorB::ConstructorC::ConstructorC::ConstructorD::Constructorsizeof(D)=32D virtual pointer address=1244928A.v address=1244932B.v address=1244940C.v address=1244948D.c address=1244952A::v=4B::v=5C::v=6D::printA::setVAA::v=52460D::setVDD::printB::setVBD::printC::setVCD::DestructorC::DestructorC::DestructorB::DestructorA::Destructor

     

           程序中的D类由A、B、C父类派生,各个父类都有两个虚函数,其中子类D重写了print()方法,大概的程序逻辑就是这样。在结果中我们可以看到,我输出了D对象的大小还有其初始地址,然后通过虚函数表来调用虚函数。下面是从结果推测出的内存分布图

     

          看到这个内存分布图,相信不难理解上面的输出结果了吧。

          本例除了解析虚函数表外,还附带地展示了多继承条件下父类子类的构造函数的调用顺序和析构函数的调用顺序,构造函数的调用顺序是:父类(按继承的顺序)->子类的成员对象->子类。析构函数的调用顺序刚好和构造函数的相反。之所以是这样一个顺序,也是有根据的,在C++中,子类继承父类后,就拥有了父类的成员,所以就必须要先把父类的成员构造好了,才能构造子类,同理,子类中的对象也是被子类所拥有,所以也需要在子类被构造前就构造好。反过来,析构的调用顺序也是因为这个原因。


    最新回复(0)