已阅读5页,还剩45页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
2020 4 1 1 5 2 2虚基类 1 虚基类的概念在C 语言中 一个类不能被多次说明为一个派生类的直接基类 但可以不止一次地成为间接基类 这就导致了一些问题 为了方便说明 先介绍多继承的 类格 表示法 派生类及其基类可用一有向无环图 DAG 表示 其中的箭头表示 由派生而来 类的DAG常称为一个 类格 复杂类格画出来通常更容易理解 例如 例5 19classL public intnext classA publicL classB publicL classC publicA publicB public voidf next 0 这时 next有两个赋值语句next 0 具有二义性 它是将A next置为零 还是将B next置为零 或者将两者都置为0 需要在函数f 中被显式的说明 2 2020 4 1 3 如果希望间接基类L与其派生类的关系是如下图 C 语言提供了这种描述手段 它将L说明为A和B的虚基类 2020 4 1 4 当在多条继承路径上有一个公共的基类 在这些路径中的某几条路经汇合处 这个公共基类就会产生多个实例 如果只想保存这个基类的一个实例 可以将这个公共基类说明为虚拟基类或称虚基类 它仅是简单地将关键字virtual加到基类的描述上 例如改写上述例子为例5 20 例5 20classL public intnext classA virtualpublicL classB virtualpublicL classC publicA publicB public voidf next 0 这时C类对象中只有L的一个复制 因而函数C f 中的语句next 0 没有二义性 对于类C而言 L类是B类的虚基类 而是类A的真基类 但对于类B而言 L类还是B类的真基类 例5 21 或classA publicvirtualL 或classA publicvirtual 5 classL public intnext classA virtualpublicL classB virtualpublicL classC publicB publicA public voidf next 0 此例中 对于类C而言 L类是A类的虚基类 而是类B的真基类 派生时 A B的顺序变了 6 2020 4 1 7 一个派生类的对象的地址可以直接赋给虚基类的指针 例如 Cobj L ptr 将产生编译错误 2020 4 1 8 2 虚基类对象的初始化虚基类的初始化与多继承的初始化在语法上是一样的 但隐含的构造函数的调用次序有点差别 虚基类构造函数的调用次序是这样规定的 1 虚基类的构造函数在非虚基类之前调用 2 若同一层次中包含多个虚基类 虚基类构造函数按它们说明的次序调用 3 若虚基类由非虚基类派生 则遵守先调用基类构造函数 再调用派生类构造函数的规则 2020 4 1 9 例如 classX publicY virtualpublicZ Xone 将产生如下调用次序 Z Y X 这里Z是X的虚基类 故先调用Z的构造函数 再调用Y的构造函数 最后才调用派生类X自己的构造函数 例5 22 include iostream h classbase public base cout Base endl classbase2 public base2 cout Base2 endl classlevel1 publicbase2 virtualpublicbase public level1 cout level1 endl classlevel2 publicbase2 virtualpublicbase public level2 cout level2 endl classtoplevel publiclevel1 virtualpubliclevel2 public toplevel cout toplevel endl toplevelview voidmain 当建立对象view时 将产生如下调用次序 level2 level1 toplevel 而level2 要求 base base2 level2 level1 要求base2 level1 toplevel 要求toplevel 所以 构造函数的调用顺序为 base base2 level2 base2 level1 toplevel 10 例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的虚基类 11 2020 4 1 12 例5 24classB classX virtualpublicB classY virtualpublicB classZ publicB classAA publicX publicY publicZ 这里AA具有两个B类的子对象 Z的B和x与Y共享的虚拟的B classV public intV classA public inta classB publicA virtualpublicV classC publicA VirtualpublicV classD publicB publicC public voidf voidD f v a 例5 25虚基类和非虚基类的不同 在D中仅仅一个v 错误 具有二义性 在D中有两个a 调用次序 B V A B C A C D D 13 2020 4 1 14 5 3虚函数与多态性 对于普通成员函数的重载 可表达为下面的方式 1 在同一个类中重载2 在不同类中重载3 基类的成员函数在派生类中重载因此 重载函数的访问是在编译时区分的 有以下三种方法 2020 4 1 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 includeclassA public voidfun cout InA endl classB publicA public voidfun cout InB endl classC publicB public voidfun cout InC endl voidmain CCobj Cobj fun Cobj B fun Cobj A fun A 16 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 17 include includeclassB class charname 80 public voidput name char s strcpy name s voidshow name cout name n main B class p B classB ob D class dp D classD ob p 错误 错误 该程序输出 ThomasEdisonAlbertEinstein555555 1234555555 1234 classD class publicB class charphone num 80 public voidput phone char num strcpy phone num num voidshow phone cout phone num n 18 基类指针指向派生类类对象 只能访问基类成员 派生类指针指向派生类类对象 访问所有成员 基类指针 派生类指针 1 可以用一个指向基类的指针指向其公有派生类的对象 但是相反却不正确 即不能用指向派生类的指针指向一个基类的对象 2 希望用基类指针访问其公有派生类的特定成员 必须将基类指针用显式类型转换为派生类指针 例如 D class p show phone 19 5 3 2虚函数 例5 28 includeclassBase protected intx public Base inta x a voidwho cout base x n classSecond d publicBase public Second d inta Base a voidwho cout Secondderivation x n classFirst d publicBase public First d inta Base a voidwho cout Firstderivation x n 1 虚函数的概念 20 voidmain Base p Basebase obj 1 First dfirst obj 2 Second dsecond obj 3 p 该程序输出 base1base2base3Firstderivation2Secondderivation3 p who 指向基类的指针p 不管是指向基类的对象base obj还是指向派生的对象first obj和second obj p who 调用的都是基类定义的who 的版本 必须显式地用first obj who 和second obj who 才能调用类first d和类second d中定义的who 的版本 其本质的原因在于普通成员函数的调用是在编译时静态区分 21 2020 4 1 22 如果随着p所指向的对象的不同p who 能调用不同类中who 版本 这样就可以用一个界面p who 访问多个实现版本 Base中的who First d中的who 以及Second d中的who 这在编程时非常有用 实际上 这表达了一种动态的性质 函数调用p who 依赖于运行时p所指向的对象 虚函数提供的就是这种解释机制 如果在base中将成员函数who 说明为虚函数 则修改上述程序为例5 29 虚函数是在基类中被冠以virtual的成员函数 它提供了一种接口界面 虚函数可以在一个或多个派生类中被重新定义 但要求在派生类中重新定义时 虚函数的函数原型 包括返回类型 函数名 参数个数 参数类型的顺序 必须完全相同 classFirst d publicBase public First d inta Base a voidwho Firstderivation x n includeclassBase protected intx public Base inta x a virtualvoidwho cout base x n classSecond d publicBase public Second d inta Base a voidwho Secondderivation x n voidmain Base p Basebase obj 1 First dfirst obj 2 Second dsecond obj 3 p 程序输出 base1Firstderivation2Secondderivation3Firstderivation2Secondderivation3 23 基类的虚函数who 定义了一种接口 在派生类中此接口定义了不同的实现版本 由于虚函数的解释机制 实现了 单界面 多实现版本 的思想 这种在运行时刻将函数界面与函数的不同实现版本进行匹配的过程 称为晚期匹配 也称为运行时的多态性 p who 24 基类函数f具有虚特性的条件是 1 在基类中 将该函数说明为virtual函数 2 定义基类的公有派生类 3 在基类的公有派生类中原型一致地重载该虚函数 4 定义指向基类的指针变量 它指向基类的公有派生类的对象 例5 30 25 voidmain derivedd base bp classbase public virtualvoidvf1 virtualvoidvf2 virtualvoidvf3 voidf classderived publicbase public voidvf1 voidvf2 int charvf3 voidf 错误 仅返回类型不同 具有虚特性 一般函数重载 参数不同 虚特性丢失 一般的函数重载 非虚函数的重载 26 例5 31 includeclassfigure protected doublex y public voidset dim doublei doublej 0 x i y j virtualvoidshow area cout Noareacomputationdefined cout forthisclass n classtriangle publicfigure public voidshow area cout Trianglewithhigh cout x andbase y cout hasanareaof cout x 0 5 y n classsquare publicfigure public voidshow area cout Squarewithdimension cout x y cout hasanareaof cout x y n classcircle publicfigure public voidshow area cout Circlewithradius cout x cout hasanaeraof cout 3 14 x x 27 voidmain figure p trianglet squares circlec p p show area 程序输出 Trianglewithhigh10andbase5hasanareaof25 0Squarewithdimension10 5hasanareaof50 0Circlewithradius9hasanareaof254 34 28 2 可以使用成员名限定可以强制使用静态联编 例5 32 includeclassA public virtualvoidfun cout InA end1 classB publicA public voidfun cout InB end1 classC publicB public voidfun cout InC end1 voidmain CCobj Cobj fun Cobj B fun Cobj A fun A Aref 调用B fun 不是C fun 使用成员名限定可以强制使用静态联编 29 2020 4 1 30 3 在成员函数中调用虚函数 在一个基类或派生类的成员函数中 可以直接调用等级中的虚函数 此时 需要根据成员函数中this指针和它所指向的对象来判断调用的是哪个函数 例5 33 includeclassA public 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 publicA public voidfun1 cout B1 2 fun2 voidfun2 cout B2 3 fun3 voidfun3 cout B3 4 fun4 voidfun4 cout B4 5 fun5 voidfun5 cout Bend endl voidmain A Apointer1 newA A Apointer2 newB Apointer1 fun1 Apointer2 fun1 deleteApointer1 deleteApointer1 程序输出 A1 2A2 3A3 4A4 5AendB1 2B2 3B3 4B4 5Bend fun2 相当于fun2 constA this this即为Apointer2 因此仍然调用所指向对象中的函数 31 例5 34 includeclassA public 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 publicA public 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 endl voidmain A Apointer newB Apointer fun1 deleteApointer 程序输出 A1 2B2 3B3 4B4 5Bend fun1 不是虚函数 故基类的指针变量 指向派生类时只能访问基类中定义的成员 fun2 fun3 fun4 fun5是虚函数 故基类的指针变量 指向派生类时访问的是派生类中定义的成员 32 例5 35 includeclassA public 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 publicA public voidfun3 cout B3 4 endl fun4 voidfun4 cout B4 5 endl fun5 voidmain A Apointer newB Apointer fun1 deleteApointer 程序输出 A1 2A2 3B3 4B4 5Aend 基类虽然将fun1 fun2定义为虚函数 但在派生类中并没有原型一致的重载它们 所以要调用基类中的函数 33 2020 4 1 34 4 在构造函数和析构函数中调用虚函数 在构造函数和析构函数中调用虚函数时 采用静态联编 即它们所调用的虚函数是自己的类或者它的基类中的虚函数 但不是任何在派生类中定义的虚函数 例5 36 includeclassA public A cout AisCreating endl virtualvoidfun1 cout Afun1 endl virtualvoidfun2 cout Afun2 endl A cout AisDestroy endl classB publicA public B cout BisCreating endl fun1 voidfun fun1 B cout Bisdestroy end1 fun2 classC publicB public C cout CisCreating endl voidfun1 cout Cfun1 endl virtualvoidfun2 cout Cfun2 endl C cout CisDestroy endl fun2 voidmain CCobj Cobj fun 程序输出 AisCreatingBisCreatingAfun1CisCreatingCfun1CisdestroyCfun2BisdestroyAfun2Aisdestroy 在构造函数和析构函数中调用虚函数时 是自己的类或者它的基类中的虚函数 但不是任何在派生类中定义的虚函数 35 2020 4 1 36 5 析构函数可以定义为虚函数 构造函数不能为虚函数 而析构函数可以定义为虚函数 若析构函数为虚函数 那么当使用delete释放基类指针指向的派生类对象时 先调用派生类的析构函数 再调用基类的析构函数 classDeriver publicBase intd public Deriver intnum1 intnum2 Base num1 d num2 cout Derivercreate n Deriver cout Deriverdestory n 例5 37 includeclassBase intb public Base intnum b num cout Basecreate n Base cout Basedestroy n voidmain Base pb1 pb2 pb1 newBase 1 pb2 newDeriver 2 3 deletepb1 deletepb2 cout n BaseBobj 4 DeriverDobj 5 6 程序输出 BasecreateBasecreateDerivercreateBasedestroyBasedestory BasecreateBasecreateDerivercreateDeriverdestroyBasedestroyBasedestory 基类对象指向派生类对象时 释放时不调用派生类的析构函数 37 例5 38 includeclassBase intb public Base intnum b num cout Basecreate n virtual Base cout Basedestroy n classDeriver publicBase intd public Deriver intnum1 intnum2 Base num1 d num2 cout Derivercreate n Deriver cout Deriverdestory n voidmain Base pb1 pb2 pb1 newBase 1 pb2 newDeriver 2 3 deletepb1 deletepb2 BasecreateBasecreateDerivercreateBasedestroyDeriverdestroyBasedestroy 需要先调用派生类的析构函数 再调用基类的析构函数 38 2020 4 1 39 6 多继承和虚基类 在多继承中 由于能从多条路径继承 如何确定函数调用时应该激活哪一个函数版本呢 下述例子说明了一个问题如 例5 39 structA1 intf structA2 virtualA1 缺省为公有派生方式 intf structA3 A2 structA4 A3 virtualA1 voidf A2 pa2 A4 pa4 pa2 f 调用A2 f pa4 f 调用A2 f 继承路径如图 A1 A1 A3 A2 A3 A4 A4 创建A4的对象时 构造函数的调用次序为 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 2020 4 1 42 5 3 3纯虚函数及抽象类 基类表示抽象的概念 如figure是一个基类表示有型的东西 可以派生出封闭图形和非封闭图形两类 Shape体现了一个抽象的概念 在figure中定义一个求面积的函数显然是无意义的 但可以将其声明为一个虚函数 提供一个派生的公共界面 并由各派生类提供求面积的各自版本 因此基类的有些虚函数没有定义是很正常的 但是要求派生类必须重新定义这些虚函数 为此C 引入了纯虚函数的概念 2020 4 1 43 纯虚函数是一个在基类中说明的虚函数它在基类中没有定义 要求任何派生类必须定义自己的版本 纯虚函数具有以下的形式 virtualtypefunc name 参数表 0 在构造函数和析构函数中调用虚函数使用静态编联 因此在这两个函数中不能调用纯虚函数 但其它函数可以调用纯虚函数 2020 4 1 44 如果一个类至少有一个纯虚函数 就称这个类为抽象类 抽象类可以定义一种接口 由派生类提供各种实现 抽象类只能用作其它类的基类 可以用作声明抽象类的指针和引用 不能创建对象 不能用作参数不能用作函数返回类型或显式转换的类型 例5 40classpoint classshape pointcenter public pointwhere returncenter voidmove pointp center p draw virtualvoidrotate int 0 virtualvoiddraw 0 shapex shap
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 外汇商品房抵押贷款合同
- 猎头服务客户关系管理创新创业项目商业计划书
- 肉类沙拉罐头行业跨境出海项目商业计划书
- 医院住院部护理管理规范及工作建议
- 车间安全生产标准及检查流程指南
- 中小企业人力资源管理制度大全
- 幼儿园自然拼读启蒙课程开发方案
- 快递行业物流流程优化建议
- 环保项目尾气治理实施方案
- 食品加工厂质量控制流程指南
- 安全等级保护咨询方案
- 数据共享与安全风险管理措施
- 2025年《护士条例》考试题有答案
- 2025年及未来5年中国节能服务转移行业市场调查研究及投资前景预测报告
- 2025安徽合肥市轨道交通集团有限公司第二批次社会招聘12人笔试参考题库附带答案详解
- 2025年国家工作人员学法用法考试题库附参考答案
- 纹绣眉毛教程课件
- 2025年中国高纯度氧化镁行业市场分析及投资价值评估前景预测报告
- 团课讲座课件
- 2025年及未来5年中国工程总承包行业市场深度分析及发展前景预测报告
- 2025江西上饶余干县天然气有限公司招聘2人考试参考试题及答案解析
评论
0/150
提交评论