虚基类与虚函数[稻谷书苑]_第1页
虚基类与虚函数[稻谷书苑]_第2页
虚基类与虚函数[稻谷书苑]_第3页
虚基类与虚函数[稻谷书苑]_第4页
虚基类与虚函数[稻谷书苑]_第5页
已阅读5页,还剩45页未读 继续免费阅读

下载本文档

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

文档简介

1、 5.2.2 虚基类 1.虚基类的概念 在C+语言中,一个类不能被多次说明为一个派生 类的直接基类,但可以不止一次地成为间接基类。这 就导致了一些问题。为了方便 说明,先介绍多继承的 “类格”表示法。 派生类及其基类可用一有向无环图(DAG)表示,其 中的箭头表示“由派生而来”。类的DAG常称为一个 “类格”。复杂类格画出来通常更容易理解。例如: 1教学运用 例 5-19 class L public: int next; ; class A : public L ; class B : public L ; class C : public A, public B public : void

2、 f() next=0; ; C类自己数据成员 B对象数据成员 L对象数据成员 A对象数据成员 L对象数据成员 C的对象 L A L B C 这时,next有两个赋值语 句next=0; 具有二义性,它是 将A:next置为零,还是将 B:next置为零,或者将两者 都置为0,需要在函数f()中被 显式的说明. 2教学运用 如果希望间接基类L与其派生类的关系是如下图 C+语言提供了这种描述手段。它将L说明为A和B的虚基类。 L AB C 3教学运用 当在多条继承路径上有一个公共的基类,在这些 路径中的某几条路经汇合处,这个公共基类就会产生 多个实例。 如果只想保存这个基类的一个实例,可以将这个

3、 公共基类说明为虚拟基类或称虚基类。 它仅是简单地将关键字virtual加到基类的描述上, 例如改写上述例子为例5-20 4教学运用 例 5-20 class L public: int next; ; class A : virtual public L ; class B : virtual public L ; class C : public A, public B public : void f() next=0; ; 这时C类对象中只有 L的一个复制,因而函 数C:f()中的语句 next=0; 没有二义性。 对于类C而言,L类 是B类的虚基类,而是 类A的真基类;但对于 类B而言

4、,L类还是B 类的真基类。 例 5-21 或class A : public virtual L 或class A : public virtual 5教学运用 class L public: int next; ; class A : virtual public L ; class B : virtual public L ; class C : public B , public A public : void f() next=0; ; 此例中,对于类C而言, L类是A类的虚基类, 而是类B的真基类。 派生时,A,B的顺序变了 6教学运用 一个派生类的对象的地址可以直接赋给虚基类的指针

5、, 例如: C obj; L * ptr= 这时不需要强制类型转换,并且,一个虚基类的引用 可以引用一个派生类的对象,例如: C obj2; L 反之则不行,无论在强制类型转换中指定什么路径, 一个虚基类的指针或引用不能转换为派生类的指针或 引用。例如: C * P=(C*)(A*)ptr; 将产生编译错误。 7教学运用 2. 虚基类对象的初始化 虚基类的初始化与多继承的初始化在语法上是一样 的,但隐含的构造函数的调用次序有点差别。 虚基类构造函数的调用次序是这样规定的: 1. 虚基类的构造函数在非虚基类之前调用。 2. 若同一层次中包含多个虚基类,虚基类构造函数 按它们说明的次序调用。 3.

6、 若虚基类由非虚基类派生,则遵守先调用基类构 造函数,再调用派生类构造函数的规则。 8教学运用 例如 : class X : public Y, virtual public Z X one; 将产生如下调用次序: Z() Y() X() 这里Z是X的虚基类,故先调用Z的构造函数,再调 用Y的构造函数,最后才调用派生类X自己的构造函数。 例 5-22 9教学运用 # include iostream.h class base public: base()coutBaseendl; ; class base2 public: base2()coutBase2endl; ; class level

7、1 : public base2, virtual public base public: level1()coutlevel1endl; ; class level2 : public base2, virtual public base public: level2()coutlevel2endl; ; class toplevel : public level1, virtual public level2 public: toplevel()couttoplevelendl; base2 base base2 level1 level2 toplevel toplevel view;

8、void main() 当建立对象view时, 将产生如下调用次序: level2() level1() toplevel() 而level2()要求: base() base2() level2() level1()要求 base2() level1() toplevel()要求 toplevel() 所以,构造函数的调 用顺序为: base() base2() level2() base2() level1() toplevel() 10教学运用 例5-23 class base ; class base2 ; class level1 : public base2, virtual pu

9、blic base ; class level2 : public base2, virtual public base ; class toplevel : virtual public level1, public level2 ; toplevel view; base2 base base2 level1 level2 toplevel level1(): base() base2() level1() level2(): base2() level2() toplevel(): toplevel() 当建立对象view 时,将产生如下 调用次序: 此例中,对于toplevel的而言,

10、 base是level2的虚基类 11教学运用 例 5-24 class B ; class X : virtual public B ; class Y : virtual public B ; class Z : public B ; class AA : public X, public Y, public Z ; B B X Y AA Z 这里AA具有两个B类的子对象:Z的B和x与Y共享的虚拟的B。 12教学运用 class V public : int V; ; class A public : int a; class B : public A, virtual public V

11、; class C : public A, Virtual public V ; class D : public B, public C public : void f(); ; void D:f( ) v+; a+; 例 5-25 虚基类和非 虚基类的不同。 A VA BC D 在D中仅仅 一个v 错误,具有 二义性,在 D中有两个a 调用次序: B( ): V( ) A( ) B( ) C( ): A( ) C( ) D( ): D( ) 13教学运用 5.3 虚函数与多态性 对于普通成员函数的重载,可表达为下面的方式: 1) 在同一个类中重载 2) 在不同类中重载 3) 基类的成员函数

12、在派生类中重载 因此,重载函数的访问是在编译时区分的,有以下三种方 法: 14教学运用 1.根据参数的特征加以区分,例如: Show(int , char)与Show(char *, float) 不是同一函数,编译能区分。 2. 使用“:”加以区分,例如: Circle : Show有别于Point : Show 3. 根据类对象加以区分。 ACircle.Show() 调用Circle:Show() APoint.Show() 调用Point : Show() 这里ACircle和APoint分别是Circle和Point的对象。 15教学运用 例 5-26 #include class

13、A public: void fun()cout“In A”endl; ; class B:public A public: void fun()cout“In B”endl; ; class C:public B public: void fun()cout“In C”fun(); 派生类对象 B:fun A:fun C:fun 16教学运用 5.3.1 基类对象的指针指向派生类对象 指向基类和派生类的指针变量是相关的,假设B_class是基 类,D_class是从B_class公有派生出来的派生类,任何被说 明为指向B_class的指针也可以指向D_class。例如: 利用p,可以访问从基

14、类B_class继承的成员,但D_class自己 定义的成员,p不能访问。例如:例5-27 指向类型B_class的对象的指针 类型B_class的对象 类型D_class的对象 p指向类型D_class的对象,它是 B_class 的派生类 p指向类型B_class的对象 B_class *p; B_class B_ob; D_class D_ob; p= p= 派生类对象 派生类成员 基类成员 17教学运用 #include #include class B_class char name80; public : void put_name(char * s) strcpy(name, s

15、); void show_name( ) coutnameput_name(Thomas Edison); p= p-put_name(Albert Einstein); B_ob.show_name( ); D_ob.show_name( ); dp= dp-put_phone(555555_1234); dp-show_phone( ); p-show_pone( ); (D_class*)p)-show_phone ( ); p-show_phone( ); 错误错误 该程序输出: Thomas Edison Albert Einstein 555555_1234 555555_1234

16、 class D_class: public B_class char phone_num80; public: void put_phone(char * num) strcpy(phone_num, num); void show_phone( ) coutphone_numshow_phone( ); 19教学运用 5.3.2 虚函数 例 5-28 #include class Base protected: int x; public: Base(int a) x=a; void who() cout“base ”x“n”; ; class Second_d: public Base

17、public: Second_d (int a):Base(a) void who() cout“Second derivation ”x“n”; ; class First_d: public Base public: First_d (int a):Base(a) void who() cout“First derivation ”xwho(); p= p-who(); p= p-who(); first_obj.who(); second_obj.who(); 该程序输出: base 1 base 2 base 3 First derivation 2 Second derivation

18、 3 second_obj x 3 Base:who() Second_d:who() first_obj x 2 Base:who() first_d:who() base_obj x 1 Base:who() p p-who() 指向基类的指针p,不管是指向基类的对象base_obj还 是指向派生的对象first_obj和second_obj, p-who()调用 的都是基类定义的 who()的版本.必须显式地用 first_obj.who(); 和 second_obj.who(); 才能调用类first_d和类second_d中定义的who()的版本。 其本质的原因在于普通成员函数的调

19、用是在编译时静态 区分。 21教学运用 如果随着p所指向的对象的不同pwho()能调用不同 类中who()版本,这样就可以用一个界面p-who()访问多 个实现版本:Base中的who(), First_d 中的 who(),以及 Second_d中的who(),这在编程时非常有用。 实际上,这表达了一种动态的性质,函数调用p-who() 依赖于运行时p所指向的对象。虚函数提供的就是这种解 释机制。如果在base中将成员函数who()说明为虚函数, 则修改上述程序为例5-29 虚函数是在基类中被冠以virtual的成员函数,它提供了 一种接口界面。虚函数可以在一个或多个派生类中被重 新定义,但

20、要求在派生类中 重新定义时,虚函数的函 数原型,包括返回类型,函数名,参数个数,参数类型 的顺序,必须完全相同。 22教学运用 class First_d : public Base public: First_d(int a ):Base(a) void who() “First derivation “x“n”; ; #include class Base protected: int x; public: Base(int a) x=a; virtual void who() cout“base ”x“n”; ; class Second_d : public Base public:

21、Second_d(int a ):Base(a) void who()“Second derivation “xwho(); p= p-who(); p= p-who(); first_obj.who(); second_obj.who(); 程序输出: base 1 First derivation 2 Second derivation 3 First derivation 2 Second derivation 3 23教学运用 second_obj x 3 Base:who() Second_d:who() first_obj x 2 Base:who() first_d:who()

22、p base_obj x 1 virtual Base:who() 基类的虚函数who()定 义了一种接口,在派 生类中此接口定义了 不同的实现版本,由 于虚函数的解释机制, 实现了“单界面,多 实现版本”的思想。 这种在运行时刻将函 数界面与函数的不同 实现版本进行匹配的 过程,称为晚期匹配, 也称为运行时的多态 性。 p-who() 24教学运用 基类函数f具有虚特性的条件是: 1) 在基类中,将该函数说明为virtual函数。 2)定义基类的公有派生类。 3) 在基类的公有派生类中原型一致地重载该虚函 数。 4) 定义指向基类的指针变量,它指向基类的公有 派生类的对象。 例 5-30 2

23、5教学运用 void main( ) derived d; base * bp= bp-vf1( ); bp-vf2( ); bp-f( ); class base public: virtual void vf1( ); virtual void vf2( ); virtual void vf3( ); void f( ); ; class derived : public base public : void vf1( ); void vf2(int); char vf3( ); void f( ); ; 错误,仅返回类型不同 具有虚特性 一般函数重载,参数不同,虚特性丢失 一般的函数重载

24、,非虚函数的重载 26教学运用 例 5-31 #include class figure protected: double x, y; public: void set_dim(double i, double j=0) x=i; y=j; virtual void show_area() cout“No area computation defined”; cout“for this class.n”; ; class triangle : public figure public: void show_area() cout“Triangle with high”; coutx“and

25、base”y; cout“ has an area of”; coutx*0.5*y“n”; ; class square : public figure public: void show_area() cout“Square with dimension”; coutx“*” y; cout“has an area of”; coutx * y“n”; ; class circle : public figure public: void show_area() cout“Circle with radius” ; coutx; cout“has an aera of” ; coutset

26、_dim(10.0, 5.0); p-show_area(); p= p-set_dim(10.0, 5.0); p-show_area(); p= p-set_dim(9.0); p-show_area(); 程序输出: Triangle with high 10 and base 5 has an area of 25.0 Square with dimension 10*5 has an area of 50.0 Circle with radius 9 has an area of 254.34 X y Figure:show_area() triangle :show_area()

27、X y Figure:show_area() square :show_area() X y Figure:show_area() circle :show_area() p 28教学运用 2.可以使用成员名限定可以强制使用静态联编 例 5-32 #include class A public: virtual void fun() cout“In A”end1; ; class B:public A public: void fun() cout“In B”end1; ; class C:public B public: void fun() cout“In C”fun(); B Bref.

28、fun(); Bref.B:fun(); 调用B:fun() 不是C:fun() 使用成员名限定可以强制 使用静态联编 29教学运用 3.在成员函数中调用虚函数 在一个基类或派生类的成员函数中,可以直 接调用等级中的虚函数。此时,需要根据成员 函数中this指针和它所指向的对象来判断调 用 的是哪个函数。 例 5-33 30教学运用 #include class A public : virtual void fun1() cout“A1-2”endl; fun2(); virtual void fun2() cout“A2-3”endl; fun3(); virtual void fun3(

29、) cout“A3-4”endl; fun4(); virtual void fun4() cout“A4-5”endl; fun5(); virtual void fun5() cout“A end”endl; ; class B:public A public: void fun1()cout“B 1-2”; fun2(); void fun2()cout“B 2-3”; fun3(); void fun3()cout“B 3-4”; fun4(); void fun4()cout“B 4-5”; fun5(); void fun5()cout“B end” fun1(); Apointe

30、r2- fun1(); delete Apointer1; delete Apointer1; 程序输出 : A 1-2 A 2-3 A 3-4 A 4-5 A end B 1-2 B 2-3 B 3-4 B 4-5 B end fun2( )相当于fun2(const A *this ) this即为Apointer2,因此仍然调用 所指向对象中的函数 31教学运用 例 5-34 #include class A public : void fun1()cout“A1-2”endl; fun2(); virtual void fun2()cout“A2-3”endl; fun3(); vir

31、tual void fun3()cout“A3-4”endl;fun4(); virtual void fun4()cout“A4-5”endl;fun5(); virtual void fun5()cout“A end”endl; ; class B:public A public: void fun1()cout“B 1-2”endl; fun2(); void fun2()cout“B 2-3”endl; fun3(); void fun3()cout“B 3-4”endl; fun4(); void fun4()cout“B 4-5”endl; fun5(); void fun5()c

32、out“B end”fun1(); delete Apointer; 程序输出: A 1-2 B 2-3 B 3-4 B 4-5 B end fun1( )不是虚函数,故基类的指针变 量,指向派生类时只能访问基类 中定义的成员。 fun2,fun3,fun4,fun5是虚函数,故基 类的指针变量,指向派生类时访 问的是派生类中定义的成员。 32教学运用 例 5-35 #include class A public : virtual void fun1()cout“A1-2”endl; fun2(); virtual void fun2()cout“A2-3”endl; fun3(); vir

33、tual void fun3()cout“A3-4”endl; fun4(); virtual void fun4()cout“A4-5”endl; fun5(); virtual void fun5()cout“A end”endl; class B:public A public: void fun3()cout“B 3-4”endl; fun4(); void fun4()cout“B 4-5”fun1(); delete Apointer; 程序输出: A 1-2 A 2-3 B 3-4 B 4-5 A end 基类虽然将fun1,fun2定义为虚函数, 但在派生类中并没有原型一致的

34、重载它们,所以要调用基类中的 函数。 33教学运用 4 . 在构造函数和析构函数中 调用虚函数 在构造函数和析构函数中调用虚函数时,采用静 态联编。即它们所调用的虚函数是自己的类或者它的 基类中的虚函数,但不是任何在派生类中定义的虚函 数。 例 5-36 34教学运用 #include class A public: A()cout“A is Creating”endl; virtual void fun1()cout“A fun1”endl; virtual void fun2()cout“A fun2”endl; A()cout“A is Destroy”endl; ; class B:p

35、ublic A public: B()cout“B is Creating”endl; fun1(); void fun()fun1(); B()cout“B is destroy”end1; fun2(); ; class C:public B public: C() cout“C is Creating”endl; void fun1()coutC fun1”endl; virtual void fun2()coutC fun2”endl; C()cout“C is Destroy”endl; fun2(); ; void main( ) C Cobj; Cobj.fun(); 程序输出:

36、 A is Creating B is Creating A fun1 C is Creating C fun1 C is destroy C fun2 B is destroy A fun2 A is destroy 在构造函数和析构函数中调用虚函数时,是自己 的类或者它的基类中的虚函数,但不是任何在派生类 中定义的虚函数。 35教学运用 5 . 析构函数可以定义为虚函数 构造函数不能为虚函数,而析构函数可以定义 为虚函数。 若析构函数为虚函数,那么当使用delete释放 基类指针指向的派生类对象时,先调用派生类的析 构函数,再调用基类的析构函数。 36教学运用 class Deriver:

37、 public Base int d; public: Deriver(int num1,int num2):Base(num1) d=num2;cout“Deriver createn”; Deriver() cout“Deriver destoryn”; ; 例 5-37 #include class Base int b; public: Base(int num) b=num;cout“Base createn”; Base() cout“Base destroyn”; ; void main() Base * pb1,*pb2; pb1=new Base(1); pb2=new De

38、river(2,3); delete pb1; delete pb2; cout“*n”; Base Bobj(4); Deriver Dobj(5,6); 程序输出: Base create Base create Deriver create Base destroy Base destory * Base create Base create Deriver create Deriver destroy Base destroy Base destory 基类对象指向派生类对象时, 释放时不调用派生类的析构 函数 37教学运用 例 5-38 #include class Base int

39、 b; public: Base(int num) b=num;cout“Base createn”; virtual Base() cout“Base destroyn”; class Deriver:public Base int d; public: Deriver(int num1,int num2):Base(num1) d=num2;cout“Deriver createn”; Deriver() coutf( ); /调用 A2:f( ) pa4-f( ); /调用 A2:f( ) 继承路径如图: A1 A2 A3 A4 A1() A2() A3() A4() 创建A4的对象时,

40、构造函数的调用次序为: 40教学运用 由于A2是A1的派生类,A2中重新定义的函数f覆盖了类A 中定义的函数f,而且A4和A2都将A1说明为虚基类,因此, pa4-f( )调用的是 A2 : f( ) 虚基类也能应用这个规则。 有人可能会想,A1:f( )离A4更近,因为A1是A4的直接基类, 而A2不是。pa4-f()应该调用A1:f( ),而不是调用A2:f( )。情况并 非如此,由DAG图可见,根据继承路径pa4-f( )应有两种调用 选择: A2:f( ); 和 A1:f( ) 41教学运用 5.3.3 纯虚函数及抽象类 基类表示抽象的概念,如figure是一个基类表示有型的 东西,可

41、以派生出封闭图形和非封闭图形两类。 Shape体现了一个抽象的概念,在figure中定义一个求 面积的函数显然是无意义的,但可以将其声明为一个 虚函数,提供一个派生的公共界面,并由各派生类提 供求面积的各自版本。因此基类的有些虚函数没有定 义是很正常的,但是要求派生类必须重新定义这些虚 函数。 为此 C+引入了纯虚函数的概念。 42教学运用 纯虚函数是一个在基类中说明的虚函数它在基类 中没有定义,要求任何派生类必须定义自己的版本。 纯虚函数具有以下的形式: virtual type func_name(参数表)0; 在构造函数和析构函数中调用虚函数使用静态编联, 因此在这两个函数中不能调用纯虚

42、函数。 但其它函数可以调用纯虚函数。 43教学运用 如果一个类至少有一个纯虚函数,就称这个 类为抽象类。 抽象类可以定义一种接口,由派生类提供各 种实现。 抽象类只能用作其它类的基类. 可以用作声明抽象类的指针和引用。 不能创建对象。 不能用作参数 不能用作函数返回类型或显式转换的类型。 44教学运用 例 5-40 class point ; class shape point center; public: point where( ) return center; void move( point p) center=p; draw( ); virtual void rotate(int)=0; virtual void draw( )=0; ; shape x; shape * p; shape fun( ); void g(shape); x= shape(23); shape 错误,抽象类不能建 立对象 可以声明抽象类的指 针 错误,抽象类不能 作为返回类型 错误,抽象类 不能作为参数 类型 可以声明抽象类的引 用 不能用作显式类型 转换 45教学运用 如果派生类没有原型一致地重载该纯虚函数。 从基类继承来的纯虚函数,在派生类中仍是纯虚函数。 例如: cl

温馨提示

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

评论

0/150

提交评论