学习C++、QT---14(C++的多重继承、菱形继承问题)

每日一言

        与其抱怨黑暗,不如提灯前行。

多重继承

这个的话也很好理解,一个子类使用public继承多个父类可以去继承使用多个父类的public的属性和方法

 你看,就是我们这边设计了两个类嘛,这个两个类我们分别设计了不同的变量和相同名字的变量,还有不同名字的函数,以及相同名字的函数,都是为了后面做铺垫

这边我们让我们的person(子类)去用public继承两个父类 怎么多重继承,他的写法为中间多个‘’逗号‘’,这样就可以多重继承了,就表示person  同时继承person1和person2  我们一定要记住这样的写法

我们如果要是用对象去直接调用两个父类都有的一模一样的函数的时候必须要进行 作用域解析 也就是用::双冒号去显示的表示我们这个meg是属于哪一个父类的,这样才可以成功,不管是直接调用还是通过自己子类的函数进行调用父类的相同函数,必须要走到 作用域解析 显视的去表达自己要用哪一个父类的这个同名函数。这样编译器才不会错

还有一点,我自己去将父类中的相同的变量也进行了测试,突然我就发现好多余啊,这样不是很不会设计吗,所以以后肯定还有更好的解决方法,但是我这边只是将对父类相同变量的调用方法也写出来了

好的这边的话会发现,如果我创建实例化对象去调用meg这个函数,会发现报了这个错误,意思是就是说这个meg编译器在多个继承中都发现了,所以不懂用哪个就报错了,这边我们正确用法看上面那张图

这个就是多重继承了

菱形继承和多继承

什么叫做菱形继承,我直接给图

解释一下:就是有个基类叫做classBase  他被classA和 classB继承,然后我们的Final类继承classA和classB

这样的话就是菱形继承了,会发生什么问题呢?

我们会发现我们原先的classBase的信息会在Final类中会拷贝两份,那么就很奇怪了,根本上就是语法的报错了,细节的话显示在代码里面

目的就是我们如何去解决这个问题,我们菱形继承是允许的,只是我们需要去解决期间会发生的问题

首先我们先用这样的方法案例进行讲解

我们会发现怎么报错了,因为这个报错信息是说name在多个继承的类中都找到了,所以不懂用哪一个,所以就报错了

为什么会报错,是因为我们的Final类里面实际上会有两份classBase的信息,但是路径不一样,一个是classA的一个是classB的我花了一张图

希望看得懂吧,比较潦草

我们的Final类的内存结构是这样的  有classA的name和print方法  也有有classB的name和print方法 而且都分在了两块不一样的内存,所以在final直接用p1.name是不是太残暴了,所以就报错了啊,因为编译器怎么会知道我们要用哪一个name呢  

 解决办法!!!

那么怎么办呢  看下面这个

那当然就是给上路径喽

那么关于这个 : :双冒号作用域解析符我们有一套记忆方法

:: 的本质与记忆技巧

本质:: 是作用域限定符,用于告诉编译器 “我要访问的标识符属于哪个作用域”。

记忆口诀

继承歧义用`::`,基类路径要指明;

命名空间防冲突,`std::cout`是典型;

静态成员属类体,直接`类名::成员`请;

全局变量被屏蔽,`::变量`破局行;

派生覆盖基类法,`基类::方法`显原形;

类内枚举嵌套类,`类名::类型`要记清。

像我们这边的话就不是继承歧义了,所以我们的路径要指明(对于Final来说classA是基类嘛)

当然了我们这样写会发现,诶那我的另外一份数据不是就多出来了就浪费空间了吗??所以我们这个用普通的方法还是有问题的

普通多继承(菱形继承)的问题

当出现类似 “菱形继承” 的场景(比如类 Base 被 Derived1Derived2 继承,FinalDerived 又同时继承 Derived1 和 Derived2 ),若不用虚继承,FinalDerived 里会有两份 Base 类的成员拷贝(分别来自 Derived1 和 Derived2 继承路径 )。这会带来两个关键问题:

  • 数据冗余:同一基类数据存多份,浪费内存空间,像 Base 里的 data 成员,在 FinalDerived 里会有两份。
  • 访问歧义:直接访问 data 时,编译器分不清该用 Derived1 路径的,还是 Derived2 路径的,编译会报错。这个就是开头那一幕

所以我们使用虚继承的方法来解决多份数据的问题

虚继承的作用:

多个子类(中间派生类)虚继承同一个父类(虚基类)时,在最终的派生类里,该父类的数据只会保留一份 

底层原理:虚基类指针 + 虚基类表这个比较复杂,感兴趣可以去了解

那么我们做出代码

会发现确实是这样,我们的virtual 放在权限的前面 那么这个就是虚继承了

我们发现main函数中确实是可以直接用p1.name来调用了,因为此时final类里面只保留了一份的chassBase的数据啊,所以就可以直接调用了,但是我们又发现了一个问题,虽然说保留一份数据,那这个一份数据是来自classA的还是来自classB的,这个分辨不了啊,而且这个是编译器做的事情,我们也不清楚

最后一个我们加入构造函数来看看是怎么样的效果

我们会发现构造函数会变得很多参数,就是到最后我们的最终派生类的时候的构造函数,看起来会比较繁琐,因为是继承如果,父类是有参构造,那么必须子类要调用基类构造函数来创建构造函数,所以到最后的时候,会有三个基类构造函数要调用

那么我们又有疑问了,这个classBase怎么会是Final的基类,这个明显不符合常理,因为这边是这样的:

虽然 Final 直接继承的是 classA 和 classB,但由于它们是虚继承 classBaseFinal 会直接继承 classBase 的唯一实例,成为 Final 的直接基类。因此,Final 的完整基类列表是:

  1. 直接基类classBase(虚基类)、classAclassB
  2. 间接基类:无(所有路径的 classBase 都被合并为一个)

2. 为什么必须由 Final 构造 classBase

在普通继承中,派生类的构造函数会自动调用直接基类的构造函数。但在虚继承中,规则变为:

  • 虚基类的构造由最终派生类负责(即 Final 必须显式调用 classBase 的构造函数)
  • 中间派生类(classAclassB)的虚基类构造函数会被忽略

总结:忽略中间类构造的本质目的

  1. 避免数据冗余:防止虚基类被多次构造,确保最终派生类中只有一份实例。
  2. 保证初始化唯一性:由最终派生类统一控制虚基类的构造参数,避免中间类传递不同参数导致的矛盾。
  3. 语法与语义的分离:中间类的虚基类初始化是语法要求(必须写),但语义上由最终派生类决定实际行为。

一句话总结虚继承通过「忽略中间类的虚基类构造」,强制由最终派生类统一管理虚基类的初始化,从而解决菱形继承的核心问题。

这个就是我们的虚继承解决菱形继承的问题的案例啦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热爱嵌入式的涛涛同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值