目录
前言
一、多继承
认识
继承顺序
二、菱形继承
三、菱形虚拟继承(重难点)
认识
底层原理(细致)
四、继承与组合
五、总结
前言
在前面我们所举的例子都是单继承,就是一个子类只有一个直接父类的关系!但是这还不是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++语法复杂,多继承就是一大体现!
好了,老铁们今天的分享就到这,如果觉得对你有用,欢迎三连,你的支持就是我前进的动力!