




已阅读5页,还剩58页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第6章多态性和虚函数 6 1多态性6 2虚函数6 3纯虚函数和抽象类6 4虚析构函数 多态性 什么是多态性 同一个消息被不同对象接收时 产生不同的结果 即采用不同方法实现同一接口 多态性的定义 定义 同一个函数名 实现不同的功能 相同名字函数在不同场合表现出不同行为 调用时 系统根据不同的参数或对象所属的类来调用相应的函数 实现动态的结合 C 支持两种多态性 编译时的多态 通过函数重载和运算符重载来实现 运行时的多态 通过继承和虚函数来实现 联编 定义 使一个程序经过编译 连接 成为可执行文件的过程 静态联编 在程序运行之前就完成的联编 系统在编译时就知道调用函数的全部信息 函数调用速度快 效率高 动态联编 在程序运行时才能完成的联编 直到程序运行时才能确定调用哪个函数 灵活性 问题抽象性和程序易维护性 两种多态性 静态联编支持的多态性 称编译时的多态性 通过函数重载和运算符重载来实现 动态联编支持的多态性 称运行时的多态性 通过继承和虚函数来实现 运行时的多态性 通过虚函数和纯虚函数来实现 在基类 定义派生类所拥有的通用接口 例如 在基类中定义名为f 的纯虚函数 在派生类 定义具体的实现方法 所有派生类中都定义了名为f 的虚函数 不同派生类的f 具有不同的功能 实现运行时的多态性 在程序中使用基类指针调用f 直到程序运行时 系统才能确定调用哪一个函数 基类指针指向哪个派生类 就调用哪个派生类 6 2虚函数 6 2 1为什么要引入虚函数6 2 2虚函数的定义与使用 6 2 1为什么要引入虚函数 指向基类对象的指针都可以指向它的公有派生类对象 若试图指向它的私有派生类对象是被禁止的 不能将一个声明为指向派生类对象的指针指向其基类的一个对象 指向派生类的指针不能继承 也就是说 基类的指针可以指向它的派生类 但不能再指向它的派生类的派生类 声明为指向基类对象的指针 当它指向公有派生类对象时 只能利用它来直接访问派生类中从基类继承来的成员 不能直接访问公有派生类中特定的成员 若想访问其公有派生类的特定成员 可以将基类指针显式类型转换为派生类指针来实现 6 2 2虚函数的定义与使用 1 虚函数的定义虚函数是引入了派生概念后 用来表现基类和派生类的成员函数之间的一种关系的 虚函数定义是在基类中进行的 virtual 虚函数提供了一种接口界面 在基类中的某个成员函数被声明为虚函数后 此虚函数就可以在一个或多个派生类中被重新定义 在派生类中重新定义虚函数时 都必须与基类中的原型完全相同 虚函数是一种非静态的成员函数 说明虚函数的方法如下 virtual 类型 函数名 参数表 6 2 2虚函数的定义与使用 如果在基类中定义了一个虚函数 该虚函数可以在它的一个或多个派生类中重载 不用冠以virtual也自动成为虚函数 举例 虚函数的使用说明 在基类中 使用关键字virtual 把public和protected部分的成员函数声明为虚函数 在派生类中重新定义虚函数时 函数的返值类型和参数应该同基类中的虚函数完全一致 如果参数不同 属于一般的函数重载 不自动为虚函数 如果仅返值类型不同 产生一个错误 既非重载函数 也非虚函数 举例 虚函数的使用说明 虚函数的重载函数仍是虚函数 若在基类中声明了一个虚函数 派生类重载该成员函数时无需使用virtual 重载的成员函数自动成为虚函数 若在派生类中没有重新定义虚函数 派生类对象将使用从基类继承的虚函数 虚函数必须是类的成员函数 不能是友元函数 也不能是静态成员函数 析构函数可以是虚函数 但是构造函数不可以是虚函数 虚函数的使用说明 如果基类中定义了虚函数 使用基类指针调用基类的一个虚函数时 C 系统对该调用进行动态绑定 根据指针当前指向的对象类型 来确定该调用的函数 然而 调用普通函数时 则是静态绑定 编译时就确定该调用的函数 在多重继承下 对虚函数的调用与单一继承的情况相似 举例 举例 运行时的多态性的作用 运行时的多态性 使得我们可以使用基类指针分别指向多个不同派生类的对象 以此来调用不同功能的函数 举例 举例 空的虚函数 派生类并不一定必须实现基类中的虚函数 如果派生类想通过虚函数机制存取虚函数 则必须建立一条从基类到派生类的虚函数路径 许多没有使用虚函数的中间类也必须声明该函数 以保证其后的派生类能使用该虚函数 可以通过声明空函数来达到此目的 空的虚函数 举例 includeclassBase public virtualvoidprint cout classbase n classSon publicbase public virtualvoidprint classGrandson publicson public virtualvoidprint cout classgrandson n voidshow base p p print voidmain base pbase newbase son pson newson grandson pgrandson newgrandson show pbase show pson show pgrandson classbase classgrandson 空的虚函数 举例 可以考虑 如果去掉son类的空虚函数 程序运行结果如何 如果去掉virtual关键字 程序运行结果又如何 如果保留son类的空虚函数 去掉grandson类的虚函数定义 程序运行结果又如何 如果把son类的虚函数定义为cout classson n 之后去掉grandson类的虚函数定义 程序的运行结果又如何 6 3 1纯虚函数的概念 有时 基类仅表示一种抽象的概念 并不与具体的事务相联系 于是 在基类中说明的虚函数只是为了给它的派生类提供一个公共的界面 这样虚函数在基类中没定义 也没有函数体 在派生类中需要根据具体情况来定义重载虚函数的功能 在这种情况下 可以将基类中的虚函数定义成纯虚函数 纯虚函数是一种没有具体实现的特殊的虚函数 一个基类中有一个纯虚函数时 则在它的派生类中至少有一个虚函数 否则纯虚函数是无意义的 6 3 1纯虚函数的概念 纯虚函数的定义格式如下 virtual 类型 函数名 参数表 0 0 不表示函数返回值为0 只起形式上的作用 告之系统这是 纯虚函数 由于纯虚函数所在的类中没有它的定义 不具备函数的功能 在该类的构造函数 析构函数及其它函数中不允许调用纯虚函数 否则会导致程序运行错误 作用 在基类中为其派生类保留一个函数的名字 以便派生类根据需要对它进行重新定义 6 3 1纯虚函数的概念 举例 includeUsingnamespacestd classCircle 声明基类Circlepublic voidsetr intx r x virtualvoidshow 0 定义纯虚函数protected intr ClassArea publicCircle public voidshow 重定义虚函数 求圆面积 cout Areais 3 14 r r endl Classperimeter publicCircle public voidshow 重定义虚函数 求圆周长 coutshow ptr 6 3 2抽象类的概念 1 抽象类和具体类的概念如果一个类至少有一个纯虚函数 那么就称该类为抽象类 能够建立实例化对象的类称为具体类 也就是不含纯虚函数的类为具体类 抽象类的主要作用是为其所组织的继承层次结构提供一个公共的基类 它刻划了公有行为的特征 其它类可以从它这里继承和实现接口 纯虚函数的实现由其具体的派生类来提供 抽象类的使用说明 抽象类只能作为其它类的基类 不能创建对象实例 因为含有一个或多个未定义的函数 抽象类不能用作参数类型 函数返回类型 或者显式转换的类型 可以声明指向抽象类的指针或引用 使用此指针并指向它的派生类 以支持运行时的多态性 在派生类中必须重载基类的纯虚函数 即给出有函数体的定义 否则 派生类仍被看作一个抽象类 抽象类的使用举例 2 抽象类举例 计算各类形状的总面积 includeclassshape 抽象类的定义 public virtualfloatarea 0 classtriangle publicshape 三角形类 protected floath w public triangle floathh floatww h hh w ww floatarea returnh w 0 5 3 抽象类举例 计算各类形状的总面积 classrectangle publictriangle 矩形类 public rectangle floath floatw triangle h w floatarea returnh w classcircle publicshape 圆类 private floatradius public circle floatr radius r floatarea returnradius radius 3 14 3 抽象类举例 计算各类形状的总面积 floattotal shape s intn 求所有图形的总面积 floatsum 0 for inti 0 iarea returnsum voidmain shape s 4 指针数组s 0 newtriangle 3 4 s 1 newrectangle 2 4 s 2 newcircle 5 s 3 newcircle 8 floatsum total s 4 cout sum endl End 6 4虚析构函数 将析构函数设计为虚函数不是必须的 只有在栈中动态创建的对象时 new 指针是指向基类的 当我们要释放动态对象时 delete 要把基类中的析构函数设计为虚函数 这时的基类和派生类的析构函数都应该为虚函数 因为析构函数执行时首先调用派生类的析构函数 其次才调用基类的析构函数 如果析构函数不是虚函数 而程序执行时又要通过基类的指针去销毁派生类的动态对象 那么用delete销毁对象时 只调用了基类的析构函数 会造成销毁对象不完全 6 4虚析构函数 includeclassbase public base cout base endl classsub publicbase public sub cout subclass endl voidmain base bc newsub deletebc 结果 base virtual 结果 sub base 建议 当使用基类指针指向公有派生类对象时 或任何时候在类中有虚函数 都应当直接增加虚析构函数 即使函数体为空 保证程序正常结束 注意 不可以在析构函数中调用虚函数 这样不可靠 第7章运算符重载 7 1概念7 2规则7 3重载为友元函数7 4重载为成员函数7 5常用运算符重载 编译时的多态性 是通过函数重载和运算符重载来实现的 系统在编译时就能确定调用哪个函数 调用速度快 效率高 确定所调用函数的方式 参数匹配支配原则类名限定 编译时的多态性 函数重载 用同一个名字定义一组相关的函数 这组函数执行相似的操作 在编译阶段 对于每一次同名函数的调用 由系统来选择具体由哪一个函数来执行 7 1运算符重载概念 从上可知 两个以上的函数如果同名 只要使用不同类型的参数或不同数量的参数 编译就能知道在什么情况下调用哪个函数 这就是函数重载的功效 而运算符重载实际上也是函数重载的一种 它是静态多态性的体现 对基本的的数据类型 C 提供了预定义的运算符 例如 但是 这些运算符的操作对象只能是基本数据类型的变量 实际上 对于很多用户自定义的类 也需要有类似的运算操作 因此 需要对运算符进行重新定义 给已有的运算符号添加新的功能 那么 C 语言允许程序员重新定义已有的运算符 使其能按用户的要求完成一些只在该特定类中使用的特定操作 这就是所谓的运算符重载 7 1运算符重载概念 7 1运算符重载概念 为何需要运算符重载 上例不能实现total com1 com2的原因是Complex类的类型不是预定义的基本数据类型 而是用户自定义的数据类型 C 知道如何相加两个int型数据 或相加两个float型数据 甚至知道如何把一个int型数据与一个float型数据相加 但无法直接将两个Complex类对象相加 在C 中 定义一个类就产生一个新的数据类型 类的对象也和变量一样既可以作为参数进行传递 还可以作为函数的返回类型 因此也希望预定义的内部运算符 如 等 在特定类的对象上以新的含义进行解释 如希望 total com1 com2 因此提出运算符重载 通过重载运算符 来解决类对象的简单运算 扩充运算符已有的功能 7 1运算符重载概念 运算符重载的实质就是函数重载 它主要优点就是允许改变系统默认的运算符操作方式 以适应用户定义类型的类似运算需求 在实现过程中 首先把指定的运算表达式转化为运算符函数的调用 运算对象转化为运算符函数的实参 然后根据实参的类型来确定需要调用的函数 这个过程是在编译过程中完成的 所以运算符重载所表现出来的多态性属于静态多态性 运算符的调用实际上就是一种函数调用 所以 运算符重载与函数重载相似 其目的是增加运算符函数 让运算符具有另一种功能 7 2运算符重载规则 运算符重载的机制简化了程序设计 同时也增加了程序的可扩充性 运用运算符重载机制 可以在不增加运算符的情况下 实现用户程序中各种各样的运算和操作 7 2运算符重载规则 不能重载的运算符是 1 成员访问运算符 2 作用域运算符 3 条件运算符 4 成员指针运算符 5 长度运算符 sizeof 6 编译预处理命令的开始符号 7 2运算符重载规则 运算符重载的规则 C 中的运算符除了少数几个以外 几乎全部可以重载 程序员不能定义新的运算符 只能重载已有的这些运算符 重载之后运算符的优先级和结合性都不能改变 运算符重载是针对新类型数据的实际需要 对原有运算符进行适当的改造 一般来讲 重载的功能应当与原有功能相类似 不能改变原运算符所需操作数的个数 同时至少要有一个操作数是自定义类型 总之 当C 语言原有的一个运算符被重载之后 它原先所具有的语义并没有消失 只相当于针对一个特定的类定义了一个新的运算符 运算符重载为友元函数和成员函数 运算符重载的实现形式有两种 重载为类的友元函数和重载为类的成员函数 使用哪种形式 取决于实际情况和习惯 可以参考以下的经验 1 只能使用成员函数重载的运算符有 new delete 2 单目运算符最好重载为成员函数 3 对于复合的赋值运算符如 建议重载为成员函数 4 对于其它运算符 建议重载为友元函数 除了赋值运算符外 其它运算符函数都可以由派生类继承 并且派生类还可有选择地重载自己所需要的运算符 包括基类重载的运算符 7 3运算符重载为友元函数 作为友元运算符函数 首先要在相应的类中声明为该类的友元函数 声明的一般形式为 friend返回类型operator运算符 形参表 友元运算符函数的定义如下 返回类型operator运算符 形参表 函数体 注意友元函数不属于任何类 它没有this指针 这与成员函数完全不同 若运算符是一元的 则参数表中有一个操作数 若运算符是二元的 则参数表中有两个操作数 也就是说在用友元定义重载运算符时 所有的操作数均需要用参数来传递 friendToperator Ta Tb 7 3运算符重载为友元函数 例如对于两个Complex类的对象相加的函数 ComplexAdd Complex 用重载运算符 编译器遇到语句c3 c1 c2时 会把它替换成c3 operator c1 c2 的完整形式 即与重载的运算符函数原形相匹配 7 3运算符重载为友元函数 这种运算符重载函数也称为双目友元运算符重载函数 双目运算符有两个操作数 通常在运算符的左右两侧 加减乘除 当用友元函数重载双目运算符时 两个操作数都要传给运算符重载函数 在C 中 不能直接进行复数加 减 乘 除运算 介我们可以定义四个友元运算符函数 通过重载 运算符来实现复数运算 classComplex 复数类 private doubleimage doublereal public Complex doublex 0 0 doubley 0 0 构造函数 real x image y voidPrint friendComplexoperator constComplex 以复数格式输出 Complexoperator constComplex 6 9i 7 3运算符重载为友元函数 还有一种运算符重载函数称为单目友元运算符重载函数 单目运算符只有一个操作数 通常在运算符的右侧 classCoord public coord inti 0 intj 0 voiddisplay friendCoordoperator Coord VoidCoord display cout x x y y endl Coordoperator Coord X 11 y 22X 12 y 23X 13 y 24 7 4运算符重载为成员函数 在上面的程序代码中 运算符 的重载函数被定义在Complex类之外 作为该类的友元函数 能访问类中的私有数据成员real和image 但是这种友元函数与类配合的情况 降低了该类独立性 破坏了该类的封装特性 因此在一般情况下 应将类所涉及的所有操作都定义在类中 即应将类的运算符重载成类中的成员函数 因为是在类中定义的操作 操作的一方就是当前的对象 成员变量也可以自由访问 这样 如果是重载双目运算符 就只要设置一个参数作为右侧运算量 而左侧运算量就是该对象本身 如果是重载单目运算符 就不必另外设置参数 运算符的操作量就是对象本身 7 4运算符重载为成员函数 返回值类型operator运算符 形参表 若运算符是一元的 则参数表为空 此时当前对象作为此运算符的单操作数 若运算符是二元的 则参数表中有一个操作数 此时当前对象作为此运算符的左操作数 参数表中的操作数作为此运算符的右操作数 依此类推 运算符函数的定义方式同一般成员函数 对类成员的访问与一般成员函数相同 定义如下 type类名 operator 参数表 函数体 重载运算符的使用方法同原运算符一样 只是它的操作数一定要是定义它的特定类的对象 07 04 cpp includeclassComplex 复数类 private doublereal image public Complex doublex 0 0 doubley 0 0 构造函数 real x image y Complexoperator constComplex 以复数格式输出 ComplexComplex operator constComplex 返回对象的引用 intmain Complexc1 2 7 c2 4 2 c3 c3 c1 c2 表达式的完整形式应该是c3 c1 operator c2 c3 Print if c3 c1 cout c3equalstoc1 endl 判断c3和c1是否相等elsecout c3doesn tequaletoc1 endl c3 c2 把c3赋值为c2的求负取反值c3 Print c3 c2 c3 Print return0 2 5ic3doesn tequaletoc1 4 2i0 0i 在以上main 函数中 编译器重新解释后 四个运算符重载对应的完整形式分别是 c1 c2等价于c1 operator c2 c3 c1等价于c3 operator c1 c2等价于c2 operator c3 c2等价于c3 operator c2 由此可以看出 针对双目运算符被重载为类的成员函数 实质是运算符左侧的对象调用了自身的运算符函数 右侧的操作量作为运算符函数的参数 因此要求运算符左侧的操作量必须是类的对象 7 4运算符重载为成员函数 7 5几个常用运算符的重载 1 自增和自减运算符的重载前自增运算符 和后自增运算符 重载的语法operator 前operator int 后 例7 5 用成员函数重载前自增和后自增运算符 通过例子可以看出 重载后置自增运算符 多了一个int型的参数 不带参数名 这个参数只是为了
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 话题讨论:诚信在生活中的作用8篇
- 农业种植区土地管理使用合同书
- 农业生产技术与安全知识考点
- 农村农业机械使用与安全责任协议
- 食品行业食品安全法规与实践练习题
- 机械工程行业实习表现证明(6篇)
- 在课堂上的一次特殊经历记事作文13篇范文
- 英语阅读理解的跨文化交际知识点集萃
- 2025年心理学考试试题及答案
- 2025年医学影像学基础知识考试试卷及答案
- 陕西省专业技术人员继续教育2025公需课《党的二十届三中全会精神解读与高质量发展》20学时题库及答案
- 重庆万州区社区工作者招聘笔试真题2024
- 郴州市2025年中考第二次模考历史试卷
- 酒店项目规划设计方案(模板)
- 2025名著导读《钢铁是怎样炼成的》阅读习题(含答案)
- 2025-2030中国冷热交换器行业市场现状分析及竞争格局与投资发展研究报告
- 美容院和干洗店合同协议
- 前程无忧测评题库
- ICU经口气管插管患者口腔黏膜压力性损伤预防的最佳证据总结 - 学习与临床应用
- 2025急性心梗诊疗指南
- 【闵行区人民法院】上海市闵行区劳动人事争议调解仲裁与审判白皮书(2023-2024年)
评论
0/150
提交评论