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

下载本文档

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

文档简介

2020/5/18,.,1,5.2.2虚基类,1.虚基类的概念在C+语言中,一个类不能被多次说明为一个派生类的直接基类,但可以不止一次地成为间接基类。这就导致了一些问题。为了方便说明,先介绍多继承的“类格”表示法。派生类及其基类可用一有向无环图(DAG)表示,其中的箭头表示“由派生而来”。类的DAG常称为一个“类格”。复杂类格画出来通常更容易理解。例如:,例5-19classLpublic:intnext;classA:publicL;classB:publicL;classC:publicA,publicBpublic:voidf()next=0;,这时,next有两个赋值语句next=0;具有二义性,它是将A:next置为零,还是将B:next置为零,或者将两者都置为0,需要在函数f()中被显式的说明.,2020/5/18,.,3,如果希望间接基类L与其派生类的关系是如下图,C+语言提供了这种描述手段。它将L说明为A和B的虚基类。,2020/5/18,.,4,当在多条继承路径上有一个公共的基类,在这些路径中的某几条路经汇合处,这个公共基类就会产生多个实例。如果只想保存这个基类的一个实例,可以将这个公共基类说明为虚拟基类或称虚基类。它仅是简单地将关键字virtual加到基类的描述上,例如改写上述例子为例5-20,例5-20classLpublic:intnext;classA:virtualpublicL;classB:virtualpublicL;classC:publicA,publicBpublic:voidf()next=0;,这时C类对象中只有L的一个复制,因而函数C:f()中的语句next=0;没有二义性。对于类C而言,L类是B类的虚基类,而是类A的真基类;但对于类B而言,L类还是B类的真基类。例5-21,或classA:publicvirtualL,或classA:publicvirtual,classLpublic:intnext;classA:virtualpublicL;classB:virtualpublicL;classC:publicB,publicApublic:voidf()next=0;,此例中,对于类C而言,L类是A类的虚基类,而是类B的真基类。,派生时,A,B的顺序变了,2020/5/18,.,7,一个派生类的对象的地址可以直接赋给虚基类的指针,例如:Cobj;L*ptr=将产生编译错误。,2020/5/18,.,8,2.虚基类对象的初始化虚基类的初始化与多继承的初始化在语法上是一样的,但隐含的构造函数的调用次序有点差别。虚基类构造函数的调用次序是这样规定的:1.虚基类的构造函数在非虚基类之前调用。2.若同一层次中包含多个虚基类,虚基类构造函数按它们说明的次序调用。3.若虚基类由非虚基类派生,则遵守先调用基类构造函数,再调用派生类构造函数的规则。,2020/5/18,.,9,例如:classX:publicY,virtualpublicZXone;将产生如下调用次序:Z()Y()X()这里Z是X的虚基类,故先调用Z的构造函数,再调用Y的构造函数,最后才调用派生类X自己的构造函数。例5-22,#includeiostream.hclassbasepublic:base()coutBaseendl;classbase2public:base2()coutBase2endl;classlevel1:publicbase2,virtualpublicbasepublic:level1()coutlevel1endl;classlevel2:publicbase2,virtualpublicbasepublic:level2()coutlevel2endl;classtoplevel:publiclevel1,virtualpubliclevel2public:toplevel()couttoplevelendl;,toplevelview;voidmain(),当建立对象view时,将产生如下调用次序:level2()level1()toplevel()而level2()要求:base()base2()level2()level1()要求base2()level1()toplevel()要求toplevel(),所以,构造函数的调用顺序为:base()base2()level2()base2()level1()toplevel(),例5-23classbase;classbase2;classlevel1:publicbase2,virtualpublicbase;classlevel2:publicbase2,virtualpublicbase;classtoplevel:virtualpubliclevel1,publiclevel2;toplevelview;,level1():base()base2()level1()level2():base2()level2()toplevel():toplevel(),当建立对象view时,将产生如下调用次序:,此例中,对于toplevel的而言,base是level2的虚基类,2020/5/18,.,12,例5-24classB;classX:virtualpublicB;classY:virtualpublicB;classZ:publicB;classAA:publicX,publicY,publicZ;,这里AA具有两个B类的子对象:Z的B和x与Y共享的虚拟的B。,classVpublic:intV;classApublic:inta;classB:publicA,virtualpublicV;classC:publicA,VirtualpublicV;classD:publicB,publicCpublic:voidf();,voidD:f()v+;a+;,例5-25虚基类和非虚基类的不同。,在D中仅仅一个v,错误,具有二义性,在D中有两个a,调用次序:B():V()A()B()C():A()C()D():D(),2020/5/18,.,14,5.3虚函数与多态性,对于普通成员函数的重载,可表达为下面的方式:1)在同一个类中重载2)在不同类中重载3)基类的成员函数在派生类中重载因此,重载函数的访问是在编译时区分的,有以下三种方法:,2020/5/18,.,15,1.根据参数的特征加以区分,例如:Show(int,char)与Show(char*,float)不是同一函数,编译能区分。2.使用“:”加以区分,例如:Circle:Show有别于Point:Show3.根据类对象加以区分。ACircle.Show()调用Circle:Show()APoint.Show()调用Point:Show()这里ACircle和APoint分别是Circle和Point的对象。,例5-26#includeclassApublic:voidfun()cout“InA”endl;classB:publicApublic:voidfun()cout“InB”endl;classC:publicBpublic:voidfun()cout“InC”endl;,voidmain()CCobj;Cobj.fun();Cobj.B:fun();Cobj.A:fun();A,5.3.1基类对象的指针指向派生类对象,指向基类和派生类的指针变量是相关的,假设B_class是基类,D_class是从B_class公有派生出来的派生类,任何被说明为指向B_class的指针也可以指向D_class。例如:,利用p,可以访问从基类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_classB_ob;D_classD_ob;p=,#include#includeclassB_classcharname80;public:voidput_name(char*s)strcpy(name,s);voidshow_name()coutnamen;,main()B_class*p;B_classB_ob;D_class*dp;D_classD_ob;p=,错误,错误,该程序输出:ThomasEdisonAlbertEinstein555555_1234555555_1234,classD_class:publicB_classcharphone_num80;public:voidput_phone(char*num)strcpy(phone_num,num);voidshow_phone()coutphone_numshow_phone();,5.3.2虚函数,例5-28#includeclassBaseprotected:intx;public:Base(inta)x=a;voidwho()cout“base”x“n”;,classSecond_d:publicBasepublic:Second_d(inta):Base(a)voidwho()cout“Secondderivation”x“n”;,classFirst_d:publicBasepublic:First_d(inta):Base(a)voidwho()cout“Firstderivation”who()调用的都是基类定义的who()的版本.必须显式地用first_obj.who();和second_obj.who();才能调用类first_d和类second_d中定义的who()的版本。其本质的原因在于普通成员函数的调用是在编译时静态区分。,2020/5/18,.,22,如果随着p所指向的对象的不同pwho()能调用不同类中who()版本,这样就可以用一个界面p-who()访问多个实现版本:Base中的who(),First_d中的who(),以及Second_d中的who(),这在编程时非常有用。实际上,这表达了一种动态的性质,函数调用p-who()依赖于运行时p所指向的对象。虚函数提供的就是这种解释机制。如果在base中将成员函数who()说明为虚函数,则修改上述程序为例5-29,虚函数是在基类中被冠以virtual的成员函数,它提供了一种接口界面。虚函数可以在一个或多个派生类中被重新定义,但要求在派生类中重新定义时,虚函数的函数原型,包括返回类型,函数名,参数个数,参数类型的顺序,必须完全相同。,classFirst_d:publicBasepublic:First_d(inta):Base(a)voidwho()“Firstderivation“x“n”;,#includeclassBaseprotected:intx;public:Base(inta)x=a;virtualvoidwho()cout“base”x“n”;,classSecond_d:publicBasepublic:Second_d(inta):Base(a)voidwho()“Secondderivation“xwho(),基类函数f具有虚特性的条件是:1)在基类中,将该函数说明为virtual函数。2)定义基类的公有派生类。3)在基类的公有派生类中原型一致地重载该虚函数。4)定义指向基类的指针变量,它指向基类的公有派生类的对象。例5-30,voidmain()derivedd;base*bp=,classbasepublic:virtualvoidvf1();virtualvoidvf2();virtualvoidvf3();voidf();classderived:publicbasepublic:voidvf1();voidvf2(int);charvf3();voidf();;,错误,仅返回类型不同,具有虚特性,一般函数重载,参数不同,虚特性丢失,一般的函数重载,非虚函数的重载,例5-31#includeclassfigureprotected:doublex,y;public:voidset_dim(doublei,doublej=0)x=i;y=j;virtualvoidshow_area()cout“Noareacomputationdefined”;cout“forthisclass.n”;,classtriangle:publicfigurepublic:voidshow_area()cout“Trianglewithhigh”;coutx“andbase”y;cout“hasanareaof”;coutx*0.5*y“n”;,classsquare:publicfigurepublic:voidshow_area()cout“Squarewithdimension”;coutx“*”y;cout“hasanareaof”;coutx*y“n”;;,classcircle:publicfigurepublic:voidshow_area()cout“Circlewithradius”;coutx;cout“hasanaeraof”;coutshow_area();,程序输出:Trianglewithhigh10andbase5hasanareaof25.0Squarewithdimension10*5hasanareaof50.0Circlewithradius9hasanareaof254.34,2.可以使用成员名限定可以强制使用静态联编,例5-32#includeclassApublic:virtualvoidfun()cout“InA”end1;,classB:publicApublic:voidfun()cout“InB”end1;,classC:publicBpublic:voidfun()cout“InC”end1;,voidmain()CCobj;Cobj.fun();Cobj.B:fun();Cobj.A:fun();A*Aref=,调用B:fun()不是C:fun()使用成员名限定可以强制使用静态联编,2020/5/18,.,30,3.在成员函数中调用虚函数,在一个基类或派生类的成员函数中,可以直接调用等级中的虚函数。此时,需要根据成员函数中this指针和它所指向的对象来判断调用的是哪个函数。,例5-33,#includeclassApublic:virtualvoidfun1()cout“A1-2”endl;fun2();virtualvoidfun2()cout“A2-3”endl;fun3();virtualvoidfun3()cout“A3-4”endl;fun4();virtualvoidfun4()cout“A4-5”endl;fun5();virtualvoidfun5()cout“Aend”endl;,classB:publicApublic:voidfun1()cout“B1-2”;fun2();voidfun2()cout“B2-3”;fun3();voidfun3()cout“B3-4”;fun4();voidfun4()cout“B4-5”;fun5();voidfun5()cout“Bend”fun1();deleteApointer1;deleteApointer1;,程序输出:A1-2A2-3A3-4A4-5AendB1-2B2-3B3-4B4-5Bend,fun2()相当于fun2(constA*this)this即为Apointer2,因此仍然调用所指向对象中的函数,例5-34#includeclassApublic:voidfun1()cout“A1-2”endl;fun2();virtualvoidfun2()cout“A2-3”endl;fun3();virtualvoidfun3()cout“A3-4”endl;fun4();virtualvoidfun4()cout“A4-5”endl;fun5();virtualvoidfun5()cout“Aend”endl;,classB:publicApublic:voidfun1()cout“B1-2”endl;fun2();voidfun2()cout“B2-3”endl;fun3();voidfun3()cout“B3-4”endl;fun4();voidfun4()cout“B4-5”endl;fun5();voidfun5()cout“Bend”fun1();deleteApointer;,程序输出:A1-2B2-3B3-4B4-5Bend,fun1()不是虚函数,故基类的指针变量,指向派生类时只能访问基类中定义的成员。,fun2,fun3,fun4,fun5是虚函数,故基类的指针变量,指向派生类时访问的是派生类中定义的成员。,例5-35#includeclassApublic:virtualvoidfun1()cout“A1-2”endl;fun2();virtualvoidfun2()cout“A2-3”endl;fun3();virtualvoidfun3()cout“A3-4”endl;fun4();virtualvoidfun4()cout“A4-5”endl;fun5();virtualvoidfun5()cout“Aend”endl;,classB:publicApublic:voidfun3()cout“B3-4”endl;fun4();voidfun4()cout“B4-5”fun1();deleteApointer;,程序输出:A1-2A2-3B3-4B4-5Aend,基类虽然将fun1,fun2定义为虚函数,但在派生类中并没有原型一致的重载它们,所以要调用基类中的函数。,2020/5/18,.,34,4.在构造函数和析构函数中调用虚函数,在构造函数和析构函数中调用虚函数时,采用静态联编。即它们所调用的虚函数是自己的类或者它的基类中的虚函数,但不是任何在派生类中定义的虚函数。例5-36,#includeclassApublic:A()cout“AisCreating”endl;virtualvoidfun1()cout“Afun1”endl;virtualvoidfun2()cout“Afun2”endl;A()cout“AisDestroy”endl;,classB:publicApublic:B()cout“BisCreating”endl;fun1();voidfun()fun1();B()cout“Bisdestroy”end1;fun2();,classC:publicBpublic:C()cout“CisCreating”endl;voidfun1()coutCfun1”endl;virtualvoidfun2()coutCfun2”endl;C()cout“CisDestroy”endl;fun2();,voidmain()CCobj;Cobj.fun();,程序输出:AisCreatingBisCreatingAfun1CisCreatingCfun1CisdestroyCfun2BisdestroyAfun2Aisdestroy,在构造函数和析构函数中调用虚函数时,是自己的类或者它的基类中的虚函数,但不是任何在派生类中定义的虚函数。,2020/5/18,.,36,5.析构函数可以定义为虚函数,构造函数不能为虚函数,而析构函数可以定义为虚函数。若析构函数为虚函数,那么当使用delete释放基类指针指向的派生类对象时,先调用派生类的析构函数,再调用基类的析构函数。,classDeriver:publicBaseintd;public:Deriver(intnum1,intnum2):Base(num1)d=num2;cout“Derivercreaten”;Deriver()cout“Deriverdestoryn”;,例5-37#includeclassBaseintb;public:Base(intnum)b=num;cout“Basecreaten”;Base()cout“Basedestroyn”;,voidmain()Base*pb1,*pb2;pb1=newBase(1);pb2=newDeriver(2,3);deletepb1;deletepb2;cout“*n”;BaseBobj(4);DeriverDobj(5,6);,程序输出:BasecreateBasecreateDerivercreateBasedestroyBasedestory*BasecreateBasecreateDerivercreateDeriverdestroyBasedestroyBasedestory,基类对象指向派生类对象时,释放时不调用派生类的析构函数,例5-38#includeclassBaseintb;public:Base(intnum)b=num;cout“Basecreaten”;virtualBase()cout“Basedestroyn”;classDeriver:publicBaseintd;public:Deriver(intnum1,intnum2):Base(num1)d=num2;cout“Derivercreaten”;Deriver()coutf();/调用A2:f()继承路径如图:,A1():A1()A3():A2()A3()A4():A4(),创建A4的对象时,构造函数的调用次序为:,由于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(),2020/5/18,.,42,5.3.3纯虚函数及抽象类,基类表示抽象的概念,如figure是一个基类表示有型的东西,可以派生出封闭图形和非封闭图形两类。Shape体现了一个抽象的概念,在figure中定义一个求面积的函数显然是无意义的,但可以将其声明为一个虚函数,提供一个派生的公共界面,并由各派生类提供求面积的各自版本。因此基类的有些虚函数没有定义是很正常的,但是要求派生类必须重新定义这些虚函数。为此C+引入了纯虚函数的概念。,2020/5/18,.,43,纯虚函数是一个在基类中说明的虚函数它在基类中没有定义,要求任何派生类必须定义自己的版本。纯虚函数具有以下的形式:virtualtypefunc_name(参数表)0;在构造函数和析构函数中调用虚函数使用静态编联,因此在这两个函数中不能调用纯虚函数。但其它函数可以调用纯虚函数。,2020/5/18,.,44,如果一个类至少有一个纯虚函数,就称这个类为抽象类。抽象类可以定义一种接口,由派生类提供各种实现。抽象类只能用作其它类的基类.可以用作声明抽象类的指针和引用。不能创建对象。不能用作参数不能用作函数返回类型或显式转换的类型。,例5-40classpoint;classshapepointcenter;public:pointwhere()returncenter;voidmove(pointp)center=p;draw();virtualvoidrotate(int)=0;virtualvoiddraw()=0;,shapex;shape*p;shapefun();voidg(shape);x=shape(23);shape,错误,抽象类不能建立对象,可以声明抽象类的指针,错误,抽象类不能作为返回类型,错误,抽象类不能作为参

温馨提示

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

评论

0/150

提交评论