当前位置 博文首页 > 文章内容

    一文读懂C++中的继承之菱形继承(案例分析)

    作者:shunshunshun18 栏目:未分类 时间:2021-04-01 14:43:39

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    前言

    我们上一篇说了世间万物都有一个继承体制,或多或少子类继承了父类的某些特征,但大多都是单向继承,但是就有些特例他就是多继承,比如:

    我们从图片中就可以看到,两栖动物它既继承了水生动物的一部分特性,也继承了陆地动物的一些特性,那么我们的代码,会不会也会有这种多继承现象呢,我们一起来看一下。

    提示:以下是本篇文章正文内容,下面案例可供参考

    一、什么是多继承?

    1.单继承

      我们来看一个图先了解一下单继承,再看有什莫区别

    也就是说,一个子类只有一个直接父类时称这个继承关系为单继承

    2.多继承

    我们把 一个子类有两个或以上直接父类时称这个继承关系为多继承

    我们看一下代码,看看多继承中存在哪些问题。

    //基类A
    class A 
    {
    public:
      A() :m_data(1)
      {
      }
      ~A(){}
     
    public:
      int m_data;   //同名变量
      
    };
    //基类B
    class B
    {
    public:
      B() :m_data(1)
      {
      }
      ~B(){}
     
    public:
      int m_data;   //同名变量
      
    };
     
    class C : public A, public B
    {
     
    };
     
    int main()
    {
      C Data;
      //Data.m_data = 10;  //错误, 提示指向不明确 不能够分辨m_data到底是谁的
      //只有通过域成员运算符给其明确指出才可以访问,
      Data.A::m_data = 5; 
      Data.B::m_data =10;
      
      std::cout << Data.A::m_data << "  " << Data.B::m_data << std::endl;
     
      return 0;
    }

    通过上面的代码我们明显看的出,多继承体系中存在二义性问题。指如果有同名的数据成员 那么就无法直接通过变量名进行读取,需要通过域(::)成员运算符进行区分

    就好像一个人说 去给老师送个东西去,那么多老师,你无法确定他所指的是哪一个老师,必须指名道姓,我们才可以区分。一个道理

    二、菱形继承

    我们先来画张图,理解一下什么叫做菱形继承

    我们把这种继承类型叫做菱形继承。

    那么这种继承体制,又会出现什么样的问题呢?我们通过下面的代码具体看一下。

    // 菱形继承 菱形继承是多继承的一种特殊情况。
    //菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。
     
     
    class A
    {
    public:
    	int m_a = 1;
    };
    class B :public A
    {
    public:
    	int m_b = 2;
    };
    class C :public A
    {
    public:
    	int m_c = 3;
    };
    class D :public B, public C
    {
    public:
    	int m_d = 4;
    };
    void main()
    {
    	D d;
    	d.m_d = 40;
    	d.m_c = 30;
    	d.m_b = 20;
    	//.m_a = 10;// 二义性
    	// 不能够访问 因为B 和C分别继承了A的m_a
    	//但是D 继承了B和C的m_a 所以D不能够分辨m_a到底是谁的
    	d.B::m_a = 100;
    	d.C::m_a = 200;
    	// 这样的话 就造成了m_a 有两个空间 一个B的100 一个C的200
    }

    我们根据上面的情况不难看出,菱形继承中也存在数据的二义性,这里的二义性是由于他们间接都有相同的基类导致的。 这种菱形继承除了带来二义性之外,还会有有数据冗余浪费内存空间

    何为空间浪费,数据冗余,我们画图展示一下。

    那么该怎样解决这种问题,

    1.虚基类的引入

    C++中引入了虚基类,其作用是 在间接继承共同基类时只保留一份基类成员。

    我么看一下代码

    class A
    {
    public:
    	int m_a = 1;
    };
    class B :virtual public A
    {
    public:
    	int m_b = 2;
    };
    class C :virtual public A
    {
    public:
    	int m_c = 3;
    };
    class D :public B, public C
    {
    public:
    	int m_d = 4;
    };
    void main()
    {
    	D d;
    	d.m_d = 40;
    	d.m_c = 30;
    	d.m_b = 20;
    	d.m_a = 100;// 让B C虚拟继承 A 加关键字virtual
    }

    我们可以看到很好解决了二义性和数据冗余的问题,那么它是具体怎么来做的,

    2.虚基表的引入

    这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到了A。

    具体我们画一张图

    这里的B  C就像两个人分别在两个不同的地方,但是都要去A这个地方,保存了A的地址,那么就可以用地图去导航,但是因为两个人所在的地方不一样,所以到A的距离肯定也会不一样,也就是A的偏移量肯定会不一样(这样说会比较好理解一点)。

    总结

    很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有 菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度上就有问题并且菱形继承虚继承也带来了性能上的损耗(因为多开了地址来存放偏移量)。