• 【C++ 继承 | 虚函数表 02】C++虚函数表剖析 ②


    多重继承(无虚函数覆盖)

    下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

    class Base1
    {
    public:
        virtual void f() { cout << "Base1::f" << endl; }  //虚函数定义
        virtual void g() { cout << "Base1::g" << endl; }
        virtual void h() { cout << "Base1::h" << endl; }
    };
    
    class Base2
    {
    public:
        virtual void f() { cout << "Base2::f" << endl; }  //虚函数定义
        virtual void g() { cout << "Base2::g" << endl; }
        virtual void h() { cout << "Base2::h" << endl; }
    };
    
    class Base3
    {
    public:
        virtual void f() { cout << "Base3::f" << endl; }
        virtual void g() { cout << "Base3::g" << endl; }
        virtual void h() { cout << "Base3::h" << endl; }
    };
    
    class Derive :public Base1, public Base2, public Base3 //多继承的情况——无虚继承覆盖
    {
    public:
        virtual void f1() { cout << "Derive::f1" << endl; } //虚函数定义
        virtual void g1() { cout << "Derive::g1" << endl; }
    };

    图解:

    对于子类实例中的虚函数表,是下面这个样子:

    我们可以看到:
    1) 每个父类都有自己的虚表。
    2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

    这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

    多重继承(有虚函数覆盖)

    下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。

    注意:子类只overwrite了父类的f()函数,而还有一个是自己的函数(我们这样做的目的是为了用g1()作为一个标记来标明子类的虚函数表)。而且每个类中都有一个自己的成员变量:

    类继承的源代码如下所示:父类的成员初始为10,20,30,子类的为100。

    class Base1 {
    public:
        int ibase1;
        Base1() :ibase1(10) {}
        virtual void f() { cout << "Base1::f()" << endl; }
        virtual void g() { cout << "Base1::g()" << endl; }
        virtual void h() { cout << "Base1::h()" << endl; }
    
    };
    
    class Base2 {
    public:
        int ibase2;
        Base2() :ibase2(20) {}
        virtual void f() { cout << "Base2::f()" << endl; }
        virtual void g() { cout << "Base2::g()" << endl; }
        virtual void h() { cout << "Base2::h()" << endl; }
    };
    
    class Base3 {
    public:
        int ibase3;
        Base3() :ibase3(30) {}
        virtual void f() { cout << "Base3::f()" << endl; }
        virtual void g() { cout << "Base3::g()" << endl; }
        virtual void h() { cout << "Base3::h()" << endl; }
    };
    
    class Derive : public Base1, public Base2, public Base3 {
    public:
        int iderive;
        Derive() :iderive(100) {}
        virtual void f() { cout << "Derive::f()" << endl; }
        virtual void g1() { cout << "Derive::g1()" << endl; }
    };

    使用图片表示是下面这个样子:

    我们可以看到:

    • 每个父类都有自己的虚表。
    • 子类的成员函数被放到了第一个父类的表中。
    • 内存布局中,其父类布局依次按声明顺序排列。
    • 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

    砖石型-重复继承

    钻石型-重复继承的源代码如下所示。其中,每个类都有两个变量,一个是整形(4字节),一个是字符(1字节),而且还有自己的虚函数,自己overwrite父类的虚函数。如子类D中,f()覆盖了超类的函数, f1() 和f2() 覆盖了其父类的虚函数,Df()为自己的虚函数。

    class B
    {
    public:
        int ib;
        char cb;
    public:
        B() :ib(0), cb('B') {}
        virtual void f() { cout << "B::f()" << endl; }
        virtual void Bf() { cout << "B::Bf()" << endl; }
    };
    
    class B1 : public B
    {
    public:
        int ib1;
        char cb1;
    public:
        B1() :ib1(11), cb1('1') {}
        virtual void f() { cout << "B1::f()" << endl; }
        virtual void f1() { cout << "B1::f1()" << endl; }
        virtual void Bf1() { cout << "B1::Bf1()" << endl; }
    };
    
    class B2 : public B
    {
    public:
        int ib2;
        char cb2;
    public:
        B2() :ib2(12), cb2('2') {}
        virtual void f() { cout << "B2::f()" << endl; }
        virtual void f2() { cout << "B2::f2()" << endl; }
        virtual void Bf2() { cout << "B2::Bf2()" << endl; }
    };
    
    class D : public B1, public B2
    {
    public:
        int id;
        char cd;
    public:
        D() :id(100), cd('D') {}
        virtual void f() { cout << "D::f()" << endl; }
        virtual void f1() { cout << "D::f1()" << endl; }
        virtual void f2() { cout << "D::f2()" << endl; }
        virtual void Df() { cout << "D::Df()" << endl; }
    };

    图解:

    钻石型重复继承内存布局:

    我们可以看见,最顶端的父类B其成员变量存在于B1和B2中,并被D给继承下去了。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两份,一份是B1继承而来的,另一份是B2继承而来的。所以,如果我们使用以下语句,则会产生二义性编译错误:

    1
    2
    3
    4
    D d;
    d.ib = 0;   //二义性错误
    d.B1::ib = 1;   //正确
    d.B2::ib = 2;   //正确

    注意,上面例程中的最后两条语句存取的是两个变量。虽然我们消除了二义性的编译错误,但B类在D中还是有两个实例,这种继承造成了数据的重复,我们叫这种继承为重复继承。重复的基类数据成员可能并不是我们想要的。所以,C++引入了虚基类的概念。

    多重继承(有虚函数覆盖)

    下面我们再来看看,如果发生虚函数覆盖的情况。

    其中:D自身的虚函数与B基类共用了同一个虚函数表,因此也称B为D的主基类(primary base class)。

    虚函数替换过程与前面描述类似,只是多了一个虚函数表,多了一次拷贝和替换的过程。

    虚函数的调用过程,与前面描述基本类似,区别在于基类指针指向的位置可能不是派生类对象的起始位置,以如下面的程序为例:

    参开资料

    1. 2.5 虚函数的实现及基本原理

  • 相关阅读:
    自定义标签
    ssm学习的第一个demo---crm(1)
    xml文件中的${}
    Mybatis的回顾学习
    Mapper的.xml文件的delete的参数问题
    mybatis中xml文件的${}和#{}区别
    Mybatis:通过MapperScannerConfigurer进行mapper扫描
    Spring的applicationContext.xml的疑问解析
    Spring MVC 的springMVC.xml疑问解析
    【剑指 Offer】04.二维数组中的查找
  • 原文地址:https://www.cnblogs.com/sunbines/p/16343498.html
Copyright © 2020-2023  润新知