【C++】继承(四)--菱形继承和菱形虚拟继承

继承(一):https://blog.csdn.net/ly_6699/article/details/88801492
讲到继承的概念和定义,基类和派生类对象赋值转换

继承(二):https://blog.csdn.net/ly_6699/article/details/88805464
讲到继承中的作用域和派生类的默认成员函数

继承(三):https://blog.csdn.net/ly_6699/article/details/88858186
讲到继承和友元,继承和静态成员,继承和组合

在这节里,我重点讲复杂的菱形继承和菱形的虚拟继承

1.单继承:一个子类只有一个直接父类

在这里插入图片描述

2.多继承:一个子类有两个或以上直接父类

在这里插入图片描述

3.菱形继承:是多继承的特殊情况,其中两个或以上子类拥有同一个直接父类

在这里插入图片描述

此时若创建一个Assistant对象,对象内包含成员如下图:
在这里插入图片描述

从上面的继承关系,不难看出在Assistant 的对象中,会出现两份Person的成员。
所以菱形继承会带来的数据的冗余和二义性问题
下面的代码可以帮助更进一步的理解:

class Person
{
public:
	string _name;  //姓名
};
class Student:public Person
{
protected:
	int _no;    //学号
};
class Teacher:public Person
{
protected:
	int _id;         //工号
};
class Assistant :public Student, public Teacher
{
	string _course;     //主修课程
};
void Test()
{
	Assistant a;
	//a._name = "ly";        //此时_name的二义性带来编译错误

	//指明成员的作用域可以解决二义性的问题,却带来了数据冗余问题
	a.Student::_name = "ll";
	a.Teacher::_name = "yy";
}

那么我们应该怎么解决菱形继承带来的问题呢?没错!虚拟继承

4.虚拟继承:碰到菱形继承时,将同一个父类的子类写成虚拟继承 ( 加virtual 关键字 )

注意:虚拟继承针对解决菱形继承带来的二义性和数据冗余,在其他地方不要随意使用。

class Person
{
public:
	string _name;
};
class Student:virtual public Person   //虚拟继承方式
{
protected:
	int _no;
};
class Teacher:virtual public Person   //虚拟继承方式
{
protected:
	int _id;
};
class Assistant :public Student, public Teacher
{
	string _course;     //主修课程
};
void Test()
{
	Assistant a;
	a._name = "ly";     //这时_name只有一个,无二义性和数据冗余
}	

虚拟继承解决二义性和数据冗余的原理
在这里插入图片描述
我对上面的图做一个解释说明

  1. 首先,当我们把Student 和Teacher 改写为虚拟继承时,在Student 和Teacher两个类中,原来各自存放Person成员的地方,现在各存了一个指针(虚基表指针)指向两张虚基表(虚基表中存的是子类相对于父类的偏移量)。
  2. 此时父类的成员被单独存放在最后,子类需要通过自己的指针找到父类成员才能进行定义。
    思考:怎么体现出解决了二义性和数据冗余?
    1. 对于二义性: 无论Student 还是Teacher,他们的指针最终指向了同一块空间,即他们所拥有的是同一个成员,当然不再存在二义性了。(这里后面继承的Teacher 中的_name 会更新Student中的_name)
    2. 对于数据冗余: 从上面的原理我们可以看出,虚拟继承可以将相同的信息都合并和更新,也就解决了数据冗余。
    3. 那所占内存空间是不是变大了?
    相信有小伙伴会对这个问题有所疑惑,但是我可以很明确的说并没有!! 虽然从上面的代码看起来我们比原来需要的空间更多了。这只是因为我们父类的成员只定义了一个,如果父类的成员有n个,那原来存储Person成员的内存需要2*n,现在只需要n+2(指针)。这对比就很明显了吧。
5.对继承的总结和反思

1.很多人C++语法复杂,其实不无道理。这里的多继承就是一个体现。因为有多继承就会出现菱形继承,继而因为要解决菱形继承带来的问题就需要引入虚拟继承,此时就会涉及虚拟继承的底层实现使得问题变得很复杂。
所以一般不建议设计出多继承,一定不要设计出菱形继承,否则在复杂度和性能上都有问题,别为难自己哦。
2.多继承可以认为是C++的缺陷之一,所以很多后来的面向对象语言都没有多继承,如JAVA。

6.面试题

1.什么是菱形继承?菱形继承会产生什么样的问题?
2.什么是菱形虚拟继承?它是怎样解决数据冗余和二义性问题的?
3.继承和组合的区别是什么?什么时候用继承什么时候用组合?
看完我的四篇博客后,上面三道题完全不是问题!!
下面讲一道选择题;
4.多继承中指针偏移的问题

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

int main()
{
	C c;
	A *p1 = &c;
	B *p2 = &c;
	C *p3 = &c;
	system("pause");
	return 0;
}
A.p1==p2==p3
B.p1<p2<p3
C.p1==p3!=p2
D.p1!=p2!=p3

这道题的答案是C。首先我们要明白,子类对象成员的位置与继承顺序有关,故C类要先继承A类再继承B类。即使这里p1和p3的意义不相同,但是他们的值却是相同的。解释如下图:
在这里插入图片描述
如果上面代码改写成class C :public B, public A 则此时 p2==p3!=p1
大家觉得我讲明白了吗?

博主对继承的理解都在上面四篇博客里了,其他大佬有更好的内容欢迎下方留言,我一定借鉴更新~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
菱形继承指的是一个派生类继承两个直接或间接基类,而这两个基类又共同继承自一个共同的基类,导致派生类中存在两份共同基类数据成员,从而产生了命名冲突和二义性的问题。 解决菱形继承问题的一种方法是使用虚拟继承虚拟继承可以使得共同基类在派生类中只有一份实例,从而避免了数据成员的重复和命名冲突问题。在使用虚拟继承时,需要在继承语句前加上关键字 virtual,例如: ``` 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; }; ``` 在上面的例子中,B 和 C 都虚拟继承自 A,而 D 继承自 B 和 C。因此,在 D 中只有一份 A 的实例,从而避免了数据成员的重复和命名冲突问题。 在初始化菱形继承的派生类时,需要注意以下几点: 1. 派生类的构造函数必须调用每个直接基类的构造函数,以及虚拟基类的构造函数,顺序为先虚拟基类,再按照继承的顺序调用直接基类的构造函数。 2. 虚拟基类的构造函数由最底层的派生类负责调用,其他派生类不需要再次调用虚拟基类的构造函数。 3. 派生类的析构函数必须调用每个直接基类的析构函数,以及虚拟基类的析构函数,顺序为先按照继承的顺序调用直接基类的析构函数,再调用虚拟基类的析构函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值