27.C++继承 3 (复杂的菱形继承与菱形虚拟继承)
⭐上篇文章:26.C++继承 2 (派生类的默认成员函数,友元静态变量)-CSDN博客
⭐本篇代码:c++学习/16.C++三大特性-继承/复杂的菱形继承和菱形虚拟继承 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)
⭐标⭐是比较重要的部分
目录
一. 多继承与菱形继承
二. 虚拟继承
三. 虚拟继承的原理
四. 继承与组合
五. 易错辨析
一. 多继承与菱形继承
在C++中,一个类既可以单继承某一个类。也可以多继承自几个类
单继承:
多继承
由于C++存在多继承,所以也有可能有菱形继承。
这样就导致了一个问题。
假设person有一个name和id变量,由于继承,pupil中既有来自child中的name和id,也有来自student中的name和id。
这就导致了数据冗余和数据歧义的问题
比如:我们的id到底初始化哪一个呢?name不是重复了吗?
代码举例:
#include <iostream>
using namespace std;
class person
{
public:
string name;
int id;
};
class student :public person
{
public:
string classname;
};
class child :public person
{
public:
int age;
};
class pupil :public student, public child
{};
int main()
{
pupil a;
a.name = "张三";
a.id = 1;
a.age = 5;
a.classname = "1";
return 0;
}
测试结果:
可以看到,编译器直接报错不明确。
我们需要指定name和id的来源。
上面这种写法,虽然不报错,仍有冗余性问题
二. 虚拟继承
C++通过虚拟继承,来解决菱形继承的冗余性和二义性问题。使用菱形继承需要我们使用一个关键字virtual
#include <iostream>
using namespace std;
class person
{
public:
string name;
int id;
};
class student :virtual public person
{
public:
string classname;
};
class child :virtual public person
{
public:
int age;
};
class pupil :public student, public child
{};
int main()
{
pupil a;
a.id = 1;
a.name = "yzc";
return 0;
}
可以看到,通过对student和child加上虚拟继承。pupil可以直接赋值name和id
三. 虚拟继承的原理
我们通过内存分布来分析虚拟继承的原理
不添加虚拟内存:
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.B 第二行是d.C 第三行是d本身的变量 存在数据冗余
添加虚拟内存:
1,2行是d.B 3,4行是d.C 5行的d本身变量 公共变量放到了最后一行
1,3行分别放的是指针,这两个指针指向两张表,表中存放的是该部分到公共部分的偏移量,用于快速定位公共部分的数据。
原理图:
四. 继承与组合
两个类可以有继承关系,也可以有组合关系。
对于继承来说:所有的派生类都是基类的一个对象
对于组合来说:类A中B类,所有A类对象都存在一个B类对象
实际中我们使用组合优先于使用继承。
五. 易错辨析
1 什么是菱形继承?他有什么问题?
菱形继承是多继承中的一种,某一个子类的两个父类都继承了同一个爷爷。这会导致数据冗余和二义性
2 什么是菱形虚拟继承?他有什么作用?原理是什么?
菱形虚拟继承是C++用于解决菱形继承中的二义性和冗余性问题的方式。原理是将所有的相同变量放在该对象内存的末尾,然后在所有父类中添加一个虚基表指针,这个指针指向虚基表,该表中存放了一个偏移量。通过这个偏移量可以找到公共部分
3 继承与组合的区别?什么时候用继承,什么时候用组合
继承和组合都能够提高对类的复用。对于继承来说,子类中的父类是透明的,可以看到其内部的实现。对于组合来说,内部类是不透明的。
一般来说,我们优先使用组合,因为组合的耦合性低,代码维护性好。不过部分类更适合继承,同时继承有多态机制,这是组合没有的