第14章多态性.ppt_第1页
第14章多态性.ppt_第2页
第14章多态性.ppt_第3页
第14章多态性.ppt_第4页
第14章多态性.ppt_第5页
已阅读5页,还剩42页未读, 继续免费阅读

下载本文档

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

文档简介

1、第14章 多态性,南开大学非计算机专业理工科 面向对象程序设计课程,2,课程内容安排(2课时),多态性的概念 虚函数 抽象类 应用实例 小结 学习指导,3,14.1 多态性的概念,在介绍如何在程序中实现多态之前,我们先给出一个实例来理解什么是多态。 【例14-1】未实现多态性的程序。 #include using namespace std; class Shape public: void Draw() coutDraw shapeendl; ; class Circle : public Shape public: void Draw() coutDraw circleendl; ;,4,

2、14.1 多态性的概念,class Triangle : public Shape public: void Draw() coutDraw triangleendl; ; class Rectangle : public Shape public: void Draw() coutDraw rectangleendl; ; void func(Shape ,5,14.1 多态性的概念,int main() Circle c; Triangle t; Rectangle r; func(c); func(t); func(r); return 0; 运行结果为: Draw shape Draw

3、 shape Draw shape,6,14.1 多态性的概念,显然,我们并不希望看到这样的结果。我们希望当执行func(c)时调用Circle类的Draw()函数,当执行func(t)时调用Triangle类的Draw()函数,当执行func(r)时调用Rectangle类的Draw()函数。这种能够根据对象的实际类型来调用该对象所属类的函数、而不是每次都调用基类中函数的特性,就是本章所要介绍的多态性。,7,14.2 虚函数,14.2.1 先期绑定和动态绑定 传统的面向结构的编译器所产生的函数调用,采用“先期绑定”的方式。所谓“绑定”就是建立函数调用和函数本体的关联。如果绑定发生于程序运行之

4、前(由编译器和连接器完成),则称为“先期绑定”。 面向对象程序设计语言采用“后期绑定”,也可叫“动态绑定”技术,即当调用某个对象的函数时,应该被执行的程序代码会根据对象的具体类型在执行期被确定下来。这是实现多态性的技术保证。 想要实现多态,就要进行“后期绑定”,在C+中,实现“后期绑定”的机制是虚函数。,8,14.2 虚函数,14.2.2 虚函数的工作方式,9,14.2 虚函数,在图14-1所示形状的类层次中, Circle类、Triangle类、Rectangle类都是从基类Shape派生出来的。每个类都有它自己的Draw()函数,并且绘制每种形状的Draw()函数都大不相同。当需要绘制形状

5、时,把各派生类对象统一作为基类Shape的对象处理是一种很好的方法。只需要简单地调用基类Shape的Draw()函数,而不必关心对象的实际类型,程序会动态地确定(即在执行时确定)调用哪个派生类的Draw()函数。为了使这种行为可行,把基类中的函数Draw()声明为虚函数,然后在每个派生类中重新定义Draw()使之能够绘制合适的形状。 虚函数的声明方法是在基类的函数原型前加上关键字virtual。,10,14.2 虚函数,【例14-2】将例14-1进行简单的修改,在基类的Draw()函数前加上关键字virtual #include using namespace std; class Shape

6、 public: virtual void Draw() /将Draw()函数声明为虚函数 coutDraw shapeendl; ;,11,14.2 虚函数,class Circle : public Shape public: virtual void Draw() /此处的virtual可以没有,不影响Draw()的虚函数性质 coutDraw circleendl; ; class Triangle : public Shape public: void Draw() coutDraw triangleendl; ;,12,14.2 虚函数,class Rectangle : publ

7、ic Shape public: void Draw() coutDraw rectangleendl; ; void drawShape(Shape ,运行结果为: Draw circle Draw triangle Draw rectangle,13,14.2 虚函数,提示: Draw()函数在基类Shape中声明为virtual,该虚函数的性质自动地向下带给其派生类,所以派生类中可以省略virtual关键字。虽然如此,但有时为了提高程序的可读性可以在每一层中显式地声明这些虚函数。 只有将派生类对象赋给基类对象引用或将派生类对象地址赋给基类对象指针时,才能够表现出多态性。如果将派生类对象赋

8、给基类对象,那么通过基类对象必然是调用基类中的函数。,14,14.2 虚函数,有了虚函数,以后如果要求drawShape()增加一个绘制新形状的功能,只要简单的从Shape类派生新类就可以了,而drawShape()函数不需要做任何改动。例如,增加一个五边形类: class Patagon : public Shape public: void Draw() coutDraw patagonendl; ; 只要新增加的形状是由Shape类派生出来的,则drawShape()函数不需要做任何修改,在主函数中如果出现如下语句,就会调用Patagon类对象的Draw()函数。 int main()

9、Patagon p; drawShape(p); return 0; 可见,多态性使应用程序代码大大简化,使程序扩充变得更加简单。,15,14.2 虚函数,14.2.3 虚析构函数 通过上面的介绍,我们已经了解了多态性和虚函数的工作方式。我们再来看一个例子: 【例14-3】Employee类及其派生类Manager。 #include using namespace std; class Employee public: Employee(char *pName,long no) m_name=new charstrlen(pName)+1; strcpy(m_name,pName); m_n

10、umber=no; ,16,14.2 虚函数,Employee() coutEmployee destructed!endl; delete m_name; virtual void DispInfo() coutName is m_name; cout, No. is m_numberendl; protected: char *m_name; long m_number; ;,17,14.2 虚函数,class Manager:public Employee public: Manager(char *pName,long no, char *dept):Employee(pName,no

11、) m_department=new charstrlen(dept)+1; strcpy(m_department,dept); Manager() coutManager destructed!endl; delete m_department; ,18,14.2 虚函数,virtual void DispInfo() coutDispInfo();/ 通过基类指针,调用派生类相应虚函数 delete pe;/ 释放指针pe所指向的地址 return 0; ,19,14.2 虚函数,在Employee类和Manager类中,都包含有指针类型的成员,应该利用析构函数显示地释放这些指针所指向的

12、内存空间。主函数中创建的是派生类Manager的对象,所以应该调用Manager类的析构函数释放空间。但是,通过观察运行结果,我们可以看到,被调用的是基类Employee的析构函数,也就是说,Manager类对象中,只有属于基类Employee的那部分指针成员被释放了,而Manager类中新添加的指针成员没有被释放,这样就会导致内存泄露问题。 要解决上述问题,可以将基类中的析构函数声明为虚析构函数。这样就会使所有派生类的析构函数自动成为虚析构函数(虽然它们与基类析构函数名不同)。这时,如果像上面那样使用delete运算符时,系统会根据对象的实际类型调用相应类的析构函数。,20,14.2 虚函数

13、,【例14-4】将例14-3中的析构函数声明为虚析构函数。 virtual Employee()/ 将析构函数声明为虚析构函数 coutEmployee destructed!endl; delete m_name; 运行结果为: Name is Alice, No. is 2, Department is Market Employee destructed! Manager destructed! 提示: 在调用派生类对象的析构函数后,会自动调用基类对象的析构函数来释放基类中定义的指针成员。,21,14.2 虚函数,14.2.4 错误地使用虚函 如果在基类中的虚函数跟子类中的函数只是名字相

14、同,而参数不同,或者返回类型不同,即使写上了virtual关键字,也不进行“后期绑定”。 【例14-5】下面程序中,基类Based的成员函数fn()和派生类SubClass的成员函数fn()参数类型不同。 #include using namespace std; class Base public: virtual void fn(int x) cout In Base class, int x = x endl; ;,22,14.2 虚函数,class SubClass :public Base public: virtual void fn(float x) cout In SubCla

15、ss, float x = x endl; ; void test(Base ,23,14.2 虚函数,int main() Base bc; SubClass sc; cout Calling test(bc)n; test(bc); cout Calling test(sc)n; test(sc); return 0; ,运行结果为: Calling test(bc) In Base class, int x =1 In Base class, int x =2 Calling test(sc) In Base class, int x =1 In Base class, int x =2

16、,24,14.2 虚函数,关于虚函数的几点说明: 只有类的成员函数才能声明为虚函数。因为虚函数只适用于有继承关系的类对象,所以普通函数不能声明为虚函数。 静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象。 内联函数不能是虚函数,因为内联函数不能在运行中确定其位置。即使虚函数放在类的内部定义,编译时,仍将其看作是非内联的。 构造函数不能是虚函数。因为构造时,对象还是一片未定型的空间。只有在构造完成后,对象才能成为一个类的名副其实的实例。,25,14.3 抽象类,对于有些类来说,它仅仅表示一个概念,将其实例化没有任何意义。比如,图14-1的Shape类中有Draw方法,但不同的形状其绘制

17、方法各不相同,只有指定具体的形状才有可能实现Draw方法,因此,Shape类中的Draw方法根本无法实现,从而创建Shape类对象也就没有任何意义。Shape类的唯一用途就是定义每一个形状所共有的那些属性和方法,并作为其他形状类(如Circle类、Rectangle类、Triangle类)的基类。 通常将这种不需要实例化的类定义成“抽象类”。抽象类不能实例化为对象,它的唯一用途是为其他类提供合适的基类,其他类可从它这里继承和(或)实现接口。与“抽象类”相对应,前面所定义的类都是“具体类”,具体类可以实例化为对象。例如,可以建立抽象基类Shape,然后从它派生出具体类Circle、Rectang

18、le和Triangle等。 一个类如果是抽象类,则该类中至少有一个成员函数是纯虚函数,纯虚函数就是在声明时初始化为0的虚函数。,26,14.3 抽象类,【例14-6】将例14-2进行简单的修改,将基类Shape的Draw()函数定义为纯虚函数,从而使Shape类是一个抽象类。 #include using namespace std; class Shape public: virtual void Draw()=0;/将Draw()函数声明为纯虚函数 ; class Circle : public Shape public: virtual void Draw() coutDraw circ

19、leendl; ;,27,14.3 抽象类,class Triangle : public Shape public: void Draw() coutDraw triangleendl; ; class Rectangle : public Shape public: void Draw() coutDraw rectangleendl; ; void drawShape(Shape ,28,14.3 抽象类,int main() /Shape s;/ 错误。抽象类不能实例化为对象 Circle c; Triangle t; Rectangle r; drawShape (c); drawS

20、hape (t); drawShape (r); return 0; ,29,14.3 抽象类,上面代码中,在Shape类内定义了一个纯虚函数Draw(),则Shape类成为抽象类。此时,如果试图通过“Shape s;”实例化Shape类对象,在编译时系统就会给出错误信息。 如果某个类是从一个带有纯虚函数的类派生出来的,并且没有在该派生类中提供该纯虚函数的定义,则该虚函数在派生类中仍然是纯虚函数,因而该派生类也是一个抽象类。比如,定义Shape类的派生类TwoDimensionShape类: class TwoDimensionShape : public Shape ; TwoDimensi

21、onShape类并没有对Shape类中的纯虚函数Draw()进行定义,因此,TwoDimensionShape类也是一个抽象类。 一个类层次结构中可以不包含任何抽象类,但是很多良好的面向对象的系统,其类层次结构的顶部是一个抽象基类。在有些情况中,类层次结构顶部有好几层都是抽象类。,30,14.4 应用实例,【例14-7】下面的范例要显示Shape、Point、Circle、Cylinder类的层次结构。 类层次分析: 这里类的层次结构的顶层是抽象基类Shape。Shape类中有一个纯虚函数PrintShapeName()和Print(),所以它是一个抽象基类。类Shape中还包含其他两个虚函数

22、Area()和Volume(),它们都有默认的实现(返回0值)。类Point从Shape类中继承了这两个函数的实现,由于点的面积和体积是0,所以这种继承是合理的。Circle类从Point类中继承了函数Volume(),但Circle本身提供了函数Area()的实现。Cylinder对函数Area()和Volume()提供了自己的实现。 注意: 尽管Shape是一个抽象基类,但是仍然可以包含某些成员函数的实现,并且这些实现是可继承的。Shape类以四个虚函数的形式提供了一个可继承的接口)类层次结构中的所有类都将包含这些虚函数)。,31,14.4 应用实例,/Shape.h #ifndef SH

23、APE_H #define SHAPE_H class Shape public: virtual double Area() return 0.0; virtual double Volume() return 0.0; / 纯虚函数 virtual void PrintShapeName() = 0; virtual void Print() = 0; ; #endif,32,14.4 应用实例,/Point.h #ifndef POINT_H #define POINT_H #include using namespace std; #include shape.h class Poin

24、t : public Shape public: Point( int = 0, int = 0 ); / 缺省构造函数 void SetPoint( int, int ); int GetX() const return m_x; int GetY() const return m_y; virtual void PrintShapeName() cout Point: ; virtual void Print(); private: int m_x, m_y; / 点的x,y坐标 ; #endif,33,14.4 应用实例,/point.cpp #include point.h #incl

25、ude using namespace std; Point:Point( int x, int y ) SetPoint( x, y ); void Point:SetPoint( int x, int y ) m_x = x; m_y = y; void Point:Print() cout ( m_x , m_y ); ,34,14.4 应用实例,/Circle.h #ifndef CIRCLE_H #define CIRCLE_H #include point.h class Circle : public Point public: Circle( double r = 0.0, i

26、nt x = 0, int y = 0 ); void SetRadius( double ); double GetRadius(); virtual double Area(); virtual void PrintShapeName() cout Circle: ; virtual void Print(); private: double m_radius; / 圆的半径 ; #endif,35,14.4 应用实例,/Circle.cpp #include using namespace std; #include circle.h Circle:Circle( double r, i

27、nt a, int b ) : Point( a, b ) / 调用基类构造函数 SetRadius( r ); void Circle:SetRadius( double r ) m_radius = r 0 ? r : 0; ,36,14.4 应用实例,double Circle:GetRadius() return m_radius; double Circle:Area() return 3.14159 * m_radius * m_radius; void Circle:Print() Point:Print(); cout ; Radius = m_radius; ,37,14.4

28、 应用实例,/Cylinder.h #ifndef CYLINDER_H #define CYLINDER_H #include circle.h class Cylinder : public Circle public: Cylinder( double h = 0.0, double r = 0.0, int x = 0, int y = 0 ); void SetHeight( double ); double GetHeight(); virtual double Area(); virtual double Volume(); virtual void PrintShapeName

29、()cout Cylinder: ; virtual void Print(); private: double m_height; / 圆柱体的高 ; #endif,38,14.4 应用实例,/Cylinder.cpp #include using namespace std; #include cylinder.h Cylinder:Cylinder( double h, double r, int x, int y ): Circle( r, x, y ) / 调用基类构造函数 SetHeight( h ); void Cylinder:SetHeight( double h ) m_h

30、eight = h 0 ? h : 0; double Cylinder:GetHeight() return m_height; ,39,14.4 应用实例,double Cylinder:Area() / 圆柱体表面积 return 2 * Circle:Area() + 2 * 3.14159 *GetRadius() * m_height; double Cylinder:Volume() return Circle:Area() * m_height; void Cylinder:Print() Circle:Print(); cout ; Height = m_height; ,4

31、0,14.4 应用实例,/ch14_7.cpp #include #include #include shape.h #include point.h #include circle.h #include cylinder.h void virtualViaPointer( Shape * ); void virtualViaReference( Shape ,41,14.4 应用实例,circle.PrintShapeName(); / 前期绑定 circle.Print(); / 前期绑定 cout n; cylinder.PrintShapeName();/ 前期绑定 cylinder.

32、Print(); / 前期绑定 cout nn; Shape *arrayOfShapes3; / 基类的数组 / arrayOfShapes0指向Point对象 arrayOfShapes0 = ,42,14.4 应用实例,/ arrayOfShapes2指向Cylinder对象 arrayOfShapes2 = ,43,14.4 应用实例,void virtualViaPointer( Shape *baseClassPtr ) baseClassPtr-PrintShapeName(); baseClassPtr-Print(); cout Area() Volume() nn; void

温馨提示

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

评论

0/150

提交评论