在前面学习了一个派生类只有一个基类,这种派生方法称为单继承或单基派生。当一个派生类具有两个或多个基类时,这种派生方法称为多重继承或多基派生。
多重继承派生类的声明
在C++中,声明具有两个以上基类的派生类与声明单基派生类的形式相似,只需要将继承的多个基类用逗号分隔即可,声明的一般形式如下:
class 派生类名:继承方式1 基类名1,……继承方式n 基类名n
{
派生类新增的数据成员和成员函数
}
默认的继承方式是private,当出现缺省时,继承方式为private
说明:对基类成员的访问必须是无二义的
当出现二义性,我们怎么解决。
多重继承派生类的构造函数与析构函数
多重继承下派生类构造函数的定义形式与单继承派生类构造函数的定义形式相似,只是n个基类的构造函数之间用逗号分隔。多重继承下派生类构造函数的定义的一般形式如下:
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2)……基类名n(参数表n)
{
派生类新增数据成员初始化语句;
}
多重继承的构造函数的执行顺序和单继承构造函数一样,先执行基类的构造函数,再执行对象成员的构造函数,最后执行派生类的构造函数。
处于同一层次的各个基类构造函数的执行顺序,取决于声明派生类时所指定的各个基类的顺序,与派生类构造函数中定义的成员初始化列表的各项顺序没有关系。
析构函数的执行顺序则刚好与构造函数的执行顺序相反。
由于基类与派生类的析构函数是相互独立的,所以不会因为派生类没有析构函数则基类的析构函数得不到使用。
虚基类
如果一个类有多个直接基类,而这些直接基类又有一个共同的基类,则在最底层的派生类中会保留这个间接的共同基类数据成员的多份同名成员。在访问这些同名的成员时,必须在派生类对象名增加直接基类名,使其唯一地标识一个成员,以免产生二义性。
为了防止图中所示的二义性出现,C++引入虚基数的概念。
虚基类的概念
上图中出现二义性的原因是,derived含有两个a,两个a都是base基类继承而来的,如果使derived只存在一个base继承而来的a,那么我们在derived类使用a时就不会产生二义性。在C++中,如果想使这个公共的基类只产生一个复制,则将这个基类说明为虚基类。虚基类在派生类的声明语法:
class 派生类名:virtual 继承方式 基类名
{
……
}
经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是说,基类成员只保留一次。
上述程序中,从类base派生出base1,base2,使用关键字virtual,把类base声明为base1,base2的虚基类。这样,从base1,base2派生出的derived只继承一次base,也就是说基类base的成员在derived中只保留一次。
程序的运行结果分析:
我们先调用基类的构造函数,所以第一句没有疑问。
我们看派生类derived,先继承base1,所以输出第二句;
derived先继承base1,再继承base2,因为base1和base2都是base的虚基类,内容只保留一次,内容保留在了base1(先继承),所以我们a的值变成了20,我们调用base2的构造函数,初始值a=20,所以base构造函数输出的a为40.
我们数据成员保留的是最后继承的数据成员的值。
虚基类的初始化
虚基类的初始化与一般的多继承的初始化语法上是一样的,但构造函数的调用顺序不同。在使用虚基类机制时应该注意以下几点:
1)如果在虚基类中定义有带参数的构造函数,并且没有定义默认形式的构造函数,则整个继承结构中,所有直接或间接的派生类都必须在构造函数的初始化成员列表对虚基类构造函数的调用,以初始化在虚基类中定义的数据成员。
2)建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。该派生类的其他对虚基类的构造函数的调用都自动被忽略。
3)若同一层次同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类的构造函数。
4)对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下。
对于非基类,构造函数的执行顺序仍是先左后右,自上而下。
5)若虚基类由非虚基类派生而来,仍是先调用基类的构造函数,再调用派生类的构造函数。
6) 关键字virtual与派生方式的关键字的先后顺序无关紧要,它们是等价的
7)一个基类在作为某些派生类的虚基类的同时,也可以作为另外派生类的非虚基类。