【C++】-----继承(复杂的多继承及虚拟继承)

目录

前言

一、多继承

认识

继承顺序

二、菱形继承

三、菱形虚拟继承(重难点)

 认识

底层原理(细致)

四、继承与组合

五、总结


前言

在前面我们所举的例子都是单继承,就是一个子类只有一个直接父类的关系!但是这还不是C++继承的结束,只是开始,下面来了解了解C++中比较复杂的继承关系!!!

一、多继承

认识

  • 顾名思义,就是一个子类有两个或多个直接父类的继承关系

如下: 

 

Son2这个类既有Son1特征也有Father的特征,就像有的水果同时也是蔬菜一样!! 从这个角度看,多继承的存在是合理的。。。但是会引发一个BUG---菱形继承(见下文讲解)!

继承顺序

多继承的对象模型中,一定要注意父类的继承顺序,顺序不同,在子类对象模型存储的位置也不同。

举例:

如上图的子类Son2,其对象模型为:

对于上述模型通常会有以下思考问题

Son2 s;

Son2* p1 = &s;
Son1* p2 = &s;
Father* p3 = &s;

问针对上图的继承关系p1、p2、p3的关系是怎样的?

对于上图三个指针的关系是:p1=p2!=p3 

若上述Son2的继承关系改成如下形式,则对象模型也会方式一定的变化!

//Sons继承关系
class Son2:public Father,public Son1
{}

此时Son2的对象模型变为:

 

那么此时三个指针的关系就是:p1 =p3 !=p2

二、菱形继承

菱形继承是多继承的一种特殊形式,也可以理解为BUG的意思!

如下:

那么子类的D的对象模型就会变成这样子!

 

从上图可以看出菱形继承存在的问题:

①数据二义性 

不知道要访问的是B类中的_name,还是C类的,造成二义性!!

这个问题的解决方案就是显示的指定是哪个父类的成员,如下:

②数据冗余

 所谓数据冗余,就是D的对象模型中存在两份_name,这个是必然的。怎么解决?虚拟继承就可以!

三、菱形虚拟继承(重难点)

 认识

 虚拟继承可以很好的解决菱形继承的二义性和数据冗余的问题。写法就是在腰部加上virtual关键字,如上述的继承关系,在B和C的继承A时使用虚拟继承。即在继承有公共父类的时候使用!

注意:虚拟继承不要在其他地方去使用!

底层原理(细致)

 先来对比一下普通的菱形继承和虚拟继承的内存模型!

普通菱形继承内存模型如下:

因此对于普通的菱形继承要访问_a,需要指明是哪个类!也可以看到数据冗余了B和C中都存同一个_a。

菱形虚拟继承的对象内存模型如下:

如果上图的内存模型可以看出:d的对象模型组成中,将A的成员变量_a放在最下面的位置,这个_a同时属于B和C的,你看到的是2,因为小编调试结束了,实际上调试过程中,是从1变到2的!这更加说明了共用的是一个_a!这样就是无需指明是具体的哪个类,很好的解决二义性和数据冗余的问题!

既然共用,那要B和C怎么去找公共的A呢?

大家可以看到B和C的内存模型分别有两个地址/指针:0x005c7b48 和 0x005c7b54(小端存储)

这两个指针实际指向的各自的两个表,叫做虚基表,指针叫做虚表指针,这个虚表里面存的是偏移量。而通过偏移量就能够找到_a了。

如:在C内存模型中0x005c7b54这个虚表指针,该虚表指针在d对象模型中的地址是0x008ff6e0,因为所指向的虚表中存的偏移量是0x0000000c,0x0000000C+0x008ff6E0=0x008ff6EC,最终得到的地址正是_a的位置。。。

B和C在内存中存储的位置又不一样,所以就需要加上偏移值!

为什么B和C要找属于自己的_a?

因为为了赋值场景呀,子类给父类赋值,不就是将属于父类的那部分切出去吗?那就要找出B/C成员中的A才能赋值过去啊!!

D d;
B b = d;
C c = d;

还需注意一个问题:使用虚拟继承时,被加上virtual关键字的类的对象模型也会改变。如上述B和C类的对象模型 会和d对象模型保持一致,都是会将_a放在最下面。。

四、继承与组合

  • 继承是is -a的关系,就是每个子类对象都是一个特殊的父类对象。
  • 组合是has -a的关系,每一个子类对象都有一个父类对象。。
//组合,将在B中包含一个A类型的成员
class A
{
private:
	int _a;
};
class B
{
private:
	A _aa;
	int _b;
};
  •  继承和组合都是复用的方式,实际中优先使用组合,而不是继承!
  • 继承中,父类的内部细节对子类可见,“白箱复用”。一定程度上破坏了父类的封装,父类的改变,对子类影响大。这就导致代码之间的依赖性强,耦合度高。。
  • 组合是继承之外的另外一种选择,组合对象的内部细节不可见,也称“黑箱复用”。代码之间依赖性并不强,你的改变并不会影响我,耦合度低。所以尽量多的去使用组合。

五、总结

①有了多继承就会又菱形继承,有了菱形继承就会有菱形虚拟继承,底层就很复杂。实际中可以使用多继承,但是切记一定不要设计出菱形继承。否则在复杂度及性能上都会存在问题!

②多继承可以认为是C++的缺陷之一,后来的很多语言就没有多继承。如java。所以说C++语法复杂,多继承就是一大体现!


好了,老铁们今天的分享就到这,如果觉得对你有用,欢迎三连,你的支持就是我前进的动力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值