下载

3下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 C++课件--薛景瑄chapter_5

C++课件--薛景瑄chapter_5.doc

C++课件--薛景瑄chapter_5

jingernanhang
2018-09-05 0人阅读 举报 0 0 0 暂无简介

简介:本文档为《C++课件--薛景瑄chapter_5doc》,可适用于IT/计算机领域

第五章:讲解§“多继承中基类指针的继承”不打星号(但内容须重新整理)第五章多态性和虚函数多态性(polymorphism)的定义(一)定义:polymorphismthequalityorstateofbeingabletoassumedifferentforms。(摘自Webster词典)多态性能够呈现不同形态的特性和状态。(二)两种多态性.编译时的多态性:亦称静态联编(束定)(staticbinding)或早期联编(earlybinding)。即本章中前半部分将要讲述的函数和运算符函数的重载。编译系统进行编译时在函数调用指令表中找到多个重载的函数中相对应的一个函数(即形参的数量和类型完全匹配的那个函数)将它们与主程序中调用它们的代码联编(binding)以备主程序运行时正确地调用。这在C++中这表现为“行为共享”(即同名函数实现不同功能)或称“同一接口多种方法”。.所谓运行时的多态性:亦称所谓动态联编(束定)(dynamicbinding)或滞后联编(latebinding)。编译系统进行编译时根据程序代码内容、按照当时动态地确定的this指针、找到相应的虚函数将它与主程序中调用它的代码联编(binding)以供主程序在运行中调用它(本章后半部分将详细讲解)。函数重载(overloading)普通函数的重载C语言中一个函数只能处理一个类型的数据。例如第三章§中encapsulcpp中两个函数sqint(int)和sqdbl(double)都是如此其中sqint(int)只能处理整型数据而sqdbl(double)只能处理double浮点型数据。能否使用一个函数来处理多个类型的数据?这可以在面向对象程序设计语言中解决。在C++中可用一个函数名sq(…)来处理所有参数类型的平方例子如下。例平方函数的重载:单个形参的重载函数overldfuncppoverloadingoffunctions#include<iostreamh>doublesq(doubley){returny*y}intsq(inty){returny*y}voidmain(){inti=doubled=cout<<sq(i)<<endlcout<<sq(d)<<endl}*Results:*例求最大值函数的重载:两个形参的重载函数overldfuncppoverloadedfunctionforx=newderive("base","derive")deletepx}*Results:CONSB:baseCONSD:deriveDESB:base*这是一个特殊情况:主程序内被定义为指向基类的指针px用于指向堆区内新建立的派生类对象。此处两个构造函数都调用new因此要求两个析构函数也都调用delete但结果只调用了classbase的析构函数不合格。为何如此?原因在于析构函数不是虚函数!编译系统在编译时因为指针px定义为指向基类的指针所以派生类对象只能通过px访问基类的析构函数而访问不到派生类的析构函数。必须将析构函数声明为虚函数才能在程序结束时自动地调用派生类的析构函数从而将派生类对象申请的空间退回给堆。例使用虚析构函数解决上述问题virdescppimprovedversionofvirdescppbyintroducingvirtualdestructor只需将例中classbase中析构函数改为:virtual~base(){…}其余都不变。其运行结果即为:*Results:CONSB:baseCONSD:deriveDESD:deriveDESB:base*正确!派生类成员函数的定向调用看一个功能更大的多态性(虚函数)用途:成员函数的定向调用。例计算家具的路程收费而不用虚函数derfuncppAmethodnotusingvirtualfunctionsrefertoderfuncpp,virfuncpp,purevircpplater#include<iostreamh>#defineFRATE单位距离基本费率classRate{doublex,y坐标public:Rate(doublei,doublej){x=iy=jrate=FRATE*(xy)}thelargerxandyare,iethefartheritis,thehighertheratewillbedoublerate单位家具面积费率}classSquare:publicRate{doublel正方形家具边长public:Square(doublei,doublej,doublek):Rate(i,j){l=k}doubleArea(){returnl*l}doubleFee(){returnrate*Area()}}classRectangle:publicRate{doublew,l矩形家具的长和宽public:Rectangle(doublei,doublej,doubleh,doublek):Rate(i,j){w=hl=k}doubleArea(){returnw*l}doubleFee(){returnrate*Area()}}voidmain(){Squaresq(,,)cout<<sqFee()<<endlRectanglerec(,,,)cout<<recFee()<<endl}*Results:*DAG两个派生类中的doubleFee(){returnrate*Area()}是相同的能不能省略将其合并为一各并放置于基类中?因为派生类可以继承基类的函数。试试看!如下:例试图省略函数Fee()与derfuncpp不同处用蓝笔显示!derfuncpptrytocombineFee()'sinderfuncppintoone,butinvainRefertovirfuncpp,purevircpplater#include<iostreamh>#defineFRATEfundamentalrateclassRate{doublex,ypublic:Rate(doublei,doublej){x=iy=jrate=FRATE*(xy)}doubleratedoubleArea(){return}doubleFee(){returnrate*Area()}}classSquare:publicRate{doublelpublic:Square(doublei,doublej,doublek):Rate(i,j){l=k}doubleArea(){returnl*l}doubleFee(){returnrate*Area()}}classRectangle:publicRate{doublew,lpublic:Rectangle(doublei,doublej,doubleh,doublek):Rate(i,j){w=hl=k}doubleArea(){returnw*l}doubleFee(){returnrate*Area()}}voidmain(){Squaresq(,,)cout<<sqFee()<<endlRectanglerec(,,,)cout<<recFee()<<endl}*Results:*此程序失败是由于静态联编:编译系统只准基类中的Fee()调用基类中的Area(),而不能调用派生类的Area()。而这也只能通过虚函数来解决!例使用虚函数计算家具的路程收费virfuncppvirtualfunctionisintroducedintoderfuncpp,sothatFee()canbeomittedinderivedclassesRefertopurevircpplater与derfuncpp不同处用红笔显示!#include<iostreamh>#defineFRATEfundamentalrateclassRate{doublex,ypublic:Rate(doublei,doublej){x=iy=jrate=FRATE*(xy)}doubleratevirtualdoubleArea(){return}doubleFee(){returnrate*Area()}}classSquare:publicRate{doublelpublic:Square(doublei,doublej,doublek):Rate(i,j){l=k}doubleArea(){returnl*l}}classRectangle:publicRate{doublew,lpublic:Rectangle(doublei,doublej,doubleh,doublek):Rate(i,j){w=hl=k}doubleArea(){returnw*l}}voidmain(){Squaresq(,,)cout<<sqFee()<<endlRectanglerec(,,,)cout<<recFee()<<endl}*Results:*联编(binding)的机制(mechanism)静态联编的机制所有成员函数(包括构造和析构函数)的函数体都位于代码区内。所有成员函数的调用指令(即入口)都位于代码区的“函数调用指令表”内。例如:jmpostream::operator<<()Ajmpostream::operator<<()Fjmpendl(e)jmpderive::derive()jmpbase::base(e)Ejmpmain()jmpflush()jmpderive::fun()编译系统进行编译时在“函数调用指令表”内找到相应的函数调用指令将它与主函数代码联编以备运行时调用。所谓动态联编的机制原理这归功于虚函数地址表(简称虚地址表)和虚地址表指针(简称虚指针)。在进行编译时编译系统对每个带虚函数的类执行以下步骤:所有成员函数(也包括所有虚函数)的函数体都位于代码区内。所有成员函数(也包括所有虚函数)的调用指令(即入口例如jmpbase::fun)都位于代码区的“函数调用指令表”内。例如以下所示函数调用指令表:jmpostream::operator<<()Ajmpostream::operator<<(a)Fjmpderive::derive()jmphex()jmpendl()Ejmpderive::fun()jmpios::unlock()jmpbase::act()Djmpbase::base()jmpderive::act(d)jmpios::setf()Cjmpostream::operator<<(d)jmpbase::fun(d)jmpmain(b)Bjmpios::lock()jmpflush(f)编译系统为每个类建立一个(只有一个)位于数据区内的该类的虚函数地址表(VirtualFunctionAddressTable或vtable简称虚地址表)表中的每个单元指向位于代码区“函数调用指令表”内的相应虚函数的调用指令。在建立该类的对象(对象一般位于栈区内)后在该对象内设置一个该对象的虚地址表指针(VirtualFunctionAddressTablepointer或vtablepointer或VPTR,简称虚指针)。该虚指针VPTR指向该类的虚地址表vtable的起始地址。然后根据程序代码的变化利用虚指针VPTR加上偏移量的形式将指针指向虚地址表vtable中相应类(或为基类或为派生类)的相应虚函数调用指令将该虚函数与调用程序(callingfunction)联编以供后者调用。所谓动态联编其实是在编译时由编译系统根据程序代码的变化而确定相应虚函数的调用指令从而实现相应虚函数的联编。这并非在程序运行中确定而是在编译时早就确定的。因此这不是真正意义上的动态联编其实质仍是静态联编。这里有指针的三层(三重)使用:第一层是使用类对象的指针(或引用)来指向类的对象(例如ptr指向classcl的对象obj)第二层是使用类对象中的虚指针(VPTR)来找到数据区中的虚地址表vtable第三层是使用该类虚地址表中的虚函数调用指令的地址来找到相应的虚函数调用指令(例如jmpcl::fun)然后即可调用虚函数(例如cl::fun())。这有些类似于一般程序中函数指针数组的使用有兴趣者可参阅附录十五的“函数指针数组的使用”。带虚函数的类的对象长度例没有和具有虚函数的各个对象的长度vptrszcppsimplifiedversionofEckel'sbook,pTocheckhowmanybytesavirtualpointeroccupieswithoutanyinheritance#include<iostreamh>classnovirtual{intipublic:voidx(){}intf(){returni}}classonevirtual{intipublic:virtualvoidx(){}intf(){returni}}classtwovirtual{intipublic:virtualvoidx(){}virtualintf(){returni}}voidmain(){cout<<"sizeof(int):"<<sizeof(int)<<endlcout<<"sizeof(void*):"<<sizeof(void*)<<endlvoid*isapointerusedtopointtoanykindofdatacout<<"sizeof(novirtual):"<<sizeof(novirtual)<<endlcout<<"sizeof(onevirtual):"<<sizeof(onevirtual)<<endlcout<<"sizeof(twovirtual):"<<sizeof(twovirtual)<<endl}*Results:sizeof(int):sizeof(void*):sizeof(novirtual):sizeof(onevirtual):sizeof(twovirtual):*可见:无虚函数的类的长度只是其所有非静态数据的长度即sizeof(novirtual)等于sizeof(int)。带虚函数的类的长度是其所有非静态数据的长度加上虚指针长度但与虚函数的数量无关即sizeof(onevirtual)或sizeof(twovirtual)等于sizeof(int)加上sizeof(void*)。这意味着无论虚函数的数量多少都只有一个虚指针。虚指针只指向一个虚地址表即this指针所指向的那个对象的虚地址表。单继承中的虚指针和虚地址表虚指针VPTR一般位于对象内存栈区空间靠前单元处在VC中虚指针VPTR位于内存栈区空间的第一个单元处。看以下例子中各种指针的使用:classbase{public:virtualvoidfun()virtualvoidact()}classcl:publicbase{public:voidfun()voidact()}……voidmain(){clobjcl*ptr……ptr>fun()ptr>act()……}vptrvtableppt内有虚指针等的动态变化。例同一类的各对象共享一个虚地址表VtabaddrcppToseethattheVtablesoftwoobjectsofthesameclasshavethesameaddress,ieonlyonevtable#include<iostreamh>classX{public:virtualvoidwho(){cout<<'X'<<endl}}classY:publicX{public:Y(inti){a=i}voidwho(){cout<<'Y'<<endl}inta}voidmain(){Yobj(),obj()int*ptrptr=(int*)objcout<<"第一个对象obj的两项内存存储内容:"<<endlcout<<hex<<*(ptr)<<"(Vtableindataarea)"<<endlVtableaddresscout<<dec<<*ptr<<endlcout<<"其数据为:"cout<<obja<<endlptr=(int*)objreassignpointercout<<"第二个对象obj的两项内存存储内容:"<<endlcout<<hex<<*(ptr)<<"(Vtableindataarea)"<<endlsameVtableaddresscout<<dec<<*ptr<<endlcout<<"其数据为:"cout<<obja<<endl}*Results:第一个对象obj的两项内存存储内容:(指向Y::vtableindataarea)其数据为:第二个对象obj的两项内存存储内容:(指向Y::vtableindataarea)其数据为:*可以看出classY的两个对象obj和obj共享一个vtable而该虚地址表的地址x是在数据区内。但classY及其基类classX的vtable是不同的它们分处不同空间。有兴趣者还可参阅附录十六和附录十七。例上一节§例的vptrszcpp:程序运行结果为:*Results:sizeof(int):sizeof(void*):sizeof(novirtual):sizeof(onevirtual):sizeof(twovirtual):*对象的三区内存存储内容栈区数据区novirtual类的对象objonevirtual类的对象onevirtual::vtableobj代码区twovirtual类的对象twovirtual::vtableobj例简单的单继承:没有数据而只有虚函数vptrszcppwithreferencetoEckel'sbook,pTocheckhowmanybytesanobjectofthederivedclassoccupiesinsingleinheritance#include<iostreamh>classbase{public:virtualvoidx(){}}classderive:publicbase{public:voidx(){}}classgrand:publicderive{public:voidx(){}}voidmain(){cout<<"sizeof(void*):"<<sizeof(void*)<<endlcout<<"sizeof(base):"<<sizeof(base)<<endlcout<<"sizeof(derive):"<<sizeof(derive)<<endlcout<<"sizeof(grand):"<<sizeof(grand)<<endl}*Results:sizeof(void*):sizeof(base):sizeof(derive):sizeof(grand):*DAGpublicpublic各对象的双区存储内容栈区数据区base的对象objbase::vtablederive的对象objderive::vtablegrand的对象objgrand::vtable这三个虚函数地址表vtable都在数据区内例如以下:数据区base::vtablebase::x()调用指令的地址(base::x入口)derive::vtablederive::x()调用指令的地址(derive::x入口)grand::vtablegrand::x()调用指令的地址(grand::x入口)这三个表不一定连在一起。例没有数据而有两个虚函数objcontcppToshowthesizesoftheclasseswhentherearetwovirtualfunctionsbutnodataVPTRisintheprecedingelement#include"iostreamh"classbase{public:virtualvoidinc(){}virtualvoidfun(){}}classderive:publicbase{public:voidinc(){}voidfun(){}}classgrand:publicderive{public:voidinc(){}voidfun(){}}voidmain(){cout<<"sizeofclassbaseis"<<sizeof(base)<<endlcout<<"sizeofclassderiveis"<<sizeof(derive)<<endlcout<<"sizeofclassgrandis"<<sizeof(grand)<<endlgrandgrderiveddbasebscout<<"Theaddressesoftheirobjectsare:"<<endlcout<<bs<<endlcout<<dd<<endlcout<<gr<<endl}*Results:sizeofclassbaseis(内容为VPTR)sizeofclassderiveis(内容为VPTR)sizeofclassgrandis(内容为VPTR)Theaddressesoftheirobjectsare:(都在栈区内)xFDECxFDFxFDF*DAGpublicpublic各对象的双区存储内容栈区数据区请注意:基类和派生类中如有两个以上虚函数则各类的虚地址表中各虚函数调用指令的地址的排列顺序必须完全相同(如上例中inc()一律在前而fun()一律在后)。而此顺序决定于基类中各虚函数的声明顺序。联编过程每个带虚函数的类中的虚地址表(vtable)包括该类中所有按照相同顺序排列的虚函数调用指令的地址(简称虚函数入口)。在该类中设置一个虚指针vpointer(缩写为VPTR)编译系统在建立对象时初始地将它指向vtable的起始地址。虚指针可以根据不同程序代码加上不同偏移量指向虚地址表中被联编的那个虚函数的调用指令的地址。例§中例的virfuncpp…………voidmain(){Squaresq(,,)cout<<sqFee()<<endlRectanglerec(,,,)cout<<recFee()<<endl}*Results:*执行主程序第二句sqFee()时:对象sq的三区存储内容square类的对象栈区数据区sqsquare::vtable上图中因为this指针指向square类的对象sq所以square::vtable中只包括square类的虚函数Area()的调用指令的地址。联编过程如下:main()要求调用sqFee()但classsquare中没有函数Fee()根据支配规则找到基类classRate中的函数Fee()。classRate中的函数Fee()准备调用虚函数Area()而所有类中都有此虚函数应该调用哪一个呢?这就通过this指针此指针指向sq对象而sqVPTR又指向square::vtable。这个square::vtable的内容是classsquare中所有虚函数(此例中只有square::Area())的调用指令的地址(根本不包括其它类的虚函数)。如果classsquare具有多个虚函数时如何能找到square::Area()的调用指令的地址呢?事实上编译系统早就在建立vtable时为虚函数Area()在各个类的vtable中确定了它的位置偏移量(所有类中都相同可以是或或)。编译系统根据此偏移量就可找到square::Area()的调用指令的地址再按此地址就不难找到虚函数square::Area()本身。编译系统即按此进行联编。编译系统在编译中就已完成此所谓动态联编的整个过程所以它实质上应是静态的联编过程。例virfuncppslightlymodifiedversionofWangYan'sbookpwithvirtualfunctionwho()#include<iostreamh>classbase{intipublic:virtualvoidwho(){cout<<"base"<<endl}}classfirst:publicbase{intipublic:voidwho(){cout<<"first"<<endl}}classsecond:publicbase{intipublic:voidwho(){cout<<"second"<<endl}}voidmain(){baseobj,*ptrfirstobjsecondobjptr=objptr>who()ptr=objptr>who()ptr=objptr>who()}*Results:basethefirstderivationthesecondderivation*DAG各对象的栈区存储内容base的对象first的对象second的对象objobjobj.主程序第一句、第二句和第三句建立对象obj和基类指针ptr、对象obj和对象obj:各对象的三区内存存储内容栈区数据区代码区base的对象objbase::vtablefirst的对象objfirst::vtablesecond的对象objsecond::vtable.主程序第四句ptr=obj将指针ptr指向base类的对象obj.主程序第五句ptr>who()调用指针ptr所指向的base::who().主程序第六句ptr=obj将指针ptr重新指向first类的对象obj.主程序第七句ptr>who()调用指针ptr所指向的first::who().主程序第八句ptr=obj将指针ptr重新指向second类的对象obj.主程序第九句ptr>who()调用指针ptr所指向的second::who()以上是类中具有一个虚函数的情况下面看类中具有两个虚函数的情况。例virfuncppToshowhowtwovirtualfunctionsareinvokedThesizesoftheobjectsareshowninvptrszcpp(见附录十八)#include<iostreamh>#definePIclasscontainer{protected:doubledimensionpublic:container(doubled){dimension=d}constructorvirtualdoublesurface(){return}virtualdoublevolume(){return}}classcube:publiccontainer{public:cube(doublelength):container(length){}virtualdoublesurface(){return(*dimension*dimension)}virtualdoublevolume(){return(dimension*dimension*dimension)}}classsphere:publiccontainer{public:sphere(doubleradius):container(radius){}virtualdoublesurface(){return(*PI*dimension*dimension)}virtualdoublevolume(){return(PI*dimension*dimension*dimension*)}}voidmain(){container*ptrcubeobj()ptr=objcout<<"cube'ssurfaceis"<<ptr>surface()<<endlcout<<"cube'svolumeis"<<ptr>volume()<<endlsphereobj()ptr=objcout<<"sphere'ssurfaceis"<<ptr>surface()<<endlcout<<"sphere'svolumeis"<<ptr>volume()<<endl}*Results:cube'ssurfaceiscube'svolumeissphere'ssurfaceissphere'svolumeis*各对象的三区存储内容如下:(请见virfunppt).主程序第一句建立基类指针ptr。.主程序第二句建立对象obj并且初始化。.主程序第三句将基类指针ptr指向对象obj。.主程序第四句调用函数ptr>surface()编译系统通过虚指针objVPTR找到虚地址表objvtable从而找到cube::surface()的调用指令地址进而通过该调用指令来调用函数cube::surface()。.主程序第五句调用函数ptr>volume()编译系统通过虚指针objVPTR找到虚地址表objvtable通过虚地址表objvtable表中的偏移地址找到cube::volume()的调用指令地址进而通过该调用指令来调用函数cube::volume()。.主程序第六句建立对象obj并且初始化。.主程序第七句将同一基类指针ptr指向对象obj。.主程序第八句调用函数ptr>surface()。类似地通过虚地址表objvtable找到sphere::surface()的调用指令地址进而调用函数sphere::surface()。.主程序第九句调用函数ptr>volume()。类似地通过虚地址表objvtable找到sphere::volume()的调用指令地址进而调用函数sphere::volume()。例进一步论证虚地址表vtable的组成objcontvtabcppToshowthesizesoftheclassesandthestoredcontentsincludingthecontentsofVPTRandVTABLEoftheobjectsofthederivedclasseswithvirtualfunctionswhentherearetwovirtualfunctionsWholecontentsofVTABLEsaredisplayed#include"iostreamh"classbase{public:virtualvoidact(){cout<<"actB"<<endl}virtualvoidfun(){cout<<"funB"<<endl}}classderive:base{public:virtualvoidfun(){cout<<"funD"<<endl}virtualvoidact(){cout<<"actD"<<endl}}voidmain(){deriveddbasebsbase*ptrbptrb=bsint**ptr=(int**)ptrb指向基类对象bscout<<"Addrofobjectbs:"<<ptr<<endlcout<<"Addrofbase::VTABLE:"<<*ptr<<endlcout<<"Indexingofvirtualfunctionbsact():"<<"x"<<hex<<**ptr<<endlcout<<"Indexingofvirtualfunctionbsfun():"<<"x"<<*(*ptr)<<endlptr指向派生类对象ddcout<<"Addrofobjectdd:"<<ptr<<endlcout<<"Addrofderive::VTABLE:"<<*ptr<<endlcout<<"Indexingofvirtualfunctionddact():"<<"x"<<hex<<**ptr<<endlcout<<"Indexingofvirtualfunctionddfun():"<<"x"<<*(*ptr)<<endl}*Results:Addrofobjectbs:xFDFAddrofbase::VTABLE:xIndexingofvirtualfunctionbsact():x(base::vtable第一单元)Indexingofvirtualfunctionbsfun():x(base::vtable第二单元)Addrofobjectdd:xFDFAddrofderive::VTABLE:xCIndexingofvirtualfunctionddact():x(derive::vtable第一单元)Indexingofvirtualfunctionddfun():xe(derive::vtable第二单元)*各对象的双区内存存储内容栈区数据区base::vtablederive::vtable以上两个vtable内的四条调用指令的地址可与此程序的汇编语言文件中的“函数调用指令表”进行如下的核对:jmpostream::operator<<()Ajmpostream::operator<<(a)Fjmpderive::derive()jmphex()jmpendl()Ejmpderive::fun()jmpios::unlock()jmpbase::act()Djmpbase::base()jmpderive::act(d)jmpios::setf()Cjmpostream::operator<<(d)jmpbase::fun(d)jmpmain(b)Bjmpios::lock()jmpflush(f)此表内四条调用指令(以红色标出)本身的四个地址、和、E与该程序的运行结果(也以红色标出)完全符合。还可看出基类classbase和派生类classderive的两个vtable中的两个虚函数act()和fun()的排列顺序完全相同虚函数的特点可归纳如下:虚特性可继承适用于指针和引用不适用于函数的按数值调用方式(例见§)构造函数不能定义为虚函数。因编译系统在建立对象的过程中不允许进行所谓动态联编所以构造函数不能为虚函数。但构造函数能够调用虚函数。析构函数应能为虚函数(例见§)静态的成员函数不能为虚函数。因为它没有this指针同时它由该类的所有对象共享无法由单个对象专用。纯虚函数(purevirtualfunction)和抽象类(abstractclass)前面§中例virfuncpp中基类classRate中使用虚函数Area()时将它定义为virtualdoubleArea(){return}此处虚函数Area()并无任何意义它只是用作各派生类中该同名虚函数的接口。在此情况下能否将它省略?例试图忽略虚函数但程序无法运行virfuncpp与virfuncpp不同处用蓝笔标出trytoomitArea()inclassRate,becauseitismeaningless,buttheprogramdoesn'twork#include<iostreamh>#defineFRATEfundamentalrateclassRate{public:Rate(doublei,doublej){x=iy=jthelargerxandyare,iefurtherthehighertheratewillberate=FRATE*(xy)}doublerate试图省略virtualdoubleArea(){return}itcannotbeomitted,otherwisetherebeError:'Area':undeclaredidentifierdoubleFee(){returnrate*Area()}private:doublex,y}以上程序中试图取消基类classRate中的Area()但程序无法运行。虽然不能取消但可简化为纯虚函数。即virtualdoubleArea()=见以下程序。例使用纯虚函数作为接口purevircpp与virfuncpp不同处用蓝笔标出virtualfunctioninvirfuncppismadepure#include<iostreamh>#defineFRATEfundamentalrateclassRate{public:Rate(doublei,doublej){x=iy=jthelargerxandyare,iefurtherthehighertheratewillberate=FRATE*(xy)}doubleratevirtualdoubleArea()=纯虚函数不必用{return}doubleFee(){returnrate*Area()}private:doublex,y}classSquare:publicRate{public:Square(doublei,doublej,doublek):Rate(i,j){l=k}doubleArea(){returnl*l}private:doublel}classRectangle:publicRate{public:Rectangle(doublei,doublej,doubleh,doublek):Rate(i,j){w=hl=k}doubleArea(){returnw*l}private:doublew,l}voidmain(){Squaresq(,,)cout<<sqFee()<<endlRectanglerec(,,,)cout<<recFee()<<endl}*Results:*例纯虚函数应用举例:对象指针的继承absclscppcopiedfromWangYan'sbook,ptheparametersofmainprogramhavebeengreatlymodified#include<iostreamh>classPoint{intx,ypublic:Point(inti=,intj=){x=iy=j}virtualvoidDraw()=}classLine:publicPoint{intx,ypublic:Line(inti=,intj=,intm=,intn=):Point(i,j){x=my=n}virtualvoidDraw()定义(实现)虚函数{cout<<"Line::Draw"<<endl}}classCircle:publicPoint{intx,ypublic:Circle(inti=,intj=,intp=,intq=):Point(i,j){x=py=q}virtualvoidDraw()定义(实现)虚函数{cout<<"Circle::Draw"<<endl}}voidDrawobj(Point*p)形参为基类对象指针{p>Draw()}voidmain(){Line*lineptr=newLineCircle*cirptr=newCircleDrawobj(lineptr)Drawobj(cirptr)}*Results:Line::DrawCircle::Draw*各对象的栈区存储内容Line的对象Circle的对象纯虚函数的使用引出抽象类的概念。当一个类中包含至少一个纯虚函数时该类即是抽象类。而抽象类是不能建立对象的。例对抽象类的判断absclscpp#include<iostreamh>classA{public:virtualvoidfun()=virtualvoidwork()=}classB:publicA{public:virtualvoidfun(){cout<<'A'}}classC:publicB{public:virtualvoidwork(){cout<<'B'}}voidmain(){Bberror:'B':cannotinstantiateabstractclassduetothefollowingmemberswarning'voidthiscallA::work(void)':purevirtualfunctionwasnotdefinedCc}其中classA是抽象类它有两个纯虚函数classB仍然是抽象类因它继承了classA的纯虚函数work()所以classA和classB都不能建立对象。classC是具体类因它继承了classB的已定义(实现)的虚函数fun()并且自己又定义(实现)了虚函数work()。所以classC可以建立对象c。可得结论如下:一个抽象类至少具有一个纯虚函数即没有指明任何具体实现的虚函数。它的主要作用是组织一个继承的层次结构派生出相关的子类。抽象类用于描述一组子类的共同的操作接口。它只能用作基类而完整的实现则由派生类加以完成。如果一个抽象类的派生类中没有对虚函数进行定义而只继承了基类的虚函数则这个纯虚函数仍然是纯虚函数而派生类仍然是一个抽象类。如果一个抽象类的派生类中给出了基类中纯虚函数的定义(实现)则这个派生类即成为一个具体类(concreteclass)。因此说纯虚函数是抽象类的充要条件。抽象类具有以下特点:抽象类是一种特殊的类这种类不能建立对象。抽象类不能用作参数类型、函数返回值或显式转换的类型。可以声明抽象类的指针和引用此指针可以指向它的派生类。*多继承中基类指针的继承在类的三层结构中如果没有虚基类而有多继承则指向第一层非虚基类的指针不能隔层由第三层派生类继承因这将产生二义性。见下例:例没有虚基类的多继承指向非虚基类的指针不能隔层继承以免产生二义性。ptrinhcppItdoesn'twork,becausethepointertobasecannotbeinheritedbythederivedclassofthethirdlevelinmultiinheritanceifthebaseclassesarethesameandnotavirtualclass#include<iostreamh>classbase{public:virtualvoidf(){cout<<"functionofbase"<<endl}}classderiveA:publicbase{public:voidf(){cout<<"functionofderiveA"<<endl}}classderiveB:publicbase{public:voidf(){cout<<"functionofderiveB"<<endl}}classgrand:publicderiveA,publicderiveB{public:voidf(){cout<<"functionofgrand"<<endl}}voidmain(){base*ptrderiveAobjderiveBobjgrandobjptr=objptr>f()ptr=objptr>f()ptr=objERROR:ambiguousconversionfrom'classgrandnear*'to'classbasenear*'ptr>f()}*Results:classderiveAclassderiveB*DAGpublicpublicpublicpublic多继承中使用级联式派生时要注意一点指向相同的间接的非虚基类的指针不能隔层继承(即不允许第三层派生类对象隔层继承第一层非虚基类的指针)。因为这种情况下将产生二义性。这是因为存在两个classbase使obj无所适从。但当存在虚基类时由于只存在一个第一层基类所以不会产生二义性。因此指向虚基类的指针能够隔层继承。例使用虚基类的多继承允许第三层派生类对象隔层继承虚基类的指针。ptrinhcppimprovedversionofWangYan'sbook,pvirtualfunctioncanbeinheritedbyderivedclassofthethirdlevelinmultiinheritanceonlywhenthebaseclassisavirtualclass#include<iostreamh>classbase{public:virtualvoidf(){cout<<"functionofbase"<<endl}}classderiveA:virtualpublicbase{public:voidf(){cout<<"functionofderiveA"<<endl}}classderiveB:virtualpublicbase{public:voidf(){cout<<"functionofderiveB"<<endl}}classgrand:publicderiveA,publicderiveB{public:voidf(){cout<<"functionofgrand"<<endl}}voidmain(){base*ptrderiveAobjderiveBobjgrandobjptr=objptr>f()ptr=objptr>f()ptr=objptr>f()}*Results:functionofderiveAfunctionofderiveBfunctionofgrand*此处classbase是虚基类则其DAG为:DAGpublicpublicpublicpublic这是虚基类的功能!允许多继承中第三层派生类对象隔层继承虚基类的指针。(第五章完)objiobjbase::iobjiderive::fun()调用指令的地址=eRate::Fee()sqlSquare::Area()sqRate::ratefirstbaseobjVPTRsecond::who()second::who入口first::who入口first::who()objVPTRptrobjVPTRbase::who()base::who入口objiobjiobjiobjbase::iobjbase::iobjVPTRobjVPTRobjVPTRgrand::fun()调用指令的地址(grand::fun入口)grand::inc()调用指令的地址(grand::inc入口)grVPTRderive::fun()调用指令的地址(derive::fun入口)derive::inc()调用指令的地址(derive::inc入口)ddVPTRbsVPTRbase::fun()调用指令的地址(base::fun入口)base::inc()调用指令的地址(base::inc入口)grandderivebasegrand::x()调用指令的地址(grand::x入口)objVPTRderive::x()调用指令的地址(derive::x入口)objVPTRbase::x()调用指令的地址(base::x入口)objVPTRgrandderivebasetwovirtual::f()调用指令的地址(twovirtual::f入口)SmithSmithsptrsptrYousptrsptrSmithobjitwovirtual::x()调用指令的地址(twovirtual::x入口)objVPTRonevirtual::x()调用指令(jmponevirtual::x)objionevirtual::x()调用指令的地址(onevirtual::x入口)objVPTRobjiderive::act()调用指令的地址=Line::xLine::yCircle::xCircle::yPoint::xPoint::yPoint::xPoint::yVPTRVPTRclasssquareclassrectangleclassRateclassRateclasssecondclassbaseclassfirstclassbaseddVPTR=CbsVPTR=代码区sqRate::xsqVPTRsquare::Area()调用指令的地址()basechtype::ch=‘A’deriveBderiveAchtype::chchtype::ch=‘A’chtype::chchtype::chnumtype::y=numtype::znumtype::y=basenumtype::znumtype::ygrandbasebase::fun()调用指令的地址=deriveBderiveAgrandbase::act()调用指令的地址=main()调用sqfee()ppt.主程序第四句ptr=obj将指针ptr指向base类的对象obj.主程序第五句ptr>who()调用指针ptr所指向的base::who().主程序第六句ptr=obj将指针ptr重新指向first类的对象obj.主程序第七句ptr>who()调用指针ptr所指向的first::who().主程序第八句ptr=obj将指针ptr重新指向second类的对象obj.主程序第九句ptr>who()调用指针ptr所指向的second::who(

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

评分:

/80

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利