C++多态(二)虚表剖析以及菱形继承

来源:互联网 发布:java连接文件服务器 编辑:程序博客网 时间:2024/06/04 18:37

在上一篇中C++多态(一)对象类型与虚函数,我简单说明了虚函数和它的内存分布,在这篇中我将就虚表结构进行一个剖析,深入了解虚表的内部结构。

一、继承体系同名函数的关系:

这里写图片描述

函数重载

我们在类的成员函数的时候就已经接触到了函数重载,但是并不知道它是多态的一种表现,
函数重载的特征:
1、同一作用域(基类和派生类不是同一作用域)
2、函数名字相同
3、参数不同(参数类型不同,参数个数不同,排布顺序不同)
4、返回值类型可以不同;

单继承函数重写(覆盖)

1、不在同一作用域(分别在基类和派生类)
2、函数名相同/参数相同/返回值相同(协变除外)
3、基类函数必须有virtual关键字
4、访问修饰符可以不同
协变:基类/派生类返回值类型是基类/派生类的指针或引用

在函数重写(覆盖)中,一般分为基类中有虚函数,在派生类中进行重写和没有进行重写;
1、用对象调用函数

class Base{public:    virtual void FunTset1()    {        cout << "Base::FunTest1()" << endl;    }    virtual void FunTset2()    {        cout << "Base::FunTest()" << endl;    }    virtual void FunTset3()    {        cout << "Base::FunTest3()" << endl;    }};class Derived:public Base{public:    virtual void FunTset1()    {        cout << "Derived::FunTest1()" << endl;    }    virtual void FunTset2()    {        cout << "Derived::FunTest()" << endl;    }};int main(){    Base b;    Derived d;    b.FunTset1();    b.FunTset2();    b.FunTset3();    d.FunTset1();    d.FunTset2();    d.FunTset3();    return 0;}

结果如下:
这里写图片描述

    Base b;    b.FunTset1();    b.FunTset2();    b.FunTset3();

先对基类进行分析:
这里写图片描述
从图中可以看到,内存1的窗口中还是只有一个虚表的地址,在内存3的窗口中可以看到有3个地址分别对应到汇编代码中FunTest1(),FunTest2(),FunTest3()的函数的地址。

    Derived d;    d.FunTset1();    d.FunTset2();    d.FunTset3();

对派生类进行分析:
这里写图片描述
图中的内容和基类中差不了多少,我们把两张不放在一起比较一下,就发现只有一个地方是一样的就是FunTest3()中的函数对应地址和基类中的函数对应地址是完全一样的,所以就能发现FunTest1()和FunTest2()函数发生了覆盖;
2、用指针/引用调用函数

class Base{public:    virtual void FunTset1()    {        cout << "Base::FunTest1()" << endl;    }    virtual void FunTset2()    {        cout << "Base::FunTest()" << endl;    }    virtual void FunTset3()    {        cout << "Base::FunTest3()" << endl;    }};class Derived:public Base{public:    virtual void FunTset1()    {        cout << "Derived::FunTest1()" << endl;    }    virtual void FunTset2()    {        cout << "Derived::FunTest2()" << endl;    }};int main(){    Base* pb;    Derived d;    pb = &d;    d.FunTset1();    d.FunTset2();    d.FunTset3();    return 0;}

这里写图片描述
结果和对象调用时一样的;但是我们查看一个地址:
这里写图片描述
当我们用指针或引用去调用函数时,对指针进行取地址后,进入它的地址中,可以看到和&d的地址是一样的,说明指针指向的虚表就是对象d所拥有的虚表;

多重继承函数重写(覆盖)

class Base1{public:    virtual void FunTest1()    {        cout << "Base1::FunTest1()" << endl;    }    int b1 = 1;};class Base2{public:    virtual void FunTest2()    {        cout << "Base1::FunTest2()" << endl;    }    int b2 = 2;};class Base3{public:    virtual void FunTest3()    {        cout << "Base1::FunTest3()" << endl;    }    int b1 = 3;};class Derived :public Base1, public Base2, public Base3{public:    void FunTest1()    {        cout << "Derived::FunTest1()" << endl;    }    void FunTest2()    {        cout << "Derived::FunTest2()" << endl;    }    void FunTest3()    {        cout << "Derived::FunTest3()" << endl;    }    virtual void FunTest4()    {        cout << "Derived::FunTest4()" << endl;    }    int d = 4;};int main(){    Derived d;    Base1& base1 = d;    base1.FunTest1();    Base2& base2 = d;    base2.FunTest2();    Base3& base3 = d;    base3.FunTest3();    d.FunTest4();    return 0;}

这里写图片描述
结果很简单,但是在多重继承的内存分配中得让我们慢慢的来看一下内存:
这里写图片描述
对于上面这幅图,我解释一下 :
1、在内存1中是查看了d;的地址可以分为4块,第一部分为继承Base1,第二部分为继承Base2,第三部分为继承Base3的,在最后一部分是Derived自己的成员;
2、在内存2中可以看到,是对Base1的地址进行了查看显示出了两个地址,其中第一个为Base1::FunTest()函数的地址,第二个是Derived中成员函数的地址,
3、是在汇编中截出FunTest4()的地址进行验证;
所以,总结一下:
1、通过基类的指针/引用调用虚函数时,调用基类还是派生类的虚函数,要根据运行时引用/指针实际引用的类型确定
2、调用非虚函数时,无论基类指向的是何种类型,都调用基类的函数;
3、派生类的虚函数表生成:①:先拷贝基类的虚函数表,②:如果派生类函数重写了基类的某个虚函数,就替换同位置的基类虚函数,③:跟上派生类自己新定义的虚函数;
4、在多重继承中派生类自己的虚函数是放在了第一个继承基类的虚表后面;

二、菱形虚拟继承:

C++继承(三)通过菱形继承看virtual继承在这篇博文中我已经简单介绍了虚拟继承,在虚拟继承中,我们能看到解决了数据的冗余与二义性,下来我们来看看菱形虚拟继承:

class B{public:    virtual void FunTest1()    {        cout << "B::FunTest1()" << endl;    }    int b = 1;};class C1:virtual public B{public:    virtual void FunTest2()    {        cout << "C1::FunTest2()" << endl;    }    int c1 = 2;};class C2:virtual public B{public:    virtual void FunTest3()    {        cout << "C2::FunTest3()" << endl;    }    int c2 = 3;};class D :virtual public C1, virtual public C2{public:    virtual void FunTest4()    {        cout << "D::FunTest4()" << endl;    }    int d = 4;};int main(){    D d;    B& b = d;    b.FunTest1();    C1& c1 = d;    c1.FunTest2();    C2& c2 = d;    c2.FunTest3();    d.FunTest4();    return 0;}

程序的结果:
这里写图片描述
结果还是很简单,但是在运行中的内存调用还是比较难的:
这里写图片描述
通过上图的图解,可以看到内存中,D的内存分配:
在D中虚拟继承的是C1和C2的成员函数,而C1和C2继承的是B中的成员函数,在带有虚函数的虚拟继承中,我们可以从上图看都有4个虚表,每个函数都有一个自己的虚表结构指向自己的虚表地址;

1 0
原创粉丝点击