首先,我们看一下菱形继承(也叫钻石继承)的类之间的关系,如下图:
这样的话,那D类有两个B类成员吗?,访问的话岂不是两个同名吗?那我们验证一下,
用代码表示的话就是:
//菱形继承
#include<iostream>
using namespace std;
//基类B
class B
{
public:
int _b;
};
//基类C1,公有继承B
class C1 :public B
{
public:
int _c1;
};
//基类C2,公有继承B
class C2 :public B
{
public:
int _c2;
};
//派生类D
class D :public C1, public C2
{
public:
int _d;
};
void FunTest1()
{
D d;
//d._b;//d对象空间中公有继承C1,C2,故有两个_b,构成二义性,计算机无法识别
cout << sizeof(D) << endl;//20
d.C1::_b = 1;
d._c1 = 2;
d.C2::_b = 3;
d._c2 = 4;
d._d = 5;
}
int main()
{
FunTest1();
return 0;
}
如果我们直接调用d._b=1时,就会发现调用失败
对象d的大小为20个字节,那是怎么来的呢?
d对象空间中公有继承C1,C2,各占8个字节,加上自己的成员变量_d总共20个字节,并且C1、C2都是公有继承B,也就是说有两个_b在d对象中也是都可以访问的,那岂不是构成二义性,计算机无法识别
好的,访问用作用域限定符解决这个问题,
那它的内存布局又是怎么回事呢?
我们对每个成员进行赋值,打开内存&d,看一下究竟:
那这样的话,一个对象就有了两个基类对象B,而我们有时候往往只需要一个怎么办,那又怎样解决这个数据冗余问题呢?
虚继承:在派生类要继承方式前面加上virtual关键字:针对于这个问题,我们基于上面的代码稍加修改即可:
//菱形继承
#include<iostream>
using namespace std;
class B
{
public:
int _b;
};
class C1 :virtual public B//请留意此处
{
public:
int _c1;
};
class C2 :virtual public B//请留意此处
{
public:
int _c2;
};
class D :public C1, public C2
{
public:
int _d;
};
void FunTest1()
{
D d;
d._b=1;//此时我们会发现二义性的问题消失了,我们对他进行赋值
d._c1 = 2;
d._c2 = 3;
d._d = 4;
cout << sizeof(D) << endl;//24
}
int main()
{
FunTest1();
return 0;
}
此时对象d的大小变为了24个字节,那他又是怎么来的,公有继承,但又是怎么解决这个_b成员二义性的问题呢?
我们对它的各个成员进行赋值,&d查看一下他的内存布局是怎么回事?
如下:
看起来像一个指针,我们打开内存看一下结构,似乎数字有些含义:
我们再看c1里面的指针:
通过程序调用结果,我们发现二义性的问题得到了解决,看来这个指针就是来解决二义性的问题:那他的所指的内容又是怎么解决这个问题呢?
我们看一下汇编:
[eax+4]->ecx取到0x14,然后把1放到该偏移量对应的地址中,我们发现此时_b被修改了。0x14就是偏移量,
同理,我们可以知道C2的指针指向的内容为_b相对于自己的偏移量为0x c。系统凭借偏移量就可以将两份一样的对象变成了一个,我们用对象d直接访问_b也是指向同一块内存。因此解决了而二义性的问题。‘
当然结构体的大小为24我们也就明白了。’(见上图内存布局图)