首页 理论教育C++程序设计基础教程:虚基类及其指针

C++程序设计基础教程:虚基类及其指针

【摘要】:虚基类的声明是用在指定派生类时,放在派生类名的后面。该指针被称为虚基类指针。解:上述程序中,各类之间构成类层次如图6.14所示,其中虚线表示是虚继承,也就是说,其基类为虚基类。

1.虚基类的声明

当某类的部分或全部直接基类是从另一个共同基类派生而来的时,这些直接基类中从上一级基类继承来的成员就拥有相同的名字。这时可以将直接基类的共同基类设置为虚基类,从不同的路径继承过来的该类成员在内存中只拥有一个拷贝,从而解决了同名成员的唯一标识问题。

虚基类的声明是在派生类的定义过程中进行的,在基类的继承方式前加上关键字“virtual”,其语法格式为:

class派生类名:virtual 继承方式基类名其中,“virtual”是虚基类的关键词。虚基类的声明是用在指定派生类时,放在派生类名的后面。

【例6.12】分析以下程序的执行结果。

解:上述程序中,虽然B和C是由共同的基类A派生而来,但由于引入了虚基类,其类层次如图6.13所示(带箭头的虚线表示虚继承),这时的数据成员x只存在一份拷贝,无论是B∷x,还是C∷x,其结果都是一样的,所以可以用x直接访问它的值。程序的执行结果如下:

x=0,y=1

x=0,z=3

m=5

x=4,y=1

x=4,z=3

m=5

图6.13 虚基类的类层次图

引进虚基类后,派生类(即子类)的对象中只存在一个虚基类的子对象。当一个类有虚基类时,编译系统将为该类的对象定义一个指针成员,让它指向虚基类的子对象。该指针被称为虚基类指针。

2.虚基类的构造函数

如果派生类有一个虚基类作为祖先类,那么在派生类构造函数的初始化列表中需要列出对虚基类构造函数的调用,如果未列出则表明调用的是虚基类的无参数构造函数。派生类构造函数调用的次序如下:

(1)先调用基类的构造函数,多个基类则按派生类声明时列出的次序、从左到右调用,而不是初始化列表中的次序。

(2)再次调用对象成员(子对象)的构造函数,按类声明中对象成员出现的次序调用,而不是初始化列表中的次序。

(3)最后执行派生类的构造函数。

在以上构造函数调用过程中,同一层中对虚基类构造函数的调用总是先于普通基类的构造函数。

【例6.13】分析以下程序的执行结果。

解:上述程序中,各类之间构成类层次如图6.14所示,其中虚线表示是虚继承,也就是说,其基类为虚基类。程序的执行结果如下:

class A

class B

class C1

class C2

class D

图6.14 类层次图

本例中各类没有数据成员,若包含有数据成员,需在构造函数中增加初始化列表,例如,增加了私有数据成员的程序如下:

程序执行结果如下:

class A:a=10

class B:50

class C1:c1=28

class C2:c2=26

class D:d=13

从结果看到,调用基类A的构造函数次序是D的B(i,j)→B的A(i)→执行A(i),而C1和C2的构造函数中B(2*i,j)和B(3*i,j)被忽略。也就是说,从虚基类派生的子类的构造函数的初始化列表中都要列出对虚基类构造函数的调用,但只有用于建立对象的派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类对象只初始化一次。

【例6.14】分析以下程序的执行结果。(www.chuimin.cn)

解:本程序的执行结果如下:

M:in B

B:from G

M:in C

C:from G

M:in A

A:from E

M:in E

E:from G

M:in D

D:from F

M:in F

F:from G

M:in G

G:from main

本例中各个类的派生层次如图6.15所示。其中每个类都有一个嵌入的M类的成员,有4个继承是虚继承:E派生自B和C,F派生自B和C。

图6.15 类层次图

在main()函数中,g的初始化需要首先初始化它的E和F部分,但是B和C子对象首先被初始化,因为它们是虚基类,并且两者的初始化在G的构造函数的初始化中进行,G是最高层派生类。类B没有基类,所以它的成员对象m被初始化,然后它的构造函数输出“B:from G”,对于E的C子对象处理相同。E子对象的初始化需要先对A、B和C子对象进行初始化。因为B和C已经被初始化,于是E子对象的A子对象接着被初始化,然后是E子对象自己初始化。相同的情况重复出现在G的F子对象上,但是虚基类的初始化不重复进行。

注意:在一般情况下,虚基类只允许定义不带参数的或带默认参数的构造函数。

3.虚基类的析构函数

同样,如果存在虚基类时,派生类析构函数的执行会涉及其基类或虚基类的析构函数的执行。析构函数的调用次序与构造函数的调用正好相反。派生类析构函数调用的次序如下:

(1)首先执行派生类的析构函数。

(2)其次调用对象成员(子对象)的析构函数,按类声明中对象成员出现的逆序调用,而不是初始化列表中的次序。

(3)再次调用普通基类的析构函数,多个基类则按派生类声明时列出的逆序、从右到左调用,而不是初始化列表中的次序。

在以上析构函数调用过程中,同一层中对普通基类构造函数的调用总是先于虚基类的析构函数。

【例6.15】分析以下程序的执行结果。

图6.16 类层次图

解:上述程序是将例6.14程序中的所有构造函数改为析构函数得到的,其对应的类层次图如图6.16所示,本程序的执行结果如下:

class E

class C

class B

class D

class B

class A

从结果可以看到,派生类的析构函数执行次序与构造函数的执行次序正好相反。