一 :菱形继承的问题
普通的菱形继承存在数据冗余和二义性的问题 ,如下代码:
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 _majorCourse; //主修课程
};void Test() {Assistant a;a._name = "peter";//这样会有二义性无法明确知道访问的是哪一个// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";
}
解释:
显示地指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决 ,所以有了虚拟菱形继承
二:虚拟菱形继承
class Person {
public:string _name;int _hugeArr[10000];
};// 虚继承
class Student : virtual public Person {
protected:int _num;
};// 虚继承
class Teacher : virtual public Person {
protected:int _id;
};class Assistant : public Student, public Teacher {
protected:string _majorCourse;
};
在类腰部位置加一个 virtual 关键字
虚拟继承不但解决数据冗余,还解决了二义性
为何请看三~
三:虚拟菱形继承的原理
为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助 内存窗口观察对象成
员的模型。
1:非虚拟菱形继承的内存分布
//类A
class A
{
public:int _a;
};//B虚拟继承A
class B : public A
{
public:int _b;
};//C虚拟继承A
class C : public A
{
public:int _c;
};//D再继承B和A
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这个对象中存放了所有的变量,无论是继承和还是自己的
②:1和3紧挨因为类B中是这两个成员变量,2和4紧挨着因为类C中是这两个成员变量
③:d的成员变量放在最后
2:虚拟菱形继承的内存分布
//类A
class A
{
public:int _a;
};//B虚拟继承A
class B : virtual public A
{
public:int _b;
};//C虚拟继承A
class C : virtual public A
{
public:int _c;
};//D再继承B和A
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不再存在两份,变成只有一份
Q:类B和类C中的第一行存放的地址有什么意义?
A:解释如图
总结:虚继承中,B和C类中,并没有直接存放类A,类A被存放在了一个公共的位置,在类B和C中,额外存放了一个地址,该地址指向的地方为虚基表,各自的虚基表中能得到各自的类和类A的相隔字节数
Q:为什么这么的麻烦?不直接把相隔的字节数直接存放在类B和类C中?却要存放一个地址,改地址指向的虚基表中才有相隔的字节数
A:因为虚基表中不止仅仅有相隔字节数这一信息,很多信息暂时还没到学的时候,而类B和类C中只有4字节的位置,所以存在虚基表的地址刚刚好
一个需要虚基表的场景:
B* pb = &d;//d是一个已有类D的对象
pb->_a++;
解释:在代码中的切片赋值的时候,我们只保留了类B的部分,此时我们要在pb中找到A的成员变量,通过类B中的虚基表即可