1. 单继承:一个子类只有一个直接父类。
多继承:一个子类有两个或以上直接父类。
菱形继承:菱形继承是多继承的一种特殊情况。
下面是代码和对象模型结构,可以看出菱形结构存在哪些问题,如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
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;
};
那么我要访问 _a,会出现什么?
因为 B 和 C 都继承了 A 中的 _a,所有访问 _a 的话,不明确访问的是哪个 _a,需要指定访问,如下:
下面是对象模型结构图,如下:
菱形继承问题:从上面的代码和对象模型结构中可以看到菱形继承有数据冗余和二义性问题。
D 继承 B、C之后,D 的对象中有两份 A 的成员。
那么怎么解决呢?这里就要用到虚拟继承,虚拟继承可以解决菱形继承的二义性和数据冗余的问题,怎么用?如下:
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;
}
在 B、C 继承 A 的时候,加上 virtual 就可以了。
下面借助调试窗口看一下菱形继承之后,不加 virtual,那么内部是什么样?
可以看到 d 对象中存在两个 _a,存在数据冗余。
那么虚拟继承之后呢,下面 加virtual 看一下,如下?
上图中可以看到,D 对象中把 _a 放到了最下面,相比于不加virtual的时候,B、C 对象中没有了 _a 但是多了一个地址,这个地址就是去找 _a 的,这个地址就是指向一张表的,这个表叫虚机表,指向虚机表的指针叫虚机表指针,虚机表中存的是偏移量,通过偏移量可以找到_a。
总结:虚继承之后,B、C对象里面没有了 _a,但是多了一个指针,这个指针是找 _a 的,先找到虚机表,在获取里面的偏移量,再拿偏移量找 _a。
下面我打开两个内存窗口调试看一下,如下:
B 中找偏移量是 20,不难看出地址是 4 字节(32位下),_a 在第 5 个位置;C 与 B 类似。
总结:
1. 能不用菱形继承就不要用!!!!!!!!!
2. 继承是一种 is-a 的关系;组合是一种 has-a 的关系。
3. 优先使用组合,而不是继承。(低耦合)降低耦合度,维护性好。