虚继承是如何解决二义性和数据的冗余的

我们知道面向对象的三大特性分别为封装,继承,多态。在继承中,我们知道一个类可以继承另一个类,这样的关系被叫做子类(派生类)继承父类(基类),并且子类可以使用到父类的接口。但是在C++中还被设计了一个多继承,是什么意思呢。指的就是一个类可以继承多个类,就好比一个人可能他在这个学校担任老师,但是在另一边他可能是某个资历更深老师的某个学生,这样也产生了,如果一个老师类里面有名称了,学生里面也有一个名称,不可能他担任老师的时候叫做张三,担任学生的时候又叫做王五了。这里也就是二义性。但是我们可以通过对数据的指定性来解决二义性的问题,例如写下这样一段代码

虚继承是如何解决二义性和数据的冗余的_第1张图片虚继承是如何解决二义性和数据的冗余的_第2张图片

 代码中写到,B继承了A,C继承了A,D继承了B,C,且作为基类中的A有一个_a的成员变量。可以大致把他们理解为这样的关系

 虚继承是如何解决二义性和数据的冗余的_第3张图片

 这就是一个菱形继承。接下来我们通过内存去看它们 

虚继承是如何解决二义性和数据的冗余的_第4张图片

 通过对d的取地址观察我们可以看到确实是解决了数据二义性,但是数据的冗余是如何去处理的呢。这里我们可以用到一个虚继承,虚继承主要解决的就是命名冲突和数据冗余的问题,只保留一份基类的成员,关键字是virtual。当我们把关键字加上去之后再去对d取地址看到的内容就是这样的

虚继承是如何解决二义性和数据的冗余的_第5张图片 虚继承是如何解决二义性和数据的冗余的_第6张图片

 

 当把虚继承关键字加上去再去观察内存的时候明显发现,此时B,C类中原本为A的数据区被写道了最后一个位置,而原本的位置变成了一个地址,如果我们通过地址找过去看看又会发现什么

 虚继承是如何解决二义性和数据的冗余的_第7张图片

 通过地址过去之后发现第一个数据是00 00 00 00是因为它需要给其它的值预留空间,但是第二个值分别是十六进制的14,0c,对应的也就是十进制的20,和12。但是这两个值能代表什么呢。如果我们对比一下就能发现,从地址开始,B便宜20字节之后刚好是到A类,而C类偏移12字节后也恰好是到A类。

虚继承是如何解决二义性和数据的冗余的_第8张图片

那么这个偏移量是用来干什么的呢。我们拿这样一段代码来举例。虚继承是如何解决二义性和数据的冗余的_第9张图片

在没有使用虚继承之前它的存储方式会是这样 

虚继承是如何解决二义性和数据的冗余的_第10张图片

但是当使用了虚继承之后它的模型也会跟着发生变化

虚继承是如何解决二义性和数据的冗余的_第11张图片

 

 可以看到b对象的模型也跟着发生了变化,也是一个指针指向一块区域,区域内存放着偏移量。

此时,我们利用B类定义一个对象,然后在用B类定义一个指针,让指针指向这个对象并且对对象中的_a进行操作,然后在让这个指针指向对象d,同样对d对象中的_a进行操作,那么此时的指针是无法知道具体指向谁的,它可能指向b对象,也可能指向d对象,所以这是就会利用偏移量,加上偏移量的值之后无论是b还是d都可以找到_a。

虚继承是如何解决二义性和数据的冗余的_第12张图片

 当我们从反汇编的角度来看它的时候,也可以看到它是先利用当前的地址去加上偏移量来找到_a

虚继承是如何解决二义性和数据的冗余的_第13张图片

 

 可能会有疑惑,为什么不能把地址直接存在对象里面,却偏偏要弄一个地址出来去存到那里,因为是要考虑到如果里面有多个值的情况,一个值就要一个地址,多个值就会造成对象空间的变大,所以会用一个指针来维护,管理一块区域。我们可以通过多加一个对象来观察确定。

虚继承是如何解决二义性和数据的冗余的_第14张图片

 

你可能感兴趣的:(C++,继承,c++,数据结构)