8-5:C++继承之多继承,菱形继承,虚继承,虚基表,继承和组合

Posted 快乐江湖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了8-5:C++继承之多继承,菱形继承,虚继承,虚基表,继承和组合相关的知识,希望对你有一定的参考价值。

一:菱形继承与菱形虚拟继承

(1)多继承

之前我们所讲得继承全部属于单继承:一个子类只有一个直接父类
在这里插入图片描述
由于在现实生活中,一个人可能会有双重角色。比如研究生可以作为助教,一方面它是学生一方面它是老师,这样一来,一个子类就有两个及以上的直接父类了,称这种继承为多继承
在这里插入图片描述

(2)菱形继承

C++有很多让人觉得不满意的地方,其中有一个地方就是多继承带来的菱形继承问题,抽象来看,继承关系呈现一种环形结构

如下,一个助教可以是老师也可以是学生,老师和学生同时又是人
在这里插入图片描述
这种继承关系会引发数据冗余和数据二义性的问题
在这里插入图片描述

  • 数据冗余:助教里面Person会被继承两次
  • 数据二义:助教的姓名到底应该是哪一个(虽然现实生活中会存在这种起别名的现象,但是我们考虑的是如身份证那样不可二义的对象)

如下代码反应的就是上述继承关系,最终编译器报错:对于name不明确

class Person
{
public:
	string _name;//姓名
};

class Student : public Person
{
protected:
	int _num;//学号
};

class Teacher : public Person
{
protected:
	int _id;//工号
};

class Assistant : public Student, public Teacher
{
protected:
	string _course;//主讲课程
};

int main()
{
	Assistant a;
	a._name = "xiaowang";

}

在这里插入图片描述

  • 上面体现的二义的问题,这个问题当然可以通过加入域作用限制符指定父类解决,但是仍然无法解决属于冗余的问题

(3)虚继承

A:如何解决

如果解决上面的问题就要使用虚继承,只需使用关键字virtual,在下面的位置加入即可
在这里插入图片描述

class Student : virtual public Person
{
protected:
	int _num;//学号
};

class Teacher : virtual public Person
{
protected:
	int _id;//工号
};

在这里插入图片描述

B:解决原理

为了方便描述,使用下面的代码作说明

class A
{
public:
	int _a;
};

class B : public A
{
public:
	int _b;
};

class C : public A
{
public:
	int _c;
};

class D : public B, public C
{
public:
	int _d;
};
int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;

	return 0;

}
  • 继承关系是下面这样的
  • 在这里插入图片描述
  • 内存窗口如下
    在这里插入图片描述

可以发现不采用虚继承的话,对于对象d它的所占的空间就是20个字节。将上述继承改为虚继承,那么它的大小应该变为16个字节,因为只保存了一份a,但是实际查看内存窗口发现它竟然占用了24个字节
在这里插入图片描述
先不要管那多出的八个字节,其余继承关系如下
在这里插入图片描述
可以看出上面的a=1是公共的,b和c都继承了a,那么在赋值时d是如何找到这个位于下方的公共的a呢?上面多出的0055db400055db48很明显是两个指针,称其为虚基表指针,他们分别指向了虚基表,虚基表中存储的是一个偏移量,通过偏移量可以找到a
在这里插入图片描述

在这里插入图片描述
尤其在发生切片操作时,这样的偏移量就能保证赋值时正确的

D d
B b=d
C c=d

在这里插入图片描述
因此使用虚继承后d.B::a=1这样的操作就是通过偏移量完成的

二:继承总结

(1)继承缺陷

有了多继承,就会出现菱形继承,有了菱形继承就有虚拟继承,因此底层的实现一定会很复杂。所以设计时不建议设计多继承,而且一定不能有菱形继承,否则会出现这样那样的问题

(2)继承与组合

继承
class A
{};
class B : public A
{}

组合
class A
{};
class B
{
	A a;
}

public继承是一种is-a的关系,也就是每个子类对象都是一个父类对象(学生也是人),而组合是一种has-a的关系

继承体现的是白箱复用,组合体现的是黑箱复用
在这里插入图片描述

  • 继承允许你根据父类的实现来定义子类的实现。这种方式就是白箱复用,在继承方式中,父类的内部细节对子类可见。继承一定程度上破坏了父类的封装,父类的改变对子类有很大的影响——耦合性太强
  • 组合是继承外的另一种很好的选择,新的更复杂的功能可以通过组合对象获得。对象组合要求被组合的对象具有良好定义的接口,对象的内部细节对“父类”是不可见的,组合类之间没有很强的依赖性——高内聚,低耦合

设计时要优先使用组合而不是继承

  • 看他们更符合is-a关系(比如猫是动物,狗是动物)还是has-a关系(往往是某个对象的属性)

以上是关于8-5:C++继承之多继承,菱形继承,虚继承,虚基表,继承和组合的主要内容,如果未能解决你的问题,请参考以下文章

C++ 继承和多态

C++中的各种“虚“-- 虚函数纯虚函数虚继承虚基类虚析构纯虚析构抽象类讲解

C++中的各种“虚“-- 虚函数纯虚函数虚继承虚基类虚析构纯虚析构抽象类讲解

C++ ——虚继承时的构造函数

C++ ——虚继承时的构造函数

C++:虚继承中对象模型分析