第8章 继承性和派生类.ppt_第1页
第8章 继承性和派生类.ppt_第2页
第8章 继承性和派生类.ppt_第3页
第8章 继承性和派生类.ppt_第4页
第8章 继承性和派生类.ppt_第5页
已阅读5页,还剩86页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、C+程序设计,第8章继承性和派生类,8.1基类和派生类 8.2单继承 8.3多继承 8.4虚基类 8.5冲突、支配规则和赋值兼容规则,主要内容,基类和派生类,类的继承与派生是指:新类继承了原有类的特征,或者由原有类派生出新类。 原有类:基类或父类。 新类:派生类或子类。 派生新类的过程一般包括三个步骤:吸收原有类的成员;调整原有类的成员;添加新的成员。 一、派生类的定义 class 派生类名:继承方式 基类名1,继承方式 基类名n 派生类成员定义; ;,基类和派生类,单继承:派生类只有一个基类的情况。 多继承:派生类同时有多个基类的情况。 继承方式:访问控制的方式(public,protect

2、ed,private),缺省情况下,认为是private继承。 一个多继承实例: class Dr1:public Base1,private Base2 public: Dr1(); Dr1(); ;,基类和派生类,二、派生类的生成过程 1、继承基类成员 除构造函数、析构函数之外,其余的所有成员都被继承。 2、改造基类成员 改造基类成员的访问控制:依靠定义派生类时的继承方式实现。 覆盖基类成员:在派生类中定义与基类成员同名的成员,发生同名覆盖。 3、添加新成员,单继承,继承方式 2派生类构造函数和析构函数,一、公有继承:基类成员与派生类成员的关系:,基类,派生类,public派生,黄色:为基

3、类中的私有成员,也为派生类的不可访问成员 浅红色:为派生类中特有成员 橙色:为基类的公有成员,也成为派生类的公有成员 深红色:为基类中的保护成员,也成为派生类的保护成员,为基类及派生类、派生子类成员访问,为任何类成员访问,只为基类成员访问,单继承 -继承方式,基类的私有成员不可访问,基类的其它成员的访问属性不变。 例8.1 公有继承 #include /基类Point类的定义 class Point public: void InitP(float xx=0, float yy=0) X=xx;Y=yy; void Move(float xOff, float yOff) X+=xOff;Y+

4、=yOff; float GetX() return X; float GetY() return Y; private: float X,Y; ;,例8.1 公有继承,例8.1 公有继承,/派生类声明部分 class Rectangle: public Point public: /新增公有函数成员 void InitR(float x, float y, float w, float h) InitP(x,y);W=w;H=h;/调用基类公有成员函数给基类私有成员赋值,然后给新增私有成员赋值 float GetH() return H; float GetW() return W; pri

5、vate: /新增私有数据成员 float W,H; ; void main(void) Rectangle rect; /定义Rectangle类的对象 rect.InitR(2,3,20,10); /设置矩形的数据,例8.1 公有继承,rect.Move(3,2); /移动矩形位置 couttThe data of rect(X,Y,W,H):endl; couttrect.GetX(),/输出矩形的特征参数 rect.GetY(), rect.GetW(), rect.GetH()endl; coutnnt; ,基类的,单继承 -继承方式,二、私有继承 基类的私有成员不可访问,基类的其它

6、成员都以私有成员的身份出现。,私有继承基类成员与派生类成员的关系:,基类,派生类,黄色:为基类中的私有成员,也为派生类的不可访问成员 蓝色:为派生类中特有成员 草绿色:为基类的公有成员,也为派生类的私有成员 红色:为基类的保护成员,也为派生类的私有成员,private派生,为基类及派生类成员访问,只为基类及派生类成员访问,只为基类成员访问,单继承-继承方式,#include class Point/基类声明 public: void InitP(float xx=0, float yy=0) X=xx;Y=yy; void Move(float xOff, float yOff) X+=xOf

7、f;Y+=yOff; float GetX() return X; float GetY() return Y; private: float X,Y; ; class Rectangle: private Point/派生类声明 public:/新增外部接口,例8.2 私有继承1,例8.2 私有继承1,void InitR(float x, float y, float w, float h)InitP(x,y);W=w;H=h; /派生类访问基类公有成员 void Move(float xOff, float yOff) Point:Move(xOff,yOff); float GetX(

8、) return Point:GetX(); float GetY() return Point:GetY(); float GetH() return H; float GetW() return W; private:/新增私有数据 float W,H; ;,覆盖基类成员,例8.2 私有继承1,void main() Rectangle rect; /定义Rectangle类的对象 rect.InitR(2,3,20,10);/设置矩形的数据 rect.Move(3,2); /移动矩形位置 couttThe data of rect(X,Y,W,H):endl; couttrect.Get

9、X(),/输出矩形的特征参数 rect.GetY(), rect.GetW(), rect.GetH()endl; coutnnt; ,派生类的,例8.3 私有继承2,#include class A/基类声明 public: void setx(int i) x=i; void showx() coutxendl; private: int x; ; class B: private A/public A/派生类声明 public:/新增外部接口 void setxy(int m,int n)setx(m);y=n; void showxy()showx();coutyendl; priva

10、te:/新增私有数据 int y; ;,例8.3 私有继承2,void main() A a; a.setx(5); a.showx(); B b; b.setxy(10,20); b.showx(); b.showxy(); coutnnt; ,访问权限出错!?,三、保护继承 基类的私有成员不可访问,基类的其它成员都以保护成员的身份出现。 保护成员:对于派生类来说,基类的保护成员象公有成员一样可以被直接访问;但在类的外部,类的保护成员象私有成员一样不能被访问。,单继承-继承方式,保护继承基类成员与派生类成员的关系:,基类,黄色:为基类中的私有成员,也为派生类的不可访问成员 浅红色:为派生类中

11、特有成员 橙色:为基类的公有成员,也为派生类的保护成员 红色:为基类的保护成员,也为派生类的保护成员,protected 派生,为基类及派生类、派生子类成员访问,为基类及派生类、派生子类成员访问,只为基类成员访问,派生类,单继承-继承方式,如:#include class student private: int age; protected: char sex; public: int Getage(int a,char s) age=a; sex=s; return age; ; class graduatestudent: protected student private: int s

12、tunum; public: void Getnum(int st); ;,例8.4 保护继承,void graduatestudent:Getnum(int st) /派生类成员函数定义 stunum=st; /coutage:ageendl; /不可访问基类的私有成员 coutstunum:stunumendl; /派生类的私有成员 coutage:Getage(19,M)endl; /基类的公有成员 coutsex:sexendl; /即:student:sex 基类的保护成员 ,即:student:Getage( ),例8.4 保护继承,void main( ) graduatestu

13、dent zh; /生成派生类对象 /zh. Getage(19,M)( ); / 即:student:Getage( ); 不能访问 zh.Getnum(12); /访问派生类中的公有成员 /coutzh.sex; /即:student:sex,error保护成员 /coutzh.stunum; /error 私有成员 /coutzh.age; /即:student:age,error ,派生类的对象不能访问,但能为graduatestudent 派生的子类访问,例8.4 保护继承,上述三种不同的继承方式可用表格表示如下:,单继承-继承方式,单继承-派生类构造函数和析构函数,基类的构造函数和

14、析构函数不能被继承。从基类继承下来的成员的初始化工作还是由基类的构造函数(需参数设置)完成的,派生类中新增成员的初始化则由派生类的构造函数完成,对派生类对象的一些扫尾工作由派生类的析构函数完成。 一、派生类的构造函数 构造派生类的对象时,需要对基类的数据成员、新增数据成员、对象的数据成员进行初始化工作。 派生类构造函数的语法规则: 派生类名:派生类名(参数总表):基类名(参数表),对象成员名(对象成员参数表) 新增成员的初始化语句; ,如果基类定义了带形参的构造函数,派生类就必须定义构造函数。 派生类构造函数的执行次序: (1)、调用基类构造函数; (2)、调用对象成员的构造函数; (3)、执

15、行派生类的构造函数的函数体。,单继承-派生类构造函数和析构函数,#include class A/定义基类A public: A( )couttt类A不带参数的构造函数!n; A(int a) couttt类A带参数的构造函数!n; Aa = a; aa = a; aaa = a; ,例8.5public继承方式的访问权限的例子,void Aprint()couttt类A的private成员aa:Aaendl; int Aa; private: int aa; protected: int aaa; ; class B : public A/定义由基类A派生的类B public:,例8.5pu

16、blic继承方式的访问权限的例子,B()couttt类B不带参数的构造函数!n; B(int i, int j, int k); void Bprint()couttt类B的private成员bb和protected成员bbb:bb,bbbendl; void B_Aprint() couttt类B的public函数访问类A的public数据成员Aa:Aaendl; couttt类B的public函数访问类A的protected数据成员aaa:aaaendl;,例8.5public继承方式的访问权限的例子,GetAaaa(); GetAaaa1(); private: int bb; void

17、 GetAaaa() couttt类B的private函数访问类A的public数据成员Aa:Aaendl; couttt类B的private函数访问类A的protected数据成员aaa:aaaendl; ,例8.5public继承方式的访问权限的例子,protected: int bbb; void GetAaaa1() couttt类B的protected函数访问类A的public数据成员Aa:Aaendl; couttt类B的protected函数访问类A的protected数据成员aaa:aaaendl; ;,例8.5public继承方式的访问权限的例子,B:B(int i,int

18、j,int k):A(i),bb(j),bbb(k)/基类B的构造函数,需负责对基类A的构造函数的初始化 couttt类B带参数的构造函数!n; void main() B b1(100, 200, 300);/定义类B的一个带参数的对象b1,并初始化构造函数和基类构造函数 B b2;/定义类B一个不带参数的对象b2,并初始化构造函数和基类构造函数,例8.5public继承方式的访问权限的例子,b1.B_Aprint();/类B调用自己的成员函数B_Aprint函数 b1.Bprint();/类B对象b1访问自己的private和protected成员 b1.Aprint();/通过类B的对象

19、b1调用类A的public成员函数 ,例8.5public继承方式的访问权限的例子,我们来对程序的执行次序进行分析一下: (1)创建类B的一个带参数的对象b1时,先调用的是基类A带参数的构造函数,并同时将i的值100传递给aa!再调用的是基类B带参数的构造函数,并同时将j、k的值200、300传递给bb、bbb,即执行次序为:A (int a) - B(int i, int j, int k) (2)在创建类B一个不带参数的对象b2时,先调用的是基类A不带参数的构造函数!再调用的是基类B不带参数的构造函数,即执行次序为:A () - B() (3)调用类B自己的成员函数B_Aprint函数,分

20、别显示在类B的用public函数访问类A的public数据成员Aa和protected数据成员,例8.5public继承方式的访问权限的例子,aa,以及用类B的private函数访问类A的public数据成员Aa和protected数据成员aa。 (4)调用类B对象b1访问自己的private成员bb和protected成员bb (5)用类B的对象b1调用类A的public成员函数以访问类A的private成员aa 在公有继承(public)时,派生类的public、private、protected型的成员函数可以访问基类中的公有成员和保护成员;派生类的对象仅可访问基类中的公有成员。,例8.

21、5public继承方式的访问权限的例子,让我们把继承方式public改为private,编译结果出现1处如下错误: Aprint : cannot access public member declared in class A 出错语句在于:b1.Aprint();,因此,我们可以得出以下结论: 在私有继承(private)时,派生类的public、private、protected型的成员函数可以访问基类中的公有成员和保护成员;但派生类的对象不可访问基类中的任何成员。另,使用class关键字定义类时,缺省的继承方式是private,也就是说,当继承方式为私有继承时,可以省略private。

22、 让我们把继承方式public改为protected,可以看出,结果和private继承方式一样。 总之,在实际的应用当中我们尽量采用public的继承方式。,单继承-派生类构造函数和析构函数,当对象被删除时,派生类的析构函数被执行。由于析构函数也不能被继承,因此在执行派生类的析构函数时,基类的析构函数也将被调用。执行顺序是先执行派生类的构造函数,再执行基类的析构函数,其顺序与执行构造函数时的顺序正好相反。如果派生类中还有子对象时,还应执行包含对子对象释放的析构函数。 执行次序为: (1)清理新增普通成员:执行派生类析构函数体 (2)清理新增对象成员:执行对象成员所在类析构函数 (3)清理新增

23、基类成员:执行基类析构函数 以下通过一个实例来讨论析构函数的具体调用方法。,单继承-派生类构造函数和析构函数,#include class A/定义基类A public: A( )couttt类A不带参数的构造函数!n; A(int a) couttt类A带参数的构造函数!其参数值a=an; A( ) couttt类A的析构函数被调用!n; ;,例8.8具有对象成员时的具体控制的例子,class B : public A/定义由基类A派生的类B public: B()couttt类B不带参数的构造函数!n; /派生类B的构造函数,需负责对基类A的构造函数的调用及其对象成员的初始化 B(int

24、i, int j):A(i),membera2(j)couttt类B带参数的构造函数!n; B( )couttt类B的析构函数被调用!n; A membera1; A membera2; ;,例8.8具有对象成员时的具体控制的例子,void main() B b1(100, 200); /定义类B的一个对象b1,并初始化构造函数和基类构造函数 ,例8.8具有对象成员时的具体控制的例子,结果: 类A带参数的构造函数!其参数值a=100 类A不带参数的构造函数! 类A带参数的构造函数!其参数值a=200 类B带参数的构造函数! 类B的析构函数被调用! 类A的析构函数被调用! 类A的析构函数被调用!

25、 类A的析构函数被调用!,由上述例子我们可以得出以下结论: 派生类构造函数首先执行,其执行次序与例8.6相同,我们再来分析一下析构函数的执行次序: (1)执行派生类的析构函数的函数体: 程序的执行为调用派生类B的析构函数。 (2)调用对象成员的析构函数 依据对象成员声明的反次序:membera1 - membera2,两次调用对象成员(基类A)的析构函数。 (3)调用基类析构函数 一次调用基类A的析构函数。,单继承-派生类构造函数和析构函数,派生类构造函数和析构函数使用中应注意的问题: (1)构造函数不能被继承,因此派生类的构造函数只能通过调用基类的某个构造函数(如果有定义的话)来初始化基类子

26、对象。 派生类的构造函数只负责初始化自己定义的数据成员。 派生类构造函数的定义中可以省略对基类构造函数的调用,其条件是在基类中必须有缺省的构造函数或者根本没有定义构造函数。当然,基类中没有定义构造函数,派生类根本不必负责调用基类的构造函数。 (2) 当基类的构造函数使用一个或多个参数时,则派生类必须定义构造函数,提供将参数传递给基类构造函数途径。,单继承-派生类构造函数和析构函数,在有的情况下,派生类构造函数的函数体可能为空,仅起到参数传递作用。 派生类的构造函数的初始化列表中列出的均是直接基类的构造函数。 (3)先调用基类的构造函数,再调用派生类自己的数据成员所属类的构造函数,最后调用派生类

27、的构造函数;派生类的数据成员的构造函数被调用的顺序取决于在类中声明的顺序。 (4)析构函数不可以继承,不可以被重载。 (5)派生类的对象的生存期结束时调用派生类的析构函数,再调用派生类自己的数据成员所属类的析构函数,在该析构函数结束之前再调用基类的析构函数;所以,析构函数的被调用次序与构造函数相反。,单继承-派生类构造函数和析构函数,多继承,1多继承的概念 多继承的构造函数和析构函数 3 多继承的二义性问题,到目前为止,所讨论的类层次中,每个类只继承一个父辈,在现实世界中事情通常是这样的。但是一些类却代表两个类的合成。例如:沙发床SofaBed,它竖起来是一个沙发Sofa,拉开就是一张床Bed

28、;沙发床应允许同时继承沙发和床的特征。 多继承可以看作是单继承的扩展。所谓多继承是指派生类具有多个基类,派生类与每个基类之间的关系仍可看作是一个单继承。 例如: class Sofa;,多继承-多继承的概念,class Bed;class SofaBed : public Sofa, publicBed; 派生类SofaBed具有两个基类(类Sofa和类Bed),因此,类C是多继承的。按照继承的规定,派生类SofaBed的成员包含了基类Sofa和基类Bed中的成员以及该类本身的成员。,多继承-多继承的概念,同单继承一样,多继承构造派生类的对象时,也需要对基类的数据成员、对象的数据成员和新增数据

29、成员进行初始化工作。 1.派生类的构造函数 多继承下派生类构造函数的语法规则: 派生类名:派生类名(参数总表):基类名1(参数表1),对象成员名1(对象成员参数表1),基类名2(参数表3),对象成员名2(对象成员参数表2),派生类数据成员(数据成员参数表) 新增成员的初始化语句; ,多继承-构造函数和析构函数,几点说明: (1)有2点与单继承一样: 1)当基类有带形参的构造函数时,派生类就必须定义构造函数。 2)派生类构造函数的执行顺序相同。 (2)在定义派生类时,继承基类的先后次序,直接决定了基类构造函数的执行顺序;如果有对象成员时,对象成员的定义先后次序,也决定了对象成员的构造函数的执行顺

30、序。,多继承-构造函数和析构函数,调用子对象类的构造函数(的话),并同时将对应的参数值进行传递; (3)定义派生类的构造函数中的基类先后次序,以及对象成员的先后次序,在执行构造函数时,对执行顺序没有任何影响。 (4)在执行构造函数时,参数传递完全取决于形式参数对应值,特别要注意,派生类的数据成员也可以直接写在冒号的后面。,多继承-构造函数和析构函数,#include class B1/基类B1,构造函数有参数 public: B1(int i) couttconstructing B1 iendl; ; class B2/基类B2,构造函数有参数 public: B2(int j) coutt

31、constructing B2 jendl; ; class B3/基类B3,构造函数无参数 public:,例8.10多继承下派生类构造函数,B3() couttconstructing B3 *endl; ; class C: public B2, public B1, public B3/派生新类C,注意基类名的顺序 public:/派生类的公有成员 C(int a, int b, int c, int d,int e):B1(a),memberB2(d),memberB1(c),B2(b),cc(e) couttconstructing C ccendl; /a=1,b=2,c=3,d

32、=4,e=5 /B1(1),memberB2(4),memberB1(3),B2(2),cc(5) /注意基类名的个数与顺序、成员对象名的个数与顺序 private:/派生类的私有对象成员,注意成员对象名的个数与顺序,例8.10多继承下派生类构造函数,int cc; B1 memberB1; B2 memberB2; B3 memberB3; ; void main() C obj(1,2,3,4,5);/a=1,b=2,c=3,d=4,e=5 派生类构造函数的执行次序: (1)、调用基类构造函数; 依据派生类声明的次序:B2(b=2) - B1(a=1) - B3(). (2)、调用对象成员

33、的构造函数;对象声明的次序: memberB1(c=3) - memberB2(d=4) - memberB3(). (3)、执行派生类的构造函数的函数体。,例8.10多继承下派生类构造函数,结果: constructing B2 2 constructing B1 1 constructing B3 * constructing B1 3 constructing B2 4 constructing B3 * constructing C 5,二、派生类的析构函数 执行次序: (1)、清理新增普通成员:执行派生类析构函数体 (2)、清理新增对象成员:执行对象所在类析构函数 (3)、清理新增基

34、类成员:执行基类析构函数,多继承-构造函数和析构函数,#include class B1/基类B1,构造函数有参数 public: B1(int i)couttconstructing B1 b1=(b1=i)endl; B1()couttdestructing B1 b1=b1endl; int b1; ; class B2/基类B2,构造函数有参数 ,例8.11多继承下派生类析构函数,public: B2(int j) couttconstructing B2 b2=(b2=j)endl; B2() couttdestructing B2 b2=b2endl; int b2; ; clas

35、s B3/基类B3,构造函数无参数 public: B3() couttconstructing B3 *endl; B3()couttdestructing B3 *endl; ;,例8.11多继承下派生类析构函数,class C: public B2, public B1, public B3/派生新类C,注意基类名的个数与顺序 public:/派生类的公有成员 C(int a, int b, int c, int d,int e):B1(a),memberB2(d),memberB1(c),B2(b),cc(e) couttconstructing C cc=ccendl; C() co

36、uttdestructing C cc=ccendl; private:/派生类的私有对象成员,注意成员对象名的个数与顺序 int cc;,例8.11多继承下派生类析构函数,void main() C obj(1,2,3,4,5);/a=1,b=2,c=3,d=4,e=5 可以看见派生类构造函数首先执行,其执行次序与例8.10相同,我们再来分析一下析构函数的执行次序: (1)执行派生类的析构函数的函数体: 程序的执行为调用派生类C的析构函数。 (2)调用对象成员的析构函数 依据对象成员声明的反次序:memberB3()- memberB2() -,例8.11多继承下派生类析构函数,member

37、B1().即程序的执行次序为最先调用对象成员类B3的析构函数;其次对象成员类B2的析构函数;接着调用对象成员类B1的析构函数。 (3)调用基类析构函数 依据派生类声明的反次序:B3()- B1() - B2(),即程序的执行次序为最先调用基类B3的析构函数;其次调用基类B1的析构函数;最后调用基类B2的析构函数。,例8.11多继承下派生类析构函数,constructing B2 b2=2 constructing B1 b1=1 constructing B3 * constructing B1 b1=3 constructing B2 b2=4 constructing B3 * const

38、ructing C cc=5 destructing C cc=5 destructing B3 * destructing B2 b2=4 destructing B1 b1=3 destructing B3 * destructing B1 b1=1 destructing B2 b2=2,一般说来,在派生类中对基类成员的访问应该是唯一的,但是,由于多继承情况下,可能造成对基类中某成员的访问出现了不唯一的情况,则称为对基类成员访问的二义性问题。 1.派生类有一个共同的基类对成员进行访问时 当一个派生类从多个基类派生,而这些基类又有一个共同的基类,则对该基类中说明的成员进行访问时,可能会出现

39、二义性。,多继承-二义性问题,#include class A public : int a; ; class B1:public A private: int b1; ;,例8.12共同的基类对数据成员进行访问,class B2:public A private: int b2; ; class C:public B1,public B2 public : int f(); private:int c; ;,例8.12共同的基类对数据成员进行访问,void main () C c1; /c1.a; /存在二义性 /c1.A:a; c1.B1:a;/正确 c1.B2:a; coutttc1.B

40、1:a内存地址= ,例8.12共同的基类对数据成员进行访问,结果:c1.B1:a内存地址=0 x0012FF6C c1.B2:a内存地址=0 x0012FF74,我们来对例8.12进行一简单的分析: 在main函数中,下面的两个访问都有二义性: c1.a; c1.A:a; /存在二义性 而下面的两个访问是正确的: c1.B1:a;c1.B2:a; /正确,例8.12共同的基类对数据成员进行访问,那么,这是为什么呢?我们用图8-8来进行解释,就非常清晰的了解了。,例8.12共同的基类对数据成员进行访问,从图8-8(a) 派生类C的DAG图中可见,不同继承路径的基类子对象被分别成为一个对象。如,派

41、生类C的数据成员a有2个,并且同时来源于同一个基类A,所以当我们使用语句c1.a; c1.A:a;来访问数据成员a时,必然产生二义性;另外,从图8-8(b) 派生类C的数据成员内存分布中,可以一目了然就能分析存在2个数据成员a。 那么,我们有没有更好的办法来消除二义性呢?答案是肯定的,详见例8.14。,例8.12共同的基类对数据成员进行访问,2.派生类对成员进行访问时 当一个派生类从多个基类派生,作用域冲突的问题就可能会出现二义性。,多继承-二义性问题,#include class A public: void display() couttclass Aendl; int a; ; clas

42、s B public: void display()couttclass Bendl; int a; ;,例8.13作用域冲突问题的例子,class C:public A,public B public: void show()A:display(); B:display(); int b; ; void main () C cc; / cc.a;/存在二义性 / cc.display();/存在二义性 cc.show(); ,例8.13作用域冲突问题的例子,结果:class A class B,我们来对例8.13进行一简单的分析: 在main函数中,下面的访问有二义性: / cc.a;/存在

43、二义性 / cc.display();/存在二义性 而下面的两个访问是正确的: cc.show(); 那么,这又是为什么呢?其实与例8.12原因相同。(读者可以自行参考书中的图8-9来进行分析。),例8.13作用域冲突问题的例子,注意: 另外,由于二义性的原因,一个类不可以从同一个类中直接继承一次以上,例如: class B: public A, public A 这是不能允许的。,多继承-二义性问题,虚基类的引入和说明 2含有虚基类的派生类的构造函数和析构函数,虚基类,为了使公共基类在它的派生类中只有一个拷贝,可将基类定义为虚基类。 虚基类的其定义格式为: class 派生类名:virtua

44、l 继承方式 基类名; 或 class 派生类名:继承方式 virtual 基类名; 我们现在来对例8.12进行修改:,虚基类 -引入和说明,#include class A public : int a; ; class B1:virtual public A private: int b1; ;,例8.14带虚基类对数据成员进行访问的例子,class B2:virtual public A private: int b2; ; class C:public B1,public B2 public : int f(); private: int c; ;,例8.14带虚基类对数据成员进行访问

45、的例子,void main () C c1; c1.a; /不再存在二义性了 c1.A:a; c1.B1:a;/正确 c1.B2:a; coutttc1.B1:a内存地址= ,例8.14带虚基类对数据成员进行访问的例子,结果: c1.B1:a内存地址=0 x0012FF7C c1.B2:a内存地址=0 x0012FF7C,在main函数中,所有的访问都不再存在二义性了,那么,这又是为什么呢? 我们用图8-10来进行解释,就非常清晰了。,例8.14带虚基类对数据成员进行访问的例子,从图8-10(a) 派生类C的DAG图中可见,不同继承路径的虚基类子对象被合并成为一个对象。我们发现派生类C的数据成

46、员a,虽然来源于同一个基类A,但由于采用了虚基类派生,使得C的数据成员a是唯一的,这便是虚基类的作用,这样便消除了合并之前可能出现的二义性;另外,从图8-10(b) 派生类C的数据成员内存分布中,可以一目了然看见数据成员a是唯一。,例8.14带虚基类对数据成员进行访问的例子,创建派生类的对象时,虚基类构造函数的调用与一般非虚基类构造函数的调用有所不同,具体为: (1)如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始列表中应列出对虚基类构造函数的调用。如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类对象的成员。 (2)在一个成员初始化列表中若出现对

47、虚基类和非虚基类构造函数的调用,则虚基类的构造函数先于非虚基类的构造函数的执行。,虚基类 -有虚基类的派生类构造函数和析构函数,创建派生类的对象时,虚基类构造函数的调用与一般非虚基类构造函数的调用有所不同,具体为: (1)如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始列表中应列出对虚基类构造函数的调用。如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类对象的成员。 (2)在一个成员初始化列表中若出现对虚基类和非虚基类构造函数的调用,则虚基类的构造函数先于非虚基类的构造函数的执行。,虚基类 -有虚基类的派生类构造函数和析构函数,(3)由于派生类的对

48、象中只有一个虚基类子对象。为保证虚基类子对象只被初始化一次,这个虚基类构造函数必须只被调用一次。 由最后派生类的构造函数通过调用虚基类的构造函数进行初始化的。 (4)若同一层次中包含多个虚基类,这些虚基类的构造函数按它们说明的次序调用; (5)若虚基类由非虚基类派生而来,则仍先调用基类构造函数,再调用派生类的构造函数。 析构函数的调用次序同8.3.2中的2。 下面举一例子说明具有虚基类的派生类的用法。,虚基类 -有虚基类的派生类构造函数和析构函数,#include class A public: A(const char *s) couttconstructing A sendl; A( ) couttdestructing A endl; ; class B:virtual public A public: B(const char *s1,const char *s2):A(s1) couttconstructing B s2endl; B( ) couttdestructing B endl; ;,不再调用虚基类构造函数,例8.15带虚基类构造函数和析构函数的例子,class C:virtual public A public: C(const char *s1,const char *s2):A(s1) coutt

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论