再上一篇:11.2 初始化基类成员
上一篇:11.3 冲突、支配规则和赋值兼容性
主页
下一篇:第十二章 类的其它特性
再下一篇:12.2 虚函数
文章列表

11.4 虚基类

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

在C++中,假定已定义了一个公共的基类A,类B由类A公有派生,类C也由类A公有派生,而D是由类B和类C共同公有派生。显然在类C中包含了类A的二个拷贝(实例)。这种同一个公共的基类在派生类中产生多个拷贝不仅多占用了存储空间,而且可能会造成多个拷贝中的数据不一致。

例11.12 一个公共的基类在派生类中产生二个拷贝。

#include

class A{

public:

int x;

A(int a=0) { x=a;}

};

class B:public A{

public:

int y;

B(int a=0, int b=0 ):A(b) { y=a;}

void PB() { cout<<"x="<

};

class C:public A{

public:

int z;

C(int a=0,int b=0):A(b)

{ z=a; }

void PC() { cout<<"x="<

};

class D:public B,public C {

public:

int m;

D(int a, int b,int d,int e,int f):B(a,b),C(d,e)

{ m=f; }

void Print(void)

{ PB();

PC();

cout<<"m="<

}

};

void main(void)

{

D d1(100,200,300,400,500);

d1.Print();

}

执行程序后输出以下3行:

x=200 y=100

x=400 z=300

m=500

基类A 基类A

基类B 基类C

类D

图 11.2 派生类中包含同一基类的二个拷贝

根据输出的结果,可以清楚地看出,在类D中包含了公共基类A的二个不同的拷贝(实例)。这种派生关系,产生的类体系如图 11.2所示。

例11.13 派生类中包含同一基类的二个拷贝,产生使用上的冲突。

#include

class A{

public:

int x;

A(int a=0) { x=a;}

};

class B:public A{

public:

int y;

B(int a=0, int b=0 ):A(b) { y=a;}

};

class C:public A{

public:

int z;

C(int a=0,int b=0):A(b)

{ z=a; }

};

class D:public B,public C {

public:

int m;

D(int a, int b,int d,int e,int f):B(a,b),C(d,e)

{ m=f; }

void Print(void)

{

cout<

cout<

cout<

}

};

void main(void)

{

D d1(100,200,300,400,500);

d1.Print();

}

编译器认为E行和F行有错,因无法确定成员x是从类B中继承来的,还是从类C中继承来的,产生了冲突。可使用作用域运算符来限定成员属于类B或类C,用B::x代替E行中的x,用C::x代替F行中的x,上面的程序就可以正确编译执行了。

在多重派的过程中,如欲使公共的基类在派生类中只有一个拷贝,则可以将这种基类说明为虚基类。在派生类的定义中,只要在基类的类名前加上关键字virtual,就可以将基类说明为虚基类。一般格式为:

class ClassName: virtual ClassName

{ ......};

class ClassName: virtual ClassName

{ ......};

其中关键字virtual可放在访问权限之前,也可以放在访问权限之后,并且该关键字只对紧随其后的基类名起作用。

例11.14 定义虚基类,使派生类中只有基类的一个拷贝。

#include

class A{

public:

int x;

A(int a=0) { x=a;} //F

};

class B:virtual public A{

public:

int y;

B(int a, int b ):A(b) { y=a;}

void PB()

{ cout<<"x="<

};

class C:public virtual A{

public:

int z;

C(int a,int b):A(b)

{ z=a; }

void PC()

{ cout<<"x="<

};

class D: public B,public C {

public:

int m;

D(int a, int b,int d,int e,int f):B(a,b),C(d,e) //G

{ m=f; }

void Print(void)

{ PB();

PC();

cout<<"m="<

}

};

void main(void)

{

D d1(100,200,300,400,500);

d1.Print();

d1.x=400;

d1.Print();

}

本例中定义的派生关系,产生的类体系如图 11.3所示。

基类A

基类B 基类C 类D

图 11.3 派生类中包含同一基类的一个拷贝

执行以上程序,输出以下6行:

x=0 y=100

x=0 z=300

m=500

x=400 y=100

x=400 z=300

m=500

从程序的输出可以看出二点:首先是在派生类D的对象d1中确实只有基类A的一个拷贝,当改变成员x的值时,由基类B和C中的成员函数输出的x的值是相同的。其次是x的初值为0。为什么在G行,类D的构造函数分别调用了其基类B和C的构造函数,而类B和类C的构造函数又分别调用了类A的构造函数,而成员x的值仍为0呢?这是因为调用虚基类的构造函数的方法与调用一般基类的构造函数的方法是不同的。由虚基类经过一次或多次派生出来的派生类,在其每一个派生类的构造函数的成员初始化列表中必须给出对虚基类的构造函数的调用;如果未列出,则调用虚基类的缺省的构造函数。在这种情况下,在虚基类的定义中必须有缺省的构造函数。

类D的构造函数尽管分别调用了其基类B和C的构造函数,由于虚基类A在类D中只有一个拷贝,所以,编译器无法确定应该由类B的构造函数来调用类A的构造函数,还是应该由类C的构造函数来调用类A的构造函数。在这种情况下,编译器约定,在执行类B和类C的构造函数时都不调用虚基类A的构造函数,而是在类D的构造函数中直接调用虚基类A的缺省的构造函数。由F行可知,该构造函数将x的值置为0。所以输出x的初值为0。若将F行改为:

A(int a) { x=a;} //F

重新编译这程序时,将指出G行有错,无法调用类A的缺省的构造函数。若将G行改为:

D(int a, int b,int d,int e,int f):B(a,b),C(d,e),A(1000) //G

即在类D的构造函数的成员初始化列表中增加调用虚基类A的构造函数,则将类D中的x成员的初值置为1000。

我们再次强调,用虚基类进行多重派生时,若虚基类没有缺省的构造函数,则在派生的每一个派生类的构造函数的成员初始化列表中都必须有对虚基类构造函数的调用。例如,设有虚基类V,类E由虚基类V派生,类F也由虚基类V派生,类G由基类E和F共同派生,类H由基类G和虚基类V共同派生,则类E、F、G和H的所有构造函数的成员初始化列表中,都必须列出对虚基类V的构造函数的调用。

习题与思考11

1.把定义直角坐标系上的一个点的类作为基类,派生出描述一条直线的类(二点坐标值确定一直线),再派生出三角形类(三点坐标确定一个三角形)。成员函数能求出二点间的距离,三角形的周长和面积等。设计一个测试程序,并构成完整的程序。

2. 阅读下面程序,写出执行结果:

#include

class A{

public:

int i;

void print() {cout<

};

class B:public A{

public:

void print() {cout<

};

class C:private A{

public:

C( ) { A::i=10; }

int i;

void print()

{ cout<

cout<

}

};

void main(void)

{

A a;

A *pa=&a;

B b, *pb;

C c, *pc;

c.i=1+(b.i=1+(a.i=1));

pa->print();

pb=&b;

pb->print();

pc=&c;

pc->print();

}

3.在定义派生类的过程中如何对基类的数据成员进行初始化?

4.在派生类中能否直接访问基类中的私有成员?在派生类中如何实现访问基类中的私有成员?

5.在多重继承中,在哪些情况下会出现冲突?如何消除冲突?

6.阅读下面程序,写出执行结果:

#include

class A{

int i,j;

public:

A(int a,int b) { i=a;j=b;}

void add(int x,int y)

{i+=x; j+=y; }

void print() {cout<<"i="<

};

class B:public A{

int x,y;

public:

B(int a,int b,int c,int d):A(a,b)

{ x=c;y=d;}

void ad(int a,int b)

{x+=a; y+=b; add(-a,-b); }

void p() { A::print(); }

void print() {cout<<"x="<

};

void main(void)

{

A a(100,200);

a.print();

B b(200,300,400,500);

b.ad(50,60);

b.A::print();

b.print();

b.p();

}

7.设计一个大学的类系统,大学中有学生、教师、干部和工人,学生的任务是学习,教师的任务是上课和科研,干部的任务是管理,工人的任务是定额生产产品。提取共性作为基类,并派生出满足要求的各个类及每一个类上的必须操作。设计一个完整的程序。8.设计一个描述儿童、成人和老人的类系统,儿童又分为学龄前和学龄期,成人有工作,老人已退休。提取共性作为基类,并派生出满足要求的各个类及每一个类上的必须操作。设计一个完整的程序。