目录
一、虚拟继承
🌟【非虚拟内存分布】
🌟【虚拟继承内存分布】
🌟【虚拟继承读取】
🌟【练习检验】
🌟【继承的总结和反思】
二、组合
🌟【继承和组合】
🌟【前言回顾】
上一篇文章我们已经知道了虚拟继承可以解决菱形继承的二义性和数据冗余的问题,那其中的原理是什么呢,现在让我们来揭晓吧。【继承上】
一、虚拟继承
//为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,
//再借助内存窗口观察对象成员的模型。
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;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
🌟【非虚拟内存分布】
会发现有两个不同的A(两个不同的地址),这样就会形成继承的二义性和数据冗余。
🌟【虚拟继承内存分布】
在继承方式前面加上关键字virtual即可变成虚继承
上图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下 面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指 向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量 可以找到下面的A。使用同一个A. (偏移量的计算是16进制)
【注意】如果有多个元素需要虚拟继承,就会按照声明顺序放在最下面,编译器会自动增加偏移量来找到所需要的元素。
🌟【虚拟继承读取】
当切片取A时,取相应元素的地址,根据偏移量加上A的值。
当赋值取A时,A的内存就在值的下面,没有偏移量。
但是两者都是一样的,这也是大佬设计的厉害之处
🌟【练习检验】
//判断输出的顺序
#include<iostream>
using namespace std;
class A{
public:
A(char *s) { cout << s << endl; }
~A(){}
};
class B :virtual public A
{
public:
B(char *s1, char*s2) :A(s1) { cout << s2 << endl; }
};
class C :virtual public A
{
public:
C(char *s1, char*s2) :A(s1) { cout << s2 << endl; }
};
class D : public C, public B
{
public:
D(char *s1, char *s2, char *s3, char *s4) :B(s1, s2), C(s1, s3), A(s1)
{
cout << s4 << endl;
}
};
int main()
{
D *p = new D("class A", "class B", "class C", "class D");
delete p;
return 0;
}
虽然B、C都包含A,但只有一份A,所以只能调一次A的构造函数。 初始化列表的顺序是先声明的先初始化,先继承的先初始化。A是最先被声明,B继承,C继承,D继承,所以输出结果为“class A class B class C class D”
🌟【继承的总结和反思】
⭕很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
⭕多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。
二、组合
对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
🌟【继承和组合】
⭕public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
⭕优先使用对象组合,而不是类继承
⭕对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象 来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复 用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
⭕实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。