【C++】-菱形继承(一个学了但最好不要使用的知识)

【C++】-菱形继承(一个学了但最好不要使用的知识)_第1张图片
作者:小树苗渴望变成参天大树
作者宣言:认真写好每一篇博客
作者gitee:gitee✨
作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、什么是菱形继承?
  • 二、菱形继承的效果
  • 三、怎么解决菱形继承带来的错误
  • 四、继承的总结和反思


前言

相信大家再学习了前面的基础继承知识后,对继承已经不在陌生了,今天博主就来上一个有难度的东西,需要大家对前面说到切片知识以及对解引用知识的掌握,才能更好的学习这篇讲到的内容,话不多说,我们开始进入正文


一、什么是菱形继承?

我们的C++语言是一个支持多继承的语言,特也可以支持单继承语言。
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
【C++】-菱形继承(一个学了但最好不要使用的知识)_第2张图片
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
【C++】-菱形继承(一个学了但最好不要使用的知识)_第3张图片

就是由于多继承的存在,就必然会存在下面的结构:这就是菱形继承
【C++】-菱形继承(一个学了但最好不要使用的知识)_第4张图片

二、菱形继承的效果

再上篇我们说到的继承后基类子类再内存当中是怎么样存储的,那么菱形继承的再内存的存储就会是下面这种情况:
【C++】-菱形继承(一个学了但最好不要使用的知识)_第5张图片
可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。 数据冗余是因为有两份相同的数据再Assistant对象当中,二义性是不知道访问哪个_name.有的人会说,我作为老师是一个名字,作为学生又是一个名字不就行了??但是Person里面不止一个属性,可能还有身份证号,年龄这些的。这里面的重复的属性应该有唯一性,如果是菱形继承会发生下面这种情况:
【C++】-菱形继承(一个学了但最好不要使用的知识)_第6张图片

我们发现我们的基类A里面的属性_a和_z最后的结果不统一,修改的不是同一个属性,万一我修改的属性是身份证号,这样就导致我有两个身份证号,肯定是不合理的,而且那么使用的时候就会有存在二义性,必须指定类域,这样的设计显然不符合我们的标准。

所以想要解决这个问题,我们要知道解决后我们想要的是什么效果,我们知道继承之后,子类就可以访问父类的成员,子类对象修改父类的成员变量时,父类本身的对象里面的属性也应该有被修改,需要同时变.应该是下面的效果:
【C++】-菱形继承(一个学了但最好不要使用的知识)_第7张图片

我们发现这才是我们想要的效果,一个就四个属性,我们通过BC父类来修改基类的属性,也会发生修改,最后的结果为最后一次修改的结果,这样就不会发生二义性。

为什么最上面会出现两个不同的结果??
原因是上面的菱形继承图,每个属性再内存当中都对应一个地址,而B,C对象里面都有_name属性,所有就有两个地址,指定修改哪个就修改哪个地址
【C++】-菱形继承(一个学了但最好不要使用的知识)_第8张图片

因为我们的基类属性再B里面有一个,再C里面也有一个,所以修改的不是同一个,我们来看内存当中是怎样的:【C++】-菱形继承(一个学了但最好不要使用的知识)_第9张图片

三、怎么解决菱形继承带来的错误

通过上面两个画图表示,我们已经知道菱形继承为什么会有我们所担心的问题了,那我们怎么解决这个问题,我们通过一个关键字virtual,来修饰腰部的类即如下面所示:
【C++】-菱形继承(一个学了但最好不要使用的知识)_第10张图片
这样就解决我们的问题,我们来看看内存当中是怎么存放的:
【C++】-菱形继承(一个学了但最好不要使用的知识)_第11张图片
大家清楚看看我们基类的两个属性再内存中只有一份地址,并且值为最后一次修改的值,通过virtual关键字就可以使我们的两份重复数据变成了一份,以解决了二义性的问题。


虚基表指针:
我们来看它到底使用什么办法去解决这个问题的???,我们这一部分是怎么也能访问到我们基类里面的变量的??
【C++】-菱形继承(一个学了但最好不要使用的知识)_第12张图片

【C++】-菱形继承(一个学了但最好不要使用的知识)_第13张图片
通过在内存里面的图来看,我们发现多了两个东西,这存的也不是值啊,应该是地址,我们来看看这地址指向什么地方:
【C++】-菱形继承(一个学了但最好不要使用的知识)_第14张图片

通过上图我们可以发现这个地址里面的第一行是为其他位置预留空间,第二行存放的是偏移量,方便找到基类里面的属性,只要找到第一个,其他基类的属性是连在一起的,就在下面。所以只会有一个偏移量,编译器不会把基类的属性放在不连续的地址上。因为都是归属最后一个类对象的。不是单独的属于腰部类里面的对象了,所以需要通过偏移量去找


重要:(virtual后) 那D部分中的B C为甚要去找属于自己那一部分的A,原因是我们之前说过的对象的赋值,B和C是D的父类,子类对象可以赋值给父类对象,就会出现这种情况:D d; B b=d; C c=d;通过偏移量找到属于自己那一部分的A在赋值给 b或c对象


那我们子类的指针要是赋值给父类的指针呢??
【C++】-菱形继承(一个学了但最好不要使用的知识)_第15张图片


在看看我们定义腰部类对象的时候内存是什么样的,是按照之前一样把基类的属性放在一起,还是也通过偏移量找基类的位置??
【C++】-菱形继承(一个学了但最好不要使用的知识)_第16张图片
这样设计的好处,因为编译器就按照相同的方式给你查找,编译器又不知道你是什么类型,加了virtual,都是按照虚地址映射表去找


多个对象的是也是可以通过虚基表去找对应的偏移量,虚基表里面可能还有其他值要放,所有不能把指向虚基表的地址直接替换成偏移量的值
【C++】-菱形继承(一个学了但最好不要使用的知识)_第17张图片

通过上面的理解相信大家应该知道我们的祖师爷是怎么解决这个问题了吧,我们通过看大小也能发现解决了数据冗余的问题,**对于基类中只有单个属性的时候或者属性大小比较的时候,节省空间效果不是很明显,**但是当基类的属性大小较大的时候就能体现出来

【C++】-菱形继承(一个学了但最好不要使用的知识)_第18张图片

注意:我们对象的空间排序是先继承在上面,刚才的d对象,因为先继承了B在继承了C,所以B在上,C在下

四、继承的总结和反思

  1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
  2. 多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。
  3. 继承和组合
    public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
    组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
    优先使用对象组合,而不是类继承 。
// Car和BMW Car和Benz构成is-a的关系   下面是继承
   class Car{
   protected:
   string _colour = "白色"; // 颜色
   string _num = "陕ABIT00"; // 车牌号
   };
   
   class BMW : public Car{
   public:
   void Drive() {cout << "好开-操控" << endl;}
   };
   
   class Benz : public Car{
   public:
   void Drive() {cout << "好坐-舒适" << endl;}
   };
   
   
   // Tire和Car构成has-a的关系    下面是组合
 
   class Tire{
   protected:
       string _brand = "Michelin";  // 品牌
       size_t _size = 17;         // 尺寸
   
   };
   
   class Car{
   protected:
   string _colour = "白色"; // 颜色
   string _num = "陕ABIT00"; // 车牌号
    Tire _t; // 轮胎
   };
  1. 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
  2. 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
  3. 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

注意:不是完全是菱形就是菱形继承,只要继承之后又两份数据之后就是菱形继承:
【C++】-菱形继承(一个学了但最好不要使用的知识)_第19张图片
这样都属于菱形继承

你可能感兴趣的:(C++进阶,c++,开发语言)