虚基类与虚函数PPT课件_第1页
虚基类与虚函数PPT课件_第2页
虚基类与虚函数PPT课件_第3页
虚基类与虚函数PPT课件_第4页
虚基类与虚函数PPT课件_第5页
已阅读5页,还剩46页未读, 继续免费阅读

下载本文档

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

文档简介

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

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

3、拟基类或称虚基类。 它仅是简单地将关键字virtual加到基类的描述上,例如改写上述例子为例5-202021/3/95 例 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而言,L类还是B类的真基类。

4、例 5-21或class A : public virtual L或class A : public virtual 2021/3/96 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的顺序变了2021/3/97 一个派生类的对象的地址可以直接赋给虚基类的指针,例如: C obj; L

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

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

7、 class level1 : 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;base2basebase2level1level2topleveltoplevel v

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

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

10、是level2的虚基类2021/3/912 例 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 ;BBXYAAZ这里AA具有两个B类的子对象:Z的B和x与Y共享的虚拟的B。2021/3/913 class V public : int V; ; class A public : int a; class B : public A, virtual public V ; clas

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

12、是在编译时区分的,有以下三种方法:2021/3/915 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的对象。2021/3/916例 5-26 #include class A public: void

13、 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:funA:funC:fun2021/3/9175.3.1 基类对象的指针指向派生类对象 指向基类和派生类的指针变量是相关的,假设B_class是基类,D_class是从B_class公有派生出来的派生类,任何被说明为指向B_class的指针也可以指向D_class。例如: 利用p,可以访问从基类B_class继承的成员,但D_

14、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=&B_ob; p=&D_ob;派生类对象派生类成员基类成员2021/3/918#include #include class B_classchar name80; public : void put_name(char * s) strcpy(name, s); voi

15、d show_name( ) coutnameput_name(Thomas Edison);p=&D_ob;p-put_name(Albert Einstein);B_ob.show_name( );D_ob.show_name( );dp=&D_ob;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 55555

16、5_1234class D_class: public B_class char phone_num80; public: void put_phone(char * num) strcpy(phone_num, num); void show_phone( ) coutphone_numshow_phone( );2021/3/920 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: publi

17、c Base 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=&first_obj; p-who(); p=&second_obj; p-who(); first_obj.who(); second_obj.who(); 该程序输出:base 1base 2base 3First d

18、erivation 2Second derivation 3 second_obj x3Base:who()Second_d:who()first_obj x2Base:who()first_d:who()base_obj x1Base:who()pp-who() 指向基类的指针p,不管是指向基类的对象base_obj还是指向派生的对象first_obj和second_obj, p-who()调用的都是基类定义的 who()的版本.必须显式地用 first_obj.who(); 和 second_obj.who(); 才能调用类first_d和类second_d中定义的who()的版本。其本质

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

20、被重新定义,但要求在派生类中 重新定义时,虚函数的函数原型,包括返回类型,函数名,参数个数,参数类型的顺序,必须完全相同。2021/3/923class 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 pub

21、lic: Second_d(int a ):Base(a) void who()“Second derivation “xwho(); p=&first_obj; p-who(); p=&second_obj; p-who(); first_obj.who(); second_obj.who(); 程序输出: base 1 First derivation 2 Second derivation 3 First derivation 2 Second derivation 32021/3/924 second_obj x3Base:who()Second_d:who()firs

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

23、派生类的对象。 例 5-302021/3/926 void main( ) derived d; base * bp=&d; 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、函数重载,参数不同,虚特性丢失一般的函数重载,非虚函数的重载2021/3/927例 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“Triangl

25、e with high”; coutx“and base”y; cout“ has an area of”; coutx*0.5*y“n”; ;class square : public figurepublic: 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

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

27、ow_area()triangle :show_area() X y Figure:show_area()square :show_area() X y Figure:show_area()circle :show_area()p2021/3/9292.可以使用成员名限定可以强制使用静态联编 例 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 fu

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

29、ut“A2-3”endl; fun3();virtual 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”; fun2();void fun2()cout“B 2-3”; fun3();void fun3()cout“B 3-4”; fun4(); void fun4()cout“B 4-5”; fun5();void f

30、un5()cout“B end” fun1(); Apointer2- 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 endfun2( )相当于fun2(const A *this ) this即为Apointer2,因此仍然调用所指向对象中的函数2021/3/932例 5-34 #include class A public : void fun1()cout“A1-2”endl; fun2(); virtual void fun2

31、()cout“A2-3”endl; fun3(); virtual 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

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

33、3”endl; fun3(); virtual 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、数,但在派生类中并没有原型一致的重载它们,所以要调用基类中的函数。2021/3/934 4 . 在构造函数和析构函数中调用虚函数 在构造函数和析构函数中调用虚函数时,采用静态联编。即它们所调用的虚函数是自己的类或者它的基类中的虚函数,但不是任何在派生类中定义的虚函数。 例 5-362021/3/935#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”end

35、l; ;class B:public 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.

36、fun(); 程序输出:A is CreatingB is CreatingA fun1C is CreatingC fun1C is destroyC fun2 B is destroyA fun2A is destroy 在构造函数和析构函数中调用虚函数时,是自己的类或者它的基类中的虚函数,但不是任何在派生类中定义的虚函数。2021/3/936 5 . 析构函数可以定义为虚函数 构造函数不能为虚函数,而析构函数可以定义为虚函数。 若析构函数为虚函数,那么当使用delete释放基类指针指向的派生类对象时,先调用派生类的析构函数,再调用基类的析构函数。2021/3/937class Deriv

37、er: 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 Deri

38、ver(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 基类对象指向派生类对象时,释放时不调用派生类的析构函数2021/3/938 例 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( ) 继承路径如图:A1A2A3A4 A1() A2() A3() A4()创建A4的对象时,构造函数的调用

40、次序为:2021/3/941 由于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( ) 2021/3/9425.3.3 纯虚函数及抽象类 基类表示抽象的概念,如figure是一个基类表示有型的东西,可以派生出封闭图

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

42、用纯虚函数。2021/3/944 如果一个类至少有一个纯虚函数,就称这个类为抽象类。 抽象类可以定义一种接口,由派生类提供各种实现。 抽象类只能用作其它类的基类. 可以用作声明抽象类的指针和引用。 不能创建对象。 不能用作参数 不能用作函数返回类型或显式转换的类型。2021/3/945例 5-40class 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 & h(shape &); 错误,抽象类不能建立对象可以声明抽象类的指针错误,抽象类不能作为返回类型错误,抽象类不能作为参数类型可以声明抽象类的引用不能用作显式类型转换2021/3/946 如果派生类没有原型一致地重载该纯虚函数。 从基类继承来的纯虚函数,在派生类中仍是纯虚函数。 例如: class ab_circl

温馨提示

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

评论

0/150

提交评论