虚函数与多态性.ppt_第1页
虚函数与多态性.ppt_第2页
虚函数与多态性.ppt_第3页
虚函数与多态性.ppt_第4页
虚函数与多态性.ppt_第5页
已阅读5页,还剩61页未读 继续免费阅读

下载本文档

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

文档简介

1 第二部分面向对象的程序设计 第3章类和对象 一 第4章类和对象 二 第5章继承和派生第6章虚函数与多态性第7章运算符重载第8章模板第9章标准模板库STL第10章C 语言的输入和输出 2 第6章虚函数与多态性 本章要点 多态性的概念虚函数的定义与应用多继承与虚函数纯虚函数与抽象类 3 多态性 Polymorphism 是面向对象程序设计的一个非常重要的特性 如果不支持多态性 C 就不是真正的面向对象程序设计语言 多态性指的是不同的对象对于同样的消息会产生不同的行为 而消息在C 语言中指的就是函数的调用 不同的函数可以具有多种不同的功能 而多态就是允许用一个函数名的调用来执行不同的功能 4 6 1多态性概述 6 1 1多态的类型多态性不仅限于C 语言 从面向对象技术的角度来看 多态性可以分为四类 1 重载多态 前面学习的函数重载就属于此概念 运算符重载也是重载多态 第7章将详细介绍 2 强制多态 指将一个变量类型加以变化 以符合一个函数或者操作的要求 例如加法运算符在进行浮点数与整型数相加时 首先要对整型数进行强制类型转换为浮点数再相加的情况 就是强制多态的实例 3 包含多态 同样的操作可用于一个类型及其子类型 包含多态一般需要进行运行时的类型检查 主要是通过虚函数来实现 4 参数多态 采用参数化模板 通过给出不同的类型参数 使得一个结构可以适用多种数据类型 C 提供的函数模板和类模板即为典型的参数多态 第8章将详细介绍 5 对于多态性 一个要解决的主要问题就是何时把具体的操作和对象进行绑定 binding 也称联编 关联 绑定也指的是程序如何为类的对象找到执行操作函数的程序入口的过程 从系统实现的角度来看 多态可以分为两类 编译时多态运行时多态 6 编译时多态 指的在程序编译过程中时决定同名操作与对象的绑定关系 也称静态绑定 静态联编 典型的技术有函数重载 运算符重载 模板 由于这种方式是在程序运行前就确定了对象要调用的具体函数 因此程序运行的时候函数调用速度快 效率较高 其缺点是编程不够灵活 7 运行时多态 指的是在程序运行过程中动态地确定同名操作与具体对象的绑定关系 也称动态绑定 动态联编等 主要通过使用继承和虚函数来实现 在编译 连接过程中确定绑定关系 程序运行之后才能确定 动态绑定的优点是编程更加灵活 系统易于扩展 由于内部增加了实现虚函数调用的机制 因此要比静态绑定的函数调用速度慢些 8 6 1 2基类指针指向派生类对象 例6 1 函数重载在多态性中的应用 06 01 cpp includeusingnamespacestd classBase 基类 public voidPrint cout BaseClassPrint endl 9 classDerived publicBase 公有派生类 public voidPrint coutPrint pb 10 BaseClassPrint DerivedClassPrint BaseClassPrint BaseClassPrint BaseClassPrint 程序的运行结果为 11 在派生类中重写了成员函数Print 使得不同的对象对同一函数名的调用产生了不同的结果 这就是多态性的具体表现 不过却是编译时多态 在程序中可以看到 要想输出对应的字符串 必须明确地指出调用哪个对象的成员函数 即通过类的对象调用 或者通过加类名限定进行调用 如例6 l中的语句 bl Print 调用基类对象b1的Print函数d1 Print 调用派生类对象d1的Print函数d1 Base Print 调用派生类对象d1中的继承基类的Print 函数 12 这样调用成员函数的方法是显示调用 对于结构复杂的程序 每个对象都显式地写出要调用的成员函数是不切实际的 往往需要通过对象指针或引用来实现对成员函数的调用 程序结果可以看出 在例6 1中定义了一个基类的指针pb 当把这个指针指向派生类的对象时 希望调用派生类对象d1的Print 函数 但却仍然调用了d1对象包含的基类成员函数Print 即本程序希望运行结果为 BaseClassPrint SubClassPrint BaseClassPrint BaseClassPrint SubClassPrint 13 6 2虚函数 6 2 1虚函数的定义格式虚函数必须存在于类的继承环境之中才有意义 声明虚函数的方法很简单 只要在基类的成员函数名前加关键字virtual即可 格式如下 class类名 virtual类型成员函数名 参数表 14 定义虚函数要注意以下的问题 1 虚函数必须声明为类的成员函数 全局函数及静态成员函数不能声明为虚函数 2 虚函数与一般成员函数一样 可定义在类体内 也可以定义在类体外 3 虚函数的声明只能出现在类函数声明语句中 而不能在成员函数实现的部分声明 4 构造函数不能声明为虚函数 5 析构函数可以是虚函数 6 当一个基类中声明了虚函数 则虚函数特性会在其直接派生类和间接派生类中一直保持下去 并且其派生类不必再用virtual关键字声明 15 例6 2 虚函数的定义与应用 对例6 1的改进 06 02 cpp includeusingnamespacestd classBase 基类 public virtualvoidPrint cout BaseClassPrint endl classDerived1 publicBase 公有派生类1 public voidPrint cout Derived1ClassPrint endl 16 classDerived2 publicBase 公有派生类2 public voidPrint coutPrint 用指针调用成员函数rb Print 用基类引用调用基类对象成员函数pb 17 BaseClassPrint BaseClassPrint Derived1ClassPrint Derived2ClassPrint Derived1ClassPrint 程序的运行结果为 18 6 2 2多继承与虚函数 前面的内容介绍了在一个基类中定义虚函数 然后定义派生类的使用情况 那么在C 多继承机制当中 虚函数问题该如何处理呢 见例6 3中的程序 19 例6 3 多继承中虚函数的定义与应用 06 03 cpp includeusingnamespacestd classBase1 public virtualvoidTestA cout Base1TestA endl classBase2 public virtualvoidTestB cout Base2TestB endl 20 classDerived publicBase1 publicBase2 public voidTestA 重写基类Base1中的虚函数TestA coutTestA 调用类Derived的TestA 函数pB2 TestB 调用类Derived的TestB 函数return0 21 DerivedTestA DerivedTestB 程序的运行结果为 22 图6 1多继承与虚函数 23 例6 4 多继承基类中有同名虚函数 06 04 cpp includeusingnamespacestd classBase1 public virtualvoidTest cout Base1Test endl classBase2 public virtualvoidTest cout Base2Test endl 24 classDerived publicBase1 publicBase2 public voidTest 直接重写虚函数Test coutTest 用基类指针pB1调用类Derived的TestA 函数pB2 Test 用基类指针pB2调用类Derived的TestB 函数return0 25 DerivedTest DerivedTest 程序的运行结果为 26 如果按照例6 4的方式 在派生类中直接重写虚函数Test 则2个基类的Test 虚函数都将被覆盖 通过两种基类的指针指向派生类对象并调用虚函数 就只能有一个Test 函数可以调用 而不是像例6 3那样有不同的实现结果 为了实现例6 3中的效果 在派生类Derived中重写不同基类中相同原型的虚函数Test 可以使用下面的方法 不要修改最初的两个基类 而是增加两个中间类 具体实现如下 27 例6 5 多继承基类中有同名虚函数的解决方法 06 05 cpp includeusingnamespacestd 第一个基类Base1classBase1 public virtualvoidTest 虚函数声明 函数实现略 第二个基类Base2classBase2 public virtualvoidTest 虚函数声明 函数实现略 28 定义针对Base1的中间类classMiddleBase1 publicBase1 protected virtualvoidBase1 Test 空的函数体 什么也不做 留在派生类中实现virtualvoidTest 重写虚函数Test Base1 Test 定义针对Base2的中间类classMiddleBase2 publicBase2 private virtualvoidBase2 Test 空的函数体 什么也不做 留在派生类中实现virtualvoidTest 重写虚函数Test Base2 Test 定义最后的派生类 由两个中间类派生而来 29 classDerived publicMiddleBase1 publicMiddleBase2 public 重写从中间类继承下来的虚函数 重写中间类的虚函数Base1 Test 实际上是重写了基类Base1的Test 函数voidBase1 Test coutTest 用基类指针pB1调用类Derived的TestA 函数pB2 Test 用基类指针pB2调用类Derived的TestB 函数return0 30 DerivedTestA DerivedTestB 程序的运行结果为 31 图6 2中间类的作用 32 6 2 3虚析构函数 析构函数的作用是在对象撤销之前做必要的 清理现场 的工作 当派生类的对象从内存中撤销时一般先调用派生类的析构函数 然后再调用基类的析构函数 但是 如果用new运算符动态生成一个派生类的堆对象 并让基类指针指向该派生类对象 当程序用delete运算符通过基类指针删除派生类对象时 会发生一种情况 系统会只执行基类的析构函数 而不执行派生类的析构函数 33 例6 6 基类中有非虚析构函数时的执行情况 06 06 cpp includeusingnamespacestd classBase 定义基类Base public Base Base类构造函数 cout ConstructBase endl Base Base类析构函数 cout DeconstructBase endl 34 classDerived publicBase 定义公有派生类Derived public Derived Derived类构造函数 cout ConstructDerived endl Derived Derived类析构函数 cout DeconstructDerived endl intmain 主函数测试 Base pb 定义基类指针pb newDerived 基类指针指向新生成的派生类堆对象deletepb return1 35 ConstructBase ConstructDerived DestructBase 程序的运行结果为 36 在程序的main 函数中 pb是基类的指针 指向了一个派生类Derived的堆对象 希望用delete释放pb所指向的空间 但运行结果为 DestructBase 表示只执行了基类Base的析构函数 而没有执行派生类Derived的析构函数 如果希望执行派生类Derived的析构函数 则应将基类的析构函数声明为虚析构函数 例如 virtual Base Base类析构函数 cout DeconstructBase endl 程序其他部分不改动 再运行程序 其结果为 DestructDerived DestructBase 37 如果将基类的析构函数声明为虚函数时 由该基类所派生的所有派生类的析构函数也都自动成为虚函数 即使派生类的析构函数与基类的析构函数名字不同 最好把基类的析构函数声明为虚函数 这将使所有派生类的析构函数自动成为虚函数 这样 如果程序中显式地用了delete运算符准备删除一个对象 而delete运算符的操作对象用了指向派生类对象的基类指针 则系统会调用相应类的析构函数 38 虚析构函数的概念和用法很简单 但它在面向对象程序设计中却是很重要的技巧 专业人员一般都习惯声明虚析构函数 即使基类并不需要析构函数 也显式地定义一个函数体为空的虚析构函数 以保证在撤销动态分配空间时能得到正确的处理 构造函数不能声明为虚函数 这是因为在执行构造函数时类对象还未完成建立过程 当然谈不上函数与类对象的绑定 39 6 3纯虚函数和抽象类 6 3 1纯虚函数有时在基类中将某一成员函数声明为虚函数 并不是类本身的要求 而是考虑到派生类的需要 在基类中只定义一个函数名 具体功能留给派生类根据需要去实现 因此可以对这种虚函数只在基类中说明函数原型 用来定义继承体系中的统一接口形式 然后在派生类的虚函数中重新定义具体实现代码 而这种基类中的虚函数就是纯虚函数 其声明一般形式为 virtual函数类型函数名 参数表 0 40 关于纯虚函数 有以下问题需要说明 1 纯虚函数没有函数体 2 最后面的 0 并不表示函数返回值为0 它只起形式上的作用 告诉编译系统 这是纯虚函数 3 这是一个声明语句 后面应有分号 4 纯虚函数只有函数的名字而不具备函数的功能 不能被调用 它只是通知编译器在这时声明一个虚函数 留待派生类中定义 在派生类中对此函数提供定义后 它才能具备函数的功能 可以被调用 如果在一个类中声明了纯虚函数 而在其派生类中没有对该函数定义 则该虚函数在派生类中仍然为纯虚函数 41 例6 7 分析下列程序的输出的结果 06 07 cpp includeusingnamespacestd classVehicle 定义交通工具类 protected intpos speed 定义成员变量位置和速度public Vehicle intps 0 intspd 0 构造函数 pos ps speed spd voidSetSpeed intspd 设置速度值 speed spd voidShow 显示交通工具位置 cout Positionat pos endl virtualvoidRun 0 声明纯虚函数Run 42 classCar publicVehicle 小汽车类 public voidRun 重写虚函数Run pos speed 位置变化 intmain 主函数 Vehicle pvh Carcar pvh 43 Positionat0Positionat5Positionat10 程序的运行结果为 程序分析 该程序在基类Vehicle中定义了一个纯虚函数Run 其形式如下 virtualvoidRun 0 程序中用Vehicle类定义的指针指向了一个Car类的实例car 然后就可以调用纯虚函数Run 从而实现动态绑定的效果 44 6 3 2抽象类 包含有纯虚函数的类是抽象类 由于抽象类常用作基类 通常称为抽象基类 抽象基类的主要作用是 通过它为一个类族建立一个公共的接口 使它们能够更有效地发挥多态特性 抽象基类声明了一族派生类的共同接口 而接口的具体实现代码 即纯虚函数的函数体 要由派生类自己定义 45 抽象类派生出新的类之后 如果派生类给出所有纯虚函数的函数实现 这个派生类就可以定义自己的对象 因而不再是抽象类 反之 如果派生类没有给出全部纯虚函数的实现 这时的派生类仍然是一个抽象类 抽象类不能实例化 即不能定义一个抽象类的对象 但是 可以声明一个抽象类的指针或引用 通过指针或引用 就可以指向并访问派生类对象 进而访问派生类的成员 这种访问是具有多态特征的 用抽象类实现多态性 46 例6 8 设计一个抽象类Shape 用来表示形状的抽象概念 并定义求面积Area 和打印Print 两个纯虚函数 然后设计圆类和矩形两个派生类 各自重写基类中的虚函数 06 08 cpp includeusingnamespacestd classShape protected doublex y public Shape doublea doubleb 构造函数 x a y b virtualdoubleArea 0 求面积函数 声明为虚函数virtualvoidPrint 0 打印输出形状信息 声明为纯虚函数 47 定义圆派生类classCircle publicShape private doubleradius 半径public Circle doubler 0 doublea 0 doubleb 0 doubleArea voidPrint Circle Circle doubler doublea doubleb Shape a b radius r doubleCircle Area 实现求圆形面积 return3 1416 radius radius voidCircle Print 打印输出圆形信息 cout CircleCenter x y Radius radius endl 48 定义矩形派生类classRectangle publicShape private doublewidth height 矩形宽和高public Rectangle doublea 0 doubleb 0 doublew 0 doubleh 0 doubleArea voidPrint Rectangle Rectangle doublea doubleb doublew doubleh Shape a b width w height h doubleRectangle Area 实现求矩形面积 returnwidth height voidRectangle Print 打印输出矩形信息 cout RectanglePosition x y Size width height endl 49 intmain Shape ps1 ps2 定义抽象类的指针变量 Shapes1 5 10 如果不注释该语句 编译程序时将出错Circlec1 10 30 15 圆对象Rectangler1 20 20 100 40 矩形对象ps1 50 CircleCenter 30 15 Radius 10Area 314 16RectanglePosition 20 20 Size 100 40 Area 4000 程序的运行结果为 51 6 4综合应用举例 本节以一个小型的汽车信息处理程序为例 说明虚函数 抽象类应用 有一个ASCII文件存储不同类型汽车信息 普通汽车Car卡车Truck吊车Crane编写三个类实现这三种汽车类 并且设计虚函数Input 读入对应车型的有关信息 然后按照指定的格式输出到屏幕 文件名设为autos txt 文件中的汽车信息如表6 1所示 52 表6 1文件中的汽车信息 53 读入以上文件实例内容后 将以如下格式输出 Style CarManufacturer VolkswagenPassenger 5Style TruckManufacturer GMPassenger 2Load 5Style CraneManufacture FordPassenger 2Load 4Height 20 54 例6 9 汽车信息处理程序 06 09 cpp include includeusingnamespacestd 定义汽车抽象基类classAuto protected stringstypename 类型名intnpassengers 乘客数量stringsmanufacturer 厂商名称public Auto 构造函数 stypename Auto npassengers 0 smanufacturer nomanufacturer 55 virtual Auto 虚析构函数 静态函数TrimLine 用于整理字符串 去掉串尾部换行字符staticvoidTrimLine char sbuf while sbuf 0 if sbuf r sbuf n sbuf 0 break sbuf virtualboolInput FILE fp 0 纯虚函数Input 输入数据virtualvoidShow 0 按照指定的格式输出车的信息 56 普通汽车Car类classCar publicAuto public Car 构造函数 stypename Car 车型名称为Car 重写虚函数Input boolInput FILE fp charsbuf 100 fgets sbuf 100 fp 读入一行字符串 包括换行符 TrimLine sbuf 去掉换行符smanufacturer sbuf 生产厂商字符串fgets sbuf 100 fp 再读一行npassengers atoi sbuf atoi 函数实现把字符串内容转换为整数returntrue 57 重写显示车信息的虚函数Show voidShow cout Style stypename endl cout Manufacturer smanufacturer endl cout Passenger npassengers endl 58 卡车Truck类 从Car类派生classTruck publicCar protected floatfload 载重量public Truck 卡车类构造函数 stypename Truck 车型名称fload 0 重写虚函数Input boolInput FILE fp charsbuf 100 Car Input fp 调用基类的Input 函数fgets sbuf 100 fp 读入一行数据 载重值fload atof sbuf atof 函数实现把字符串内容转换为浮点数returntrue 59 重写显示卡车信息的虚函数Show voidShow Car Show 调用基类的输出函数Show cout Load fload endl 输出卡车的载重值 吊车Crane类 从Car类派生classCrane publicTruck protected floatfheight 吊车的举物高度public Crane stypename Crane fheight 0 60 重写虚函数Input boolInput FILE fp charsbuf 100 Truck Input fp fgets sbuf 100 fp fheight atof sbuf returntrue 重写显示吊车信息的虚函数Show voidShow Truck Show cout Height fheight endl 61 主函数intmain FILE stream 定义文件指针stream fopen autos txt r 以只读方式打开数据文件autos txtif stream NULL 打开文件失败 cout Can topenthefile en

温馨提示

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

评论

0/150

提交评论