类的多重继承
引入:通常一个事物会同时具有多种基类的属性,如一个老师同时是职员也是一位父亲,这个时候就会使用多重继承来展示事物的本质属性
1:形式
class A:[继承方式]B,[继承方式]C {} 注:以逗号在多个基类间产生分隔
2:构造函数
同样是在派生类构造函数中利用函数总表初始化各个基类,先调用基类的构造函数,后执行函数体,调用基类构造函数的顺序是按照继承声明时的顺序来的。
3:两个问题
数据重复问题:
多个基类很可能会造成派生类继承到一些重复的属性,比如上例中老师有职员的所有特征,也有父亲的所有特征,但是父亲和职员都有年龄,住址,爱好。。。这就会造成数据的重复,总不能有两个年龄吧,这里要区分好继承和子对象的一些区别,不能误以为研究生类的导师姓名和学生的姓名也属于数据重复,此时导师是研究生的一个子对象,只能说研究生有一个什么什么样的导师(类的组合),而不能说研究生是一个什么什么样的导师(类的继承)。
二义性问题:
当多个基类中的都有某一属性,且标志符还相同时,就不仅存在数据重复的问题了,还存在这二义性。比如职员类和父亲类中年龄都命名为m_nAge时,在派生类中若之间使用m_nAge来使用对象时,编译器便不能获知你想使用的究竟是哪个基类中的数据。
解决办法:在会产生二义性的地方,标明你想使用对象的作用域,如Teacher::m_nAge,一定要将作用域标注的有区分度。如果此时派生类中又重新定义一个名为m_nAge的数据,那么它将会屏蔽其它任何一个同名的数据
可以这样来理解 同名数据引用的优先级:派生类> 基类1=基类2….
数据重复问题
虚基类:主要用来处理,派生的多个直接基类有共同的基类的情况,因为此时我们可以明确,这种情况一定会有多余的数据。当这些直接基类没有基类的时候,编译器便不能肯定是否有数据重复,因为这些基类可能会用不同的标识符来标明一个属性,如年龄。
使用方法:在间接基类继承继承说明继承方式后加上virtual关键字,而派生类继承说明时则不用加。
派生类的构造函数:
不仅要负责直接执行基类构造函数,而且还要执行总基类构造函数(与普通继承不同), 但是由于总基类在派生类中只有一份数据成员,所以这份成员的初始化必须由派生类直接给出,编译器只执行最后的派生类对总基类构造函数的调用,而不执行总基类的直接派生类对总基类构造函数的调用,这会保证总基类的数据成员不会被多次初始化。
如
class A 总基类
{public:
A(int n){};
}
class B:public virtual A 间接基类
{ public:
B(int n):A(n){};
}
Class C: public virtual A 间接基类
{ public:
C(int n):A(n){};
}
Class D: pubic B, public C
{ public:
D(int n) :B(n), C(n) , A(n){}
}
一般说来,不提倡在程序中使用多重继承,只有在简单且不易出现二义性的时候才会使用,避免关系混乱。
5:基类和派生类之间的转换
须知:只有公用的派生类才是基类真正的子类型,它完整的继承类基类的功能,当使用保护或者私有继承的时候,就会改变数据成员在基类中的访问属性,就像基类中的public成员就不能在派生类外进行访问了,换句话说,这时派生类就开始和基类产生一些重要的不容忽视的区别。
赋值兼容:在基类和其子类型之间也存在与类型转换相似的行为,在某些需要基类的地方可以使用子类型进行替代(因为子类型具有基类的特征,如需要本科生也可以用研究生替代,但是反过来就说不通,基类并不一定具有子类型的特征)。具体的替代情形有以下几种。
1:指针,引用替代
A one; B two;如B是A的子类型,那么 可以有
A *pA = &two;
这种使用没有错,但是pA还是指向A类的指针,所以仍然不能用pA访问派生类新增的数据成员。准确来说pA是一种被截断的B*型指针,只是子类型中的基类成员的地址。引用实质也是指针
因此也存在A&rA = two;但rA也只关联基类那一部分成员。
2:值转换
如:one = two;同样将two 中的基类部分拷贝到one中。
在其它的隐式的转换中发生的也是这些情况,如函数的形参为基类或基类的指针型,传入子类型,或子类型的指针。
提醒:赋值兼容现象出现在子类型和基类中。