C++虚函数

转自:http://blog.csdn.net/haoel/article/details/1948051

测试代码:

class Base {

public:

    virtual void f() { cout << "Base::f" << endl; }

    virtual void g() { cout << "Base::g" << endl; }

    virtual void h() { cout << "Base::h" << endl; }

};


    Base b;
    Fun pFun = NULL;
    cout << "虚函数表地址:" << (int*)(&b) << endl;
    cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;
    // Invoke the first virtual function 
    pFun = (Fun)*((long long*)*(long long*)(&b));
    pFun();

    pFun = (Fun)*((long long*)*(long long*)(&b) + 1);
    pFun();


    pFun = (Fun)*((long long*)*(long long*)(&b)+2);
    pFun();

其中:*(long long*)(&b)是把对象b最前面8字节内容取出;((long long*)*(long long*)(&b)),把取出的内容看作地址,也就是虚函数表的开始地址,即Base::f()所在位置;*((long long*)*(long long*)(&b))把虚函数表第一个位置内容取出;(Fun)*((long long*)*(long long*)(&b)内容作为地址,赋给函数指针。
结果:
虚函数表地址:0000006A8A65FB78
虚函数表 — 第一个函数地址:000000004CEA0498
Base::f
Base::g
Base::h

C++虚函数_第1张图片
其它成员指的是member,不包括member function。成员函数存放在代码区域,同过对象空间内的指针调用。
注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。

一般继承(无虚函数覆盖)
C++虚函数_第2张图片
我们可以看到下面两点:
虚函数按照其声明顺序放于表中。
父类的虚函数在子类的虚函数前面。

一般继承(有虚函数覆盖)
C++虚函数_第3张图片
我们从表中可以看到下面两点:
覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
没有被覆盖的函数依旧。

多重继承(没有虚函数覆盖)
假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。
C++虚函数_第4张图片
C++虚函数_第5张图片
我们可以看到:
每个父类都有自己的虚表。
子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

多重继承(有虚函数覆盖)
C++虚函数_第6张图片
C++虚函数_第7张图片
我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

    Derive d;
    Base1 *b1 = &d;
    Base2 *b2 = &d;
    Base3 *b3 = &d;

    b1->f(); //Derive::f()
    b2->f(); //Derive::f()
    b3->f(); //Derive::f()

    b1->g(); //Base1::g()
    b2->g(); //Base2::g()
    b3->g(); //Base3::g()
    long long** p = (long long**)&b;
    pFun = (Fun)p[0][0];
    pFun();
    pFun = (Fun)p[0][1];
    pFun();
    pFun = (Fun)p[0][2];
    pFun();

此处注意数组指针和二级指针的区别。
注:如果基类的析构函数声明为虚函数,那么在虚函数表base1中,Deriver::g1前会有base1析构函数的地址;在vs的虚函数表中base1析构函数后的地址不会显示,但确实存在。

你可能感兴趣的:(C++,虚函数)