菱形继承,顾名思义,这种继承方式的结构有着与菱形这个图形相似的结构,但首先我们先来了解一下单继承和多继承这两个概念。
单继承:一个子类只有一个直接父类时称这个继承关系是单继承。
多继承:一个子类有两个或两个以上直接父类时称这个继承关系是多继承。
这样,为了方便大家的理解我们接下来引入一个“栗子”。
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;//课程主修
};
这样,我们用一个直观的图来分析一下单继承与多继承。
接下来,我们来看一下菱形继承的图示。
相信看到这里大家一定有了一个清晰的认识了。那么菱形继承有没有缺陷呢?还是通过一段代码来看这个问题。
#include<iostream>
#include<string>
using namespace std;
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;//课程主修
};
int main()
{
Assistant a;
a._name = "刘禅";
a._name = "诸葛亮";
}
但是输出窗口却出现了这样的问题?
这就是类在继承时的同名隐藏,我们可以指定作用域来调。
#include<iostream>
#include<string>
using namespace std;
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;//课程主修
};
int main()
{
Assistant a;
a.Student::_name = "刘禅";
a.Teacher::_name = "诸葛亮";
}
这样,就不会出现上面的问题,但是现在我们从监视窗口来看一下。
我们看到了,当我们改了Student,Techer类的时候,两个类中的_name都被改掉了,什么鬼???现实生活中一个人怎么会拥有两个名字呢(这里不包含小名,“狗蛋”之类的外号啦,你的身份证只有一个名字对吧!!!)
从这个例子来看,Assistant的对象中有两份Person成员。
由此看来,菱形继承存在二义性和数据冗余(浪费空间)的问题。
这个时候,为了解决这个问题我们引入虚继承(这里用到关键字virtual) 。
尤其,注意!!!virtual的位置一定要注意!!!
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
string _name;//姓名
};
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;//课程主修
};
int main()
{
Assistant a;
a.Student::_name = "刘禅";
a.Teacher::_name = "诸葛亮";
}
现在,我们还是通过监视窗口看一下。
这样,我们就通过虚继承的方式解决了菱形继承的缺陷。
接下来,我们着重讲解一下菱形继承对象模型。为了方便说明,接下来换另外一个“栗子”。
#include<iostream>
#include<string>
using namespace std;
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()
{
//cout<<sizeof(D)<<endl;
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
B b;
C c;
b = d;
c = d;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
B bb;
bb._a = 6;
bb._b = 7;
return 0;
}
这次我们选择在内存中来看一下这个问题,因为监视窗口有优化作用,就好比用化妆来迷惑你,从底部来看她,给她卸个妆。
这次,我们来比较一下有无虚继承的差异,左边代表没有,右边代表虚继承。
我们可以很清楚的看到A在D中单独只放了一份,这里我们叫做虚基类,而当我们使用A时,由于在空间里它只存放了一份,那就需要一个东西能够准确地找到,需要存放它的相对地址,即偏移量,这里B指向的地址空间,也就是绿色框的第二行就存放了这个数据,让我们能够准确找到,这个表就叫做偏移表。
目前为止我们用虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。
1.很明显,实现起来很复杂啊,不到万不得已都不要定义菱形结构的虚继承体。
2.再其次效率会受到影响,在这里我们用的都是间接访问,多次访问一定会浪费时间,随之带来性能上的损耗。