首页 > 继承和多态 3.0 -- 菱形继承

继承和多态 3.0 -- 菱形继承

单继承和多继承

C++的继承方式是支持单继承和多继承的,首先看一下代码,分清单继承和多继承

单继承

class A
{
public:int _a;
};class B :public A
{
public:int _b;
};class C : public A
{
public:int _c;
};

类似于上面的方式就是单继承,或者C类可以不继承A类,而是直接继承B类,这种方式也是单继承

多继承

class D :public B,public C
{
public:int _d;
};

上面的这种方式就是多继承,即是D类既继承了B类,又继承了C

通过上一个章节的学习我们知道,继承的时候,在子类的 成员变量里面包含了父类的成员变量,则我们可以分析出,上面的继承 关系中,B类里面包含变量_a,和变量_b;C类里面包含了变量_a和变量_c;这个时候我们的D类里面就包含了B类的成员变量和C类的成员变量,所以D类里面包含的成员变量是_a,_b,_a,_c,_d。

菱形继承

基于上面的分析我们引入了菱形继承,下面我们用一个图来表示上面的成员的关系

image

菱形继承问题 – 二义性和数据冗余

还是上面的代码,这个时候我们实例化一个D类的对象,这个时候我们想对_a赋值,就会出现报错,请看下面的代码

D d;d._a = 1;return 0;

这个时候编译器报的错误就是D::_a不明确,为什么不明确呢,这个从上面的图中可以看出来,因为我们的D类的对象中有有两个_a的成员变量,这个时候对_a赋值的时候,编译器不知道该给哪个_a赋值,这个问题就是====二义性====问题。

首先看下面的解决办法

D d;d.B::_a = 1;

通过这种方式我们就不会出现二义性的问题了。

还有一个问题就是,我们的D类型的对象中,有两个_a这个时候的_a表示的意义都是一样的,如果我们的A类里面的成员变量非常的大,这个时候是不是就是==数据冗余==呢,我们需要一个A类的成员就够了。

解决菱形继承 – 虚拟继承

为了解决上面的问题,我们引入了虚拟继承的概念,请看下面的代码

class A
{
public:int _a;
};class B :virtual public A
{
public:int _b;
};class C : virtual public A
{
public:int _c;
};class D :public B,public C
{
public:int _d;
};int main()
{D d;d._a = 1;return 0;
}

这个时候在主函数中,再次使用上面的方式对_a赋值的时候,就不会报错了,那么我们不禁想问,编译器到底是如何做的呢,为什么这么做的时候就不会出现问题了呢,这个问题要从C++对象模型说起

虚继承对象模型

下面是普通的继承,没有虚拟继承,我们结合旁边的代码可以看到,如果没有虚拟继承的时候,D类的对象中会为两个A类型里面的_a变量,这就是数据冗余和二义性

image

下面我们再来看一下如果我们使用的是虚拟继承给赋值的给赋值的时候是什么结果呢,看下面的截图

image

这个时候我们注意到一个问题是,和刚刚的那个截图不一样的是,在变量_b的上面放置的不是一个变量_a,而是一个类似于一个地址一样的数据,同样的道理,在变量_c的上面放置的也不是变量_a,也是一个地址一样的内容,我们又来发现一个问题就是,我们给变量_a,赋值的地方显示的是在变量_d的下面,然后我们再来看看那个类似于地址一样的内容到底是什么呢

我们把这个类似地址的数据放置放在监视窗口中看一下

image

这个时候我们看到,监视窗口2里面放置的内存的位置放置的十六进制数是14,转化成十进制就是20,我们再来比较一下监视窗口1中,那个地址和_a实际存放的位置的距离是20,这个时候我们就能够理解了,原来这里放置的是_a这个变量的偏移量。

还有一个疑问就是,为什么监视窗口2中对应的地址上面放置的是0,而不是14呢,这是因为,这个0所在位置是为了给接下来的虚函数准备的,这个以后会讨论到。

这里需要说明的是,监视窗口1中放置的是地址是虚基表指针,它指向的是一个虚基表。

我们还需要考虑的一个问题就是,为什么这里不直接放置_a的地址,而是放置的是相对位置的偏移量呢,这里我们分析这样的一个问题,如果我们拿这个类去实例化多个对象的话,是不是每个对象就有一个存放_a的地址呢,那么这个时候,每个对象的放置的地址都一样了,而如果放置的是偏移量的话,每个对象的地址空间中放置的内容都是一样的,这就减少了计算的开销。

更多相关:

  • 英语的重要性,毋庸置疑!尤其对广大职场人士,掌握英语意味着就多了一项竞争的技能。那,对于我们成人来说,时间是最宝贵的。如何短时间内在英语方面有所突破,这是我们最关心的事情。英语学习,到底有没有捷径可以走,是否可以速成?周老师在这里明确告诉大家,英语学习,没有绝对的捷径走,但是可以少走弯路。十多年的教学经验告诉我们,成功的学习方法可以借...

  • 展开全部 其实IDLE提供了一个显32313133353236313431303231363533e78988e69d8331333365663438示所有行和所有字符的功能。 我们打开IDLE shell或者IDLE编辑器,可以看到左下角有个Ln和Col,事实上,Ln是当前光标所在行,Col是当前光标所在列。 我们如果想得到文件代码...

  • 前言[1]从 Main 方法说起[2]走进 Tomcat 内部[3]总结[4]《Java 2019 超神之路》《Dubbo 实现原理与源码解析 —— 精品合集》《Spring 实现原理与源码解析 —— 精品合集》《MyBatis 实现原理与源码解析 —— 精品合集》《Spring MVC 实现原理与源码解析 —— 精品合集》《Spri...

  • 【本文摘要】【注】本文所述内容为学习Yjango《学习观》相关视频之后的总结,观点归Yjango所有,本文仅作为学习之用。阅读本节,会让你对英语这类运动类知识的学习豁然开朗,你会知道英语学习方面,我们的症结所在。学习英语这类运动类知识,需要把握四个原则第一,不要用主动意识。第二,关注于端对端第三,输入输出符合实际情况第四,通过多个例子...

  • 点云PCL免费知识星球,点云论文速读。文章:RGB-D SLAM with Structural Regularities作者:Yanyan Li , Raza Yunus , Nikolas Brasch , Nassir Navab and Federico Tombari编译:点云PCL代码:https://github.co...

  • 多线程编程 mind-Mapping保存有一下导图的xmind文件,可直接获取 互斥变量 互斥对象 ptrhead相关接口 条件变量 future异步访问类 async类 promise类 package_task类...

  • 我们在实际开发的过程中,可能需要某些类的成员变量并不是针对每一个对象的,而是针对每一个类而言的,比如在银行中有一个利率数据,我们希望的是,当一个利率改变的时候,所有的对象都能够看到这个改变的数据,并利用它,而不是每一个对象都有一个利率成员变量。这个时候就设计到了静态成员变量。 一. 内存那些事 静态成员变量是存放在静态...

  • volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。下面举例说明。在DSP开发中,经常需要等待某个事件的触...

  •   引子     阅读以下代码,并尝试分析 代码解析 在主线程中,线程Id为1,为线程变量赋值 变量==d6ff开启一个新的task,此时线程Id为4,变量==d6ff,并调用Task1开启一个同步Task3,线程Id为1。变量==d6ff,修改值==f598此时第二步启动的Task1运行,线程Id为4,变量==d6ff,修改值=...

  • 操作系统基础    操作系统是协调、控制、管理计算机硬件资源与软件资源的控制程序 为什么要用操作系统?    1.操作系统可以把复杂的操作简化给用户使用或者应用程序  2.可以让应用程序对计算机硬件竞争变的有序  一套完整的计算机分为:操作系统、应用程序、计算机硬件 编程语言的分类   机械语言:使用二进制让计算机工作   优点:运行...