继承分为:单继承、多继承和菱形继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承。
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。
菱形继承:菱形继承是多继承的一种特殊情况。有两个子类继承同一个父类,而又有子类同时继承这两个子类。
通过上面的图可以看出菱形继承有数据冗余和二义性的问题,在Assistant的对象中Person成员会有两份。
以学生老师和课程三个关系为例
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 = "Tom";//这样会有二义性,无法明确知道访问的是Student的成员还是Teacher的成员
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "Tom";
a.Teacher::_name = "peter";
}
为了解决菱形的二义性和数据冗余问题就引出了虚拟继承,在上述代码Student和Teacher继承Person时使用虚拟继承,只需要在public前加上virtual即可。
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 ; // 主修课程
};
void Test ()
{
Assistant a;
a._name = "小明"
}
既然虚拟继承能解决数据冗余和二义性问题,那它的原理是什么呢?
下图是菱形继承的内存对象成员模型:这里数据对象A有两个不同的地址且有两个不同的数据,出现了数据冗余。
接下来看菱形虚拟继承的内存对象成员模型:
从图中可以看出D对象中将A放到了对象组成的最下面,这个A同时属于B和C,那B和C如何去找公共的A呢,我们发现B和C对象里分别有一个地址,这两个地址就是两个指针,指向了两个表,这两个指针叫虚基表指针,而这两个表叫虚基表。虚基表里存有离A距离的偏移量,通过偏移量可以找到公共的A。
至于为什么D中的B和C要去找属于自己的A,有这样一个场景:
D d;
B b = d;
C c = d;
当发生赋值时,d 要找B或C成员中的A才能赋值过去。
继承的总结和反思
C++语法的复杂性在于,C++有继承,有继承就会有多继承,多继承算是C++中的一个缺陷吧。因为有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,它的底层实现是很复杂的。
接下来我们要谈谈继承和组合的区别:
从前面学过的继承中可以看出public继承实际是一种is-a的关系,也就是说每个派生类对象都是一个基类对象。而组合是一种has-a的关系,假设B组合了A,那么每个B对象中都有一个A对象。
继承允许你根据基类的实现来定义派生类的实现,这种通过生成派生类的复用通常称为白箱复用。“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见,继承一定程度上破坏了基类的封装,基类的改变会对派生类产生很大的影响。因此派生类和基类的依赖关系是很强的,耦合度高。
对象组合是类继承之外的另一种复用选择,新的更复杂的功能可以通过组装或组合对象来获得,对象组合要求被组合的对象具有良好定义的接口,这种复用风格称为黑箱复用,黑箱复用对象内部的细节是不可见的。组合类之间没有很强的依赖关系,所以耦合度低,封装性好。总而言之:组合就是两个及以上相互独立的类能够放在一起,然后通过一个类就可以调用另一个类的对象从而调用另一个类的功能。比如有一辆车和一个音响两个独立的类,车里有方向盘可以用来开车,有座椅可供人去坐,音响可以通过连接蓝牙来播放音乐,当把音响安装到车里就把音响的功能组合到车里去了。
可见,继承体现的是一种专门化的概念,而组合体现的是一种组装的概念,将不同的功能组合在一起。
什么时候用继承?什么时候用组合?
尽量多去用组合。因为组合的耦合度低,代码维护性好。但有些关系就适合用继承那就用继承,比如要实现多态就必须用继承。所以当继承和组合都能用的时候就用组合。