派生类只有一个基类,这种派生方法称为单继承或单基派生。当一个派生类具有两个或多个基类时,这种派生方法称为多重继承或多基派生。
1. 多重继承派生类的声明
在C++中,声明具有两个以上基类的派生类与声明单基派生类的形式相似,只需将要继承的多个基类用逗号分隔即可,其声明的一般形式如下:
1 | class 派生类名:继承方式1 基类名1,...... ,继承方式n 基类名n{ |
注意:对基类成员的访问必须是无二义性的,例如下列程序段对基类成员的访问是二义性的,必须想办法消除二义性。
1 | clas X{ |
如果定义类Z的对象obj:
Z obj;
则以下对函数f()的访问是二义性的:
obj.f(); //二义性错误,不知调用的是类X的f(),还是类Y的f()
使用成员名限定可以消除二义性,例如:
obj.X::f(); //调用类X的f()
obj.Y::f(); //调用类Y的f()
2. 多重继承派生类的构造函数与析构函数
多重继承下派生类构造函数的定义形式与单继承派生类构造函数的定义形式相似,只是n个基类的构造函数之间用逗号分隔。多重继承下派生类构造函数定义的一般形式如下:
1 | 派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),...... , 基类名n(参数名n){ |
派生类的参数个数必须包含完成所有基类初始化所需的参数个数。
多重继承的构造函数的执行顺序与单继承构造函数的执行顺序相同,也是遵循先执行基类的构造函数,再执行对象成员的构造函数,最后执行派生类构造函数的原则。在多个基类之间,则严格按照派生类声明时从左向右的顺序来排列先后。
由于析构函数是不带参数的,在派生类中是否要定义析构函数与它所属的基类无关,所以与单继承情况类似,基类的析构函数不会因为派生类没有析构函数而得不到执行,他们各自是独立的。析构函数和构造函数的执行顺序正好是相反的。
3. 虚基类
如果一个类有多个直接基类,而这些直接基类又有一个共同的基类,则在最低层的派生类中会保留这个间接的共同基类数据成员的多份同名成员。在访问这些同名的成员时,必须在派生类对象名后面增加直接基类名,使其惟一的标识一个成员,以免产生二义性。
3.1 虚基类的概念
在C++中,如果想使这个公共的基类只产生一个复制,则可以将这个基类说明成虚基类。这就要求从类 Base派生新类时,使用关键字virtual将类Base说明成虚基类。
虚基类在派生类中的声明,其语法形式如下:
1 | Class 派生类名:virtual 继承方式 基类名{ |
经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是说,基类成员只保留一次。
1 | 虚基类的使用 |
3.2 虚基类的初始化
虚基类的初始化与一般的多继承的初始化在语法上是一样的,但构造函数的调用顺序不同。在使用虚基类机制时应该注意一下几点:
(1)如果在虚基类中定义有带形参的构造函数,并且没有定义默认形式的构造函数,则整个继承结构中,所有直接或间接的派生类都必须在构造函数的成员初始化列表中列出对虚函数类构造函数的调用,以初始化在虚基类中定义的数据成员。
(2)建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。该派生类的其他基类对虚基类构造函数的调用都自动被忽略。
(3)若同一层次中同时包含虚基类和非虚基类,应先调用虚基类中的构造函数,再调用非虚基类的构造函数,最后调用派生类构造函数。
(4)对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下。
(5)对于非虚基类,构造函数的执行顺序仍然是先左后右,自上而下。
(6)若虚基类由非虚基类派生而来,则仍然先调用基类构造函数,再调用派生类的构造函数。
1 | 虚基类的派生类构造函数的执行顺序。 |
不难看出,上述程序中虚基类Base的构造函数只执行了一次。显然,当Derived的构造函数调用了虚基类Base的构造函数之后,类Base1和类Base2对Base构造函数的调用被忽略了。这也是初始化虚基类和初始化非虚基类不同的地方。
说明:
(1)关键字virtual与派生方式关键字(public或private)的先后顺序无关紧要,它只说明是“虚拟派生”。
1 | class Derived:virtual public Base{ |
(2)一个基类在作为某些派生类虚基类的同时,又作为另一些派生类的非虚基类,这种情况是允许的。