类的多重继承

时间:2020-12-11 20:01:31

类的多重继承

引入:通常一个事物会同时具有多种基类的属性,如一个老师同时是职员也是一位父亲,这个时候就会使用多重继承来展示事物的本质属性

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中。

在其它的隐式的转换中发生的也是这些情况,如函数的形参为基类或基类的指针型,传入子类型,或子类型的指针。

 

提醒:赋值兼容现象出现在子类型和基类中。