第7章 多态性(1)c++_第1页
第7章 多态性(1)c++_第2页
第7章 多态性(1)c++_第3页
第7章 多态性(1)c++_第4页
第7章 多态性(1)c++_第5页
已阅读5页,还剩25页未读 继续免费阅读

下载本文档

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

文档简介

1、C+高级程序设计高级程序设计第7章 多态性第第7 7章章 多态性多态性l 多态性(Polymorphism)是面向对象程序设计的重要特性之一。l 多态是指为一个函数名称关联多种含义的能力,它不仅提高了面向对象软件设计的灵活性,而且使得设计和实现具有良好的可重用性和可扩充性的应用软件成为可能。l 本章主要介绍动态绑定、虚函数、抽象类等重要的概念和实现方法。 7.1 7.1 面向对象编程面向对象编程多态多态 7.2 7.2 虚函数与动态绑定虚函数与动态绑定 7.3 7.3 纯虚函数与抽象类纯虚函数与抽象类7.4 7.4 案例实训案例实训 第第7 7章章 多态性多态性7.1 7.1 面向对象编程面向

2、对象编程多态多态 在面向对象程序设计中,多态性是指同样的消息被不同类型的对象接收时会产生完全不同的行为,即每个对象可以用自己特有的方式响应相同的消息。这里的消息是指对函数的调用,不同的行为是指不同的实现,即执行不同的函数。 从程序实现的角度,多态可分为两类:编译时的多态和运行时的多态。编译时的多态性是通过静态绑定实现的,而运行时的多态性则是在程序运行过程中通过动态绑定实现的。这里的绑定(Binding,又称联编)是指函数调用与执行代码之间关联的过程。 l 静态绑定(Static Binding)是在程序的编译与连接时就已确定函数调用和执行该调用的函数之间的关联。在生成的可执行文件中,函数调用所

3、关联执行的代码是已确定的,因此静态绑定也称为早绑定(Early Binding)。前面介绍的函数重载(含运算符重载)就属于编译时的多态。l 动态绑定(Dynamic Binding)是在程序运行时根据具体情况才能确定函数调用所关联的执行代码,因而也称为晚绑定(Late Binding)。动态绑定所支持的多态性能为程序设计带来良好的灵活性、可重用性和可扩充性。在C+中,通常意义上所说的多态性是指动态多态性。l在C+中,动态多态性的实现方法是在同一个类的继承层次结构中通过定义虚函数虚函数(Virtual Function)实现。 7.1 7.1 面向对象编程面向对象编程多态多态 7.1 7.1 面

4、向对象编程面向对象编程多态多态设计平面与立体几何形处理程序,类的层次结构如图所示。几何形类为基类,其中定义了求面积和体积的成员函数。在派生类中,根据几何形特征,分别重新定义相应函数以正确地求出相应的面积和体积。7.1 7.1 面向对象编程面向对象编程多态多态 在类的继承中,重新定义同名且形参相同的成员函数称为同名覆盖。现假设需要设计一个显示函数,其功能是显示类层次结构中所有类(包含还未定义的派生类)对象的面积和体积等信息,该函数需要能接收类层次结构中的所有类的对象,故函数形参应定义为几何形类的指针(或引用)。由于该函数的形参是基类指针(或引用),从前一章的知识可知,若传递的实参为派生类对象,则

5、函数只能访问几何形类的成员函数而不能访问派生类中的面积和体积函数。 C+的解决方法是将几何形类中的面积和体积函数定义将几何形类中的面积和体积函数定义为虚函数为虚函数,程序在运行时利用多态性能正确地调用与所传递对象对应的计算面积和体积的成员函数。7.1 7.1 面向对象编程面向对象编程多态多态l 在在C+C+中,当通过基类指针中,当通过基类指针( (或引用或引用) )请求调用虚函数时,请求调用虚函数时,C+C+程序会在运行过程中正确地选择与对象关联的派生类中重定程序会在运行过程中正确地选择与对象关联的派生类中重定义的虚函数。义的虚函数。l 几何形类事实上是一个非常抽象的概念,其具体形状未知,几何

6、形类事实上是一个非常抽象的概念,其具体形状未知,面积和体积无法计算,用其定义对象也无实际意义。这种类面积和体积无法计算,用其定义对象也无实际意义。这种类在面向对象程序设计中被称为抽象类,其主要用途是为其他在面向对象程序设计中被称为抽象类,其主要用途是为其他类提供合适的基类。类提供合适的基类。l 在抽象类中通常仅定义一些没有实现的虚函数(接口),而在抽象类中通常仅定义一些没有实现的虚函数(接口),而在其派生类中才实现各自对应的函数。这就是所谓的在其派生类中才实现各自对应的函数。这就是所谓的“单个单个接口,多种方法接口,多种方法”的软件设计思想和技术。的软件设计思想和技术。 类中的成员函数被声明为

7、虚函数后,C+编译器将对虚函数进行特别处理以支持动态绑定。本节在介绍虚函数的基本用法后,着重解析VC+中动态绑定机制的实现方法,旨在从技术层面理解多态性的概念。7.2 7.2 虚函数与动态绑定虚函数与动态绑定7.2.1 虚函数的定义和使用 虚函数的定义方法是用关键字virtual修饰类的成员函数。例如: virtual double area(); 在C+中,不是任何成员函数都能说明为虚函数,虚函数的使用需要注意以下几点:l 在派生类中重定义的虚函数要求函数签名和返回值必须与基类虚函数完全一致,而关键字virtual可以省略。在类的层次结构中,成员函数一旦在某个类中被声明为虚函数,那么在该类之

8、后派生出来的新类中它都是虚函数。l 虚函数不能是友元函数或静态成员函数。7.2 7.2 虚函数与动态绑定虚函数与动态绑定7.2 7.2 虚函数与动态绑定虚函数与动态绑定l 构造函数不能是虚函数,而析构函数可以是虚函数。l 基类的虚函数在派生类中可以不重新定义。若在派生类中没有重新改写基类的虚函数,则调用的仍然是基类的虚函数。l 通过类的对象调用虚函数仅属于正常的成员函数调用,调用关系是在编译时确定的,属于静态绑定。动态绑定(动态多态性)仅发生在使用基类指针或基类引用调用虚函数的过程中。【例7-1】设计动物类及其派生类,并定义虚函数显示每种动物爱吃的食物。 7.2 7.2 虚函数与动态绑定虚函数

9、与动态绑定程序说明:(1)Animal基类中定义了eat()虚函数,用于显示动物爱吃的食物,成员函数getName()用于返回动物名称,构造函数用于初始化对象。除Poultry类没有重定义eat()函数外,其余派生类均重新定义了该虚函数。 从运行结果可知,Poultry派生类没有自己的eat()函数,则继承了基类函数,而Monkey、Panda等派生类对象均调用了自己的虚成员函数eat()。 (2)show()函数的形参是指向Animal类的指针ptr,函数体中通过指针调用getName()和eat()函数。getName()是Animal类的成员函数,属于常规访问。 由于eat()是虚函数,

10、ptr-eat()语句能根据ptr所指向的对象类型正确地调用对应函数,而若eat()不是虚函数,则ptr指针只能访问Animal的eat()函数。7.2 7.2 虚函数与动态绑定虚函数与动态绑定(3)指针数组Animal *ptrArray6;用于保存基类或派生类对象的地址,之后的6条语句是在自由存储区产生6个不同类的对象并存储它们的首地址于数组中。(4)如果在程序中定义下列函数,在主程序中定义对象并传递给该函数,则同样也能正确调用虚函数,实现多态: void show(Animal &ref) cout ref.getName() ,; ref.eat(); Animal类的指针或引

11、用能根据所指向或引用的对象正确地调用虚函数。 7.2 7.2 虚函数与动态绑定虚函数与动态绑定 7.2.2 VC+动态绑定的实现机制 C+语言标准并没有规定动态绑定的实现方法,本节主要介绍VC+在内部是怎样实现虚函数、多态和动态绑定。下面通过跟踪和分析例7-1来剖析VC+实现动态绑定的方法。 在VC+中,多态是通过3个层次的指针(即“三层间接访问”)实现的。为便于对比和分析,在Animal类中添加显示动物寿命的虚成员函数lifeSpan(),如下: virtual void lifeSpan() cout 寿命大致在年到年之间! endl; 7.2 7.2 虚函数与动态绑定虚函数与动态绑定 在

12、主函数中定义对象Monkey myObj。以跟踪方式运行例7-1,监视窗口中如图7-2所示。7.2 7.2 虚函数与动态绑定虚函数与动态绑定l 图7-2中显示了程序运行时ptrArray0、ptrArray3、ptrArray4这3个Animal指针所指对象和myObj对象的存储信息。l 所有对象都拥有一个名称为_vfptr的指针(称为虚函数表指针),其中ptrArray0和myObj的_vfptr完全相同。图7-2中,不同类对象的_vfptr分别指向了Monkey类、Duck类和Poultry类的虚函数表,表名均为vftable。l Monkey类的虚函数表中有两个函数指针,分别指向Monk

13、ey:eat()和Animal:lifespan(),Duck类的虚函数表中的两个函数指针分别指向Duck:eat()和Animal:lifespan(),lifeSpan类的虚函数表中的两个函数指针分别指向Animal:eat()和Animal:lifespan()。7.2 7.2 虚函数与动态绑定虚函数与动态绑定 VC+处理动态绑定的基本方法是:编译器为拥有虚函数的类创建一个虚函数表,在对象中封装_vfptr指针,用于指向类的虚函数表vftable。虚函数表中存储了该类所拥有的虚函数的入口地址,即函数指针。如果派生类重新定义了基类的虚函数,那么虚函数表中保存的是指向该类虚函数的指针,否则保

14、存的是其父类的对应虚函数指针。例如,Poultry类由于没有重定义虚函数,其虚函数表中保存的是基类Animal的虚函数指针。调用哪个虚函数决定于所访问的虚函数表中所记录的虚函数指针。7.2 7.2 虚函数与动态绑定虚函数与动态绑定 多态的实现涉及到3个层次的指针,如图7-3所示。第1层次指针是虚函数表vftable中的函数指针,它们指向虚函数被调用时的实际函数。第2层次指针是对象中封装的_vfptr指针,其中存储了类的虚函数表的入口地址。第3层次是对象指针(也可以是引用),以间接方式访问对象。该指针通常是类层次结构中基类的指针,可以指向派生类的所有对象。7.2 7.2 虚函数与动态绑定虚函数与

15、动态绑定7.2.3 虚析构函数 类的构造函数不能声明为虚函数。从派生类对象创建的角度,对象总是要先构造对象中的基类部分,然后才构造派生类部分。构造函数的访问顺序是:先调用基类的构造函数,后调用派生类自身的构造函数。如果构造函数设为虚函数,那么派生类对象在构建时将直接调用派生类构造函数,而父类的构造函数就不得不显式地调用。 7.2 7.2 虚函数与动态绑定虚函数与动态绑定 对于基类包含虚函数的类,其析构函数往往需要声明为虚函数。这是因为多态性常常是通过指向派生类对象的基类指针而实现。如果基类指针指向的是自由存储区中派生类的对象,此时需要用delete语句释放空间。由于基类指针只能访问基类中的非虚

16、成员函数,所以对象在撤消时只调用了基类的非虚析构函数,而派生类的析构函数没有被调用。 在基类中定义其析构函数是虚函数,其所有派生类中的析构函数将都是虚函数,尽管它们的名称并不相同。如果对一个基类指针应用delete运算符显式地销毁其类层次结构中的一个对象,则系统会依次调用派生类和基类的虚析构函数撤消各自创建的对象。7.2 7.2 虚函数与动态绑定虚函数与动态绑定【例7-2】虚析构函数应用示例。程序说明:l 如果去除源程序中Base类构造函数前的virtual关键字,则程序运行结果中将缺少第4行“Derived类的析构函数被调用”,即仅调用了基类的非虚析构函数。l 若修改Base *basePt

17、r指针为Derived *basePtr(改为派生类指针),则无论析构函数是否为虚函数,派生类和基类的析构函数均被调用。但这种用法不具有多态性。 7.3 7.3 纯虚函数与抽象类纯虚函数与抽象类 C+语言允许类中虚函数在声明时直接指定“=0”,说明该函数不提供具体的实现,这种虚函数称为纯虚函数(Pure Virtual Function)。纯虚函数的声明格式如下: virtual 函数名()=0; 含一个或多个纯虚函数的类称为抽象类(Abstract Class)。由于纯虚函数没有具体的函数体,用抽象类定义对象是无实际意义的,因而用含有纯虚函数的抽象类定义对象,C+编译器将报错。7.3 7.3

18、 纯虚函数与抽象类纯虚函数与抽象类 在几何形类结构中,几何形是一个抽象的概念,对于一个不知具体形状的几何形,我们是无法计算其面积或体积的。通常我们将几何形类定义为抽象类,用它作为具体类的基类,即以其为基础派生出各种具体的几何形类(如圆类、三角形类、长方体类等)。几何形类中定义的纯虚函数(如求面积、求体积)在派生类中被定义,并根据具体几何形的特征编写相应的虚函数实现。【例7-3】纯虚函数与抽象类示例。以几何形类为抽象基类,派生圆、矩形、圆柱等类,计算各种几何形的面积和体积。7.3 7.3 纯虚函数与抽象类纯虚函数与抽象类程序说明:l 抽象类Shape中声明了4个纯虚函数,在派生类中对它们分别进行

19、了定义。在主函数中,定义了一个基类指针Shape *ptr,该指针在程序运行时可指向任何派生类的对象,并用一致的方法ptr-input(); ptr-output();实现不同几何形对象的数据输入和结果输出。体现出“单个接口,多种方法”的软件设计思想。l menu()函数为用户提供了操作软件的界面。l 读者不妨在例程的基础上,派生三棱柱、球等几何体,体验多态性所带来的程序容易扩展的优点。7.3 7.3 纯虚函数与抽象类纯虚函数与抽象类【例7-4】用梯形法求函数的定积分。l 分析: 函数f(x)在闭区间a, b上的定积分的几何意义是曲线f(x)、x轴、直线f(a)和f(b)所围成的曲边梯形的面积。梯形法求定积分的方法是将区间a, b等分成若干个小区间,在小区间上用小梯形的面积代替曲边梯形的面积,如图7-4所示。当小区间的个数足够多时,小梯形面积之和为函数f(x)在a, b上定积分的近似值。7.3 7.3 纯虚函数与抽象类纯虚函数与抽象类程序说明:l Trapezium类中定义了纯虚函数virtual double fun(double x) const=0;,因此该类为抽象类。在派生类中只要用相应的被积函数实现虚函数fun,则通过基类的积分计算函数double Integerate()能算出相应函数的定积分的值。l 本例的显示结果并不理想,建议读者

温馨提示

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

评论

0/150

提交评论