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呢?上面多出的0055db40
和0055db48
很明显是两个指针,称其为虚基表指针,他们分别指向了虚基表,虚基表中存储的是一个偏移量,通过偏移量可以找到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++中的各种“虚“-- 虚函数纯虚函数虚继承虚基类虚析构纯虚析构抽象类讲解