《面向对象程序设计》第6章_第1页
《面向对象程序设计》第6章_第2页
《面向对象程序设计》第6章_第3页
《面向对象程序设计》第6章_第4页
《面向对象程序设计》第6章_第5页
已阅读5页,还剩47页未读 继续免费阅读

下载本文档

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

文档简介

1、面向对象程序设计面向对象程序设计0Date: 2022年5月9日星期一临沂大学汽车学院:韩晓翠临沂大学汽车学院:韩晓翠第第6 6章章 多态性与虚函数多态性与虚函数面向对象程序设计面向对象程序设计1Date: 2022年5月9日星期一第第6章章 多态性与虚函数多态性与虚函数6.1 6.1 多态性的概念多态性的概念6.2 6.2 一个典型的例子一个典型的例子6.3 6.3 虚函数虚函数6.4 6.4 纯虚函数与抽象类纯虚函数与抽象类要点要点面向对象程序设计面向对象程序设计2Date: 2022年5月9日星期一学习要点学习要点 多态性的多态性的概念概念与与分类分类 典型典型示例示例讨论讨论学以致用学

2、以致用面向对象程序设计面向对象程序设计3Date: 2022年5月9日星期一学习目标学习目标 掌握多态性的概念与分类掌握多态性的概念与分类 改写示例程序,完成学以致用题目改写示例程序,完成学以致用题目讨论讨论学以致用学以致用面向对象程序设计面向对象程序设计4Date: 2022年5月9日星期一问题问题封装性和继承性实现的是代码重用。如何实现接口重用,即一个接口,多种方法?利用多态性什么是多态性?什么是多态性?面向对象程序设计面向对象程序设计5Date: 2022年5月9日星期一6.1 6.1 多态性的概念多态性的概念什么叫多态?什么叫多态?多态的意思是多态的意思是一种事物的多种形态一种事物的多

3、种形态。在在C+C+中,中,是指具有不同功能的函数可是指具有不同功能的函数可以用同一个函数名以用同一个函数名。面向对象方法面向对象方法中对多态性的描述:中对多态性的描述:向不向不同的对象发送同一个同的对象发送同一个消息消息,不同的对象,不同的对象在接收时会产生不同的在接收时会产生不同的行为行为(即即方法方法)。面向对象程序设计面向对象程序设计6Date: 2022年5月9日星期一 多态性分类多态性分类从系统实现的角度看,多态性分为从系统实现的角度看,多态性分为两类两类:静态多态性静态多态性:又称:又称编译时的多态性编译时的多态性。 如函数重载和运算符重载。如函数重载和运算符重载。动态多态性动态

4、多态性:又称为:又称为运行时的多态性运行时的多态性。 它主要表现为虚函数它主要表现为虚函数( ( virtual functionvirtual function ) )。面向对象程序设计面向对象程序设计7Date: 2022年5月9日星期一6.2 6.2 一个典型的例子一个典型的例子一个综合性的示例。一个综合性的示例。先建立一个先建立一个point ( 点点 )类,包含类,包含数据成员数据成员x,y ( 坐标坐标点点 )。 以它为以它为基类基类,派生出一个,派生出一个circle ( 圆圆 )类,增加类,增加数据数据成员成员 r 半径。半径。 再以再以circle 类为直接基类,派生出类为直

5、接基类,派生出cylinder ( 圆柱圆柱体体 )类,增加类,增加数据成员数据成员 h ( 高高 )。 要求要求重载运算符重载运算符“”,使之能输出这些,使之能输出这些对象。对象。面向对象程序设计面向对象程序设计8Date: 2022年5月9日星期一这个题目程序较长,分为若干步骤这个题目程序较长,分为若干步骤进行,分步调试:进行,分步调试:(1)先声明基类先声明基类point类,并调试之;类,并调试之;(2)再声明派生类再声明派生类circle类,调试之;类,调试之;(3)最后声明最后声明cylinder类,调试之。类,调试之。P6_1_1.CPPP6_1_2.CPPP6_1_3.CPP面向

6、对象程序设计面向对象程序设计9Date: 2022年5月9日星期一(1)先声明基类先声明基类point#include class point /声明类声明类pointpublic: point (float a, float b ); /构造函数构造函数 void setPoint (float, float); /设置点坐标设置点坐标 float getX( ) const return x; / 读读x 值值 float getY( ) const return y; / 读读y 值值 friend ostream& operator( ostream&,const po

7、int &);protected: float x,y;/ 下面定义下面定义 point类的成员函数类的成员函数point : point (float a, float b) / point的构造函数的构造函数 x = a; y = b; void point : setPoint (float a, float b) x =a; y = b; ostream &operator(ostream &output, const point &p) / 重载运算符重载运算符 output x= p.x, y= p.y endl; return output;int

8、 main( ) point p(3.5,6.4); coutpendl; p.setPoint(8.5,6.8); coutpendl; return 0;P6_1_1.CPP面向对象程序设计面向对象程序设计10Date: 2022年5月9日星期一学生模仿练习学生模仿练习建立一个建立一个RectangleRectangle( (长方形长方形) )类,包含类,包含数据成员数据成员len,wlen,w(长和宽)、(长和宽)、成员函数成员函数area( )area( )计算长方形计算长方形面积。面积。( (Lx6_1_1.cppLx6_1_1.cpp) )以以RectangleRectangle为

9、基类为基类, ,派生出一个派生出一个BoxBox( (长方体长方体) )类,类,增加增加数据成员数据成员h h( (高高) )、成员函数成员函数volume( )volume( )计算长计算长方体的体积。方体的体积。要求:要求:重载运算符重载运算符“” ,使之能输出这些对象。,使之能输出这些对象。面向对象程序设计面向对象程序设计11Date: 2022年5月9日星期一(2)再声明派生类再声明派生类circlecircle类类class circle : public point /声明类声明类citclepublic: circle (float x=0, float y=0, float r

10、=0); /构造构造函数函数 void setRadius ( float); /设置圆半径设置圆半径 float getRadius ( ) const return radius; / 读读 r值值 float area ( ) const; / 计算圆面积计算圆面积 friend ostream & operator ( ostream &, const circle &);protected: float radius;/ 下面定义下面定义 circle类的成员函数类的成员函数circle : circle (float x, float y, float r)

11、 : point (a,b),radius(r) / point的构造函数的构造函数void circle : setRadius (float r) radius = r; float circle : area( ) const / 计算圆面积计算圆面积 return 3.14159*radius*radius; ostream &operator (ostream &output, const circle &c) / 重载运算符重载运算符 output x= c.x , y= c.y , r= c.radius , area= c.area( )endl; re

12、turn output;int main( ) circle c(1.1,2.2,1.0); c.setRadius(2.0); c.setPoint(2.0, 2.0 ); coutcendl; return 0;P6_1_2.CPP面向对象程序设计面向对象程序设计12Date: 2022年5月9日星期一学生模仿练习学生模仿练习建立一个建立一个RectangleRectangle( (长方形长方形) )类,包含类,包含数据成员数据成员len,wlen,w(长和宽)、(长和宽)、成员函数成员函数area( )area( )计算长方形计算长方形面积。面积。( (Lx6_1_1.cppLx6_1_

13、1.cpp) )以以RectangleRectangle为基类为基类, ,派生出一个派生出一个BoxBox( (长方体长方体) )类,类,增加增加数据成员数据成员h h( (高高) )、成员函数成员函数volume( )volume( )计算长计算长方体的体积。方体的体积。要求:要求:重载运算符重载运算符“” ,使之能输出这些对象。,使之能输出这些对象。面向对象程序设计面向对象程序设计13Date: 2022年5月9日星期一P6_1_3.CPP(3)最后声明最后声明cylindercylinder类类class cylinder : public circle /声明类声明类cylinderp

14、ublic: cylinder (float x=0, float y=0, float r=0, float h=0 ); /构造函数构造函数 void setHeight ( float h) height = h; /设置圆柱体高设置圆柱体高 float getHeight ( ) const return height; / 读读 r值值 float area ( ) const; / 计算圆柱表面积计算圆柱表面积 float volume ( ) const; /求体积求体积 friend ostream & operator ( ostream &, const c

15、ylinder &);protected: float height;/ 下面定义下面定义 cylinder类的成员函数类的成员函数cylinder : cylinder (float a, float b, float r,float h) : circle (a,b,r), height (h) / point的构造函数的构造函数float cylinder : area( ) const / 计算圆柱表面积计算圆柱表面积 return 2* circle : area( )+2*3.14159*radius*height; float cylinder : volume( ) c

16、onst return circle:area( )*height; / 求圆柱体积求圆柱体积ostream &operator (ostream &output, const cylinder &cy) / 重载运算符重载运算符 output x= cy.x , y= cy.y , r= cy.radius , height= cy.height , area= cy.area( ) ,volume= cy.volume( )endl; return output;面向对象程序设计面向对象程序设计14Date: 2022年5月9日星期一写出主函数写出主函数int ma

17、in( ) cylinder cy1(3.5, 6.4, 5.2, 10 ); cout original cylinder: n cy1; cy1.setHeight(15); cy1.setRadius(7.5); cy1.setPoint(5, 5); cout new cylinder: n cy1; point &pRef = cy1; cout n pRef as a point: pRef; circle &cRef =cy1; cout n cRef as a circle: cRef; return 0;P6_1_4.CPP面向对象程序设计面向对象程序设计15

18、Date: 2022年5月9日星期一本例中存在本例中存在静态多态性静态多态性,即运算,即运算符重载,注意,符重载,注意,三个运算符函数是重三个运算符函数是重载载,不是同名覆盖不是同名覆盖,因为参数不一样。,因为参数不一样。这种这种多态性多态性,编译系统就可以判定应编译系统就可以判定应调用哪个重载运算符函数调用哪个重载运算符函数。示例小结示例小结面向对象程序设计面向对象程序设计16Date: 2022年5月9日星期一学以致用学以致用 改写示例程序:在例改写示例程序:在例6.1基础上作一些修改。定义基础上作一些修改。定义Point(点点)类类,由,由Point类派生出类派生出Circle(圆圆)类

19、类,再由,再由Circle类派生出类派生出Cylinder(圆柱体圆柱体)类类。将类的定义部。将类的定义部分分别作为分分别作为3个头文件,对它们的成员函数的声明部个头文件,对它们的成员函数的声明部分分别作为分分别作为3个源文件个源文件(.cpp文件文件),在主函数中用,在主函数中用#include命令把它们包含进来,形成一个完整的程序,命令把它们包含进来,形成一个完整的程序,并上机运行。并上机运行。面向对象程序设计面向对象程序设计17Date: 2022年5月9日星期一讨论并总结讨论并总结 静态多态性与动态多态性的主要区别静态多态性与动态多态性的主要区别面向对象程序设计面向对象程序设计18Da

20、te: 2022年5月9日星期一结论结论静态多态性与动态多态性的主要区别静态多态性与动态多态性的主要区别 静态多态性指的是程序在编译时,系统就能静态多态性指的是程序在编译时,系统就能决定调用哪个函数,如重载。决定调用哪个函数,如重载。 动态多态性指在运行中才能动态确定操作指动态多态性指在运行中才能动态确定操作指针所指的对象,主要通过虚函数来实现。针所指的对象,主要通过虚函数来实现。面向对象程序设计面向对象程序设计19Date: 2022年5月9日星期一课外训练课外训练 改写模仿练习程序:在模仿练习的基础上作一些修改。改写模仿练习程序:在模仿练习的基础上作一些修改。定义定义RectangleRe

21、ctangle( (长方形长方形) )类类,Rectangle(长方形长方形)类派生类派生出出BoxBox (长方体长方体)类类,再由再由Circle类派生出类派生出Cylinder(圆柱体圆柱体)类类。将类的定义部分分别作为。将类的定义部分分别作为2个头个头文件,对它们的成员函数的声明部分分别作为文件,对它们的成员函数的声明部分分别作为2个源文个源文件件(.cpp文件文件),在主函数中用,在主函数中用#include命令把它们包含命令把它们包含进来,形成一个完整的程序,并上机运行。进来,形成一个完整的程序,并上机运行。面向对象程序设计面向对象程序设计20Date: 2022年5月9日星期一学

22、习要点学习要点 虚函数虚函数 纯虚函数纯虚函数与抽象类与抽象类讨论讨论学以致用学以致用面向对象程序设计面向对象程序设计21Date: 2022年5月9日星期一学习目标学习目标 掌握虚函数掌握虚函数 改写示例程序,完成学以致用题目改写示例程序,完成学以致用题目讨论讨论学以致用学以致用面向对象程序设计面向对象程序设计22Date: 2022年5月9日星期一6.3 6.3 虚函数虚函数 上节介绍的程序中,上节介绍的程序中,circle circle 类和类和 cylinder cylinder 类都定义了类都定义了 area area 函数,但功能不一样。函数,但功能不一样。前者前者是求是求圆面积圆

23、面积,后者后者是求是求圆柱圆柱体表面积体表面积。由于处在不同的类中,同名是合法的。由于处在不同的类中,同名是合法的。 如果在圆柱体对象如果在圆柱体对象 cy1cy1中要调用求圆面积的函数,则应中要调用求圆面积的函数,则应该写成:该写成:cy1.circle:area( )cy1.circle:area( ),而不能写成,而不能写成cy1.area( )cy1.area( )。用这种。用这种方法来区分两种同名函数。使用起来不方便。方法来区分两种同名函数。使用起来不方便。 能否用一个调用形式,既能调用派生类的函数,又能调能否用一个调用形式,既能调用派生类的函数,又能调用基类同名函数?用基类同名函数

24、?C+C+中的虚函数就是用来解决这一问题。中的虚函数就是用来解决这一问题。面向对象程序设计面向对象程序设计23Date: 2022年5月9日星期一6.3.1 6.3.1 虚函数的作用虚函数的作用6.3.2 6.3.2 静态关联与动态关联静态关联与动态关联6.3.3 6.3.3 在什么情况下应当声明虚函数在什么情况下应当声明虚函数6.3.4 6.3.4 虚析构函数虚析构函数面向对象程序设计面向对象程序设计24Date: 2022年5月9日星期一6.3.1 6.3.1 虚函数的作用虚函数的作用 虚函数的作用虚函数的作用:虚函数的作用是允许:虚函数的作用是允许在派生类中重新定义与基类同名的函在派生类

25、中重新定义与基类同名的函数,并且可以通过基类指针或引用来数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。访问基类和派生类中的同名函数。面向对象程序设计面向对象程序设计25Date: 2022年5月9日星期一#include #include class studentpublic: student (int n,string nam, float s) num= n; name= nam; score= s; void display( ) cout num: num name: name score: scoreendl; protected: int num; string

26、name; float score;class graduate : public studentpublic: graduate (int n, string nam, float s, float p): student (n,nam,s), pay(p) void display( ) cout num: num name: name score: score pay: paydisplay( ); / 指向基类对象指向基类对象s1 pt = &g1; pt-display( ); / 指向派生类对象指向派生类对象g1,仅仅输出了派生类的基类数据成员,因为输出了派生类的基类数据成

27、员,因为它调用的是基类成员函数它调用的是基类成员函数display! return 0; 假如想输出派生类的全部数据,当然可以采用下面两种方法之一:假如想输出派生类的全部数据,当然可以采用下面两种方法之一:n通过派生类对象名通过派生类对象名g1,调用派生类对象的成员函数:,调用派生类对象的成员函数:g1.display( );n定义一个指向派生类的指针定义一个指向派生类的指针ptr,并指向,并指向g1,然后用,然后用ptr-display( )。面向对象程序设计面向对象程序设计26Date: 2022年5月9日星期一 我们可以用虚函数顺利解决这一问题。方法是:我们可以用虚函数顺利解决这一问题。

28、方法是: 在基类在基类student 中声明中声明 display函数时,在最左边加上一个关键字函数时,在最左边加上一个关键字virtual :virtual void display( ); 就把就把student类的类的成员函数成员函数display函数函数声明声明为为虚函数虚函数,程序其,程序其它部分不变,编译运行后,可见,使用它部分不变,编译运行后,可见,使用pt-display( ),的确将,的确将graduate类对象类对象 g1 的全部数据显示了出来,说明它调用的是的全部数据显示了出来,说明它调用的是g1 的的成员函数成员函数 display。 在在派生类中重新定义该函数派生类中

29、重新定义该函数,要求,要求函数名、函数类型、参数表完全相函数名、函数类型、参数表完全相同同。但。但不不必加必加virtual关键字关键字。 只只在类里的成员函数声明时,加上关键字在类里的成员函数声明时,加上关键字virtual,在,在类外定义定义虚类外定义定义虚函数函数时,时,不必再加不必再加virtual关键字。关键字。 定义一个指向基类对象的指针,并使它指向同一类中的某一对象;定义一个指向基类对象的指针,并使它指向同一类中的某一对象; 通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数,而不是基类的同名函数!的

30、同名函数,而不是基类的同名函数!面向对象程序设计面向对象程序设计27Date: 2022年5月9日星期一 通过使用虚函数和指针,就能方便地调用同一类通过使用虚函数和指针,就能方便地调用同一类族中不同类对象的同名函数,只要先用基类指针指族中不同类对象的同名函数,只要先用基类指针指向该对象即可。向该对象即可。#include #include class studentpublic: student (int n,string nam, float s) num= n; name= nam; score= s; virtual void display( ) cout num: num name:

31、 name score: scoreendl; protected: int num; string name; float score;class graduate : public studentpublic: graduate (int n, string nam, float s, float p): student (n,nam,s), pay(p) void display( ) cout num: num name: name score: score pay: paydisplay( ); / 指向基类对象指向基类对象s1 pt = &g1; pt-display( )

32、; / 指向派生类对象指向派生类对象g1,调调用用g1的显示函数的显示函数display,打印出,打印出g1全全部数据成员部数据成员 return 0;面向对象程序设计面向对象程序设计28Date: 2022年5月9日星期一将函数重载与虚函数比较,可见:将函数重载与虚函数比较,可见:函数重载函数重载是解决的是是解决的是同一层次同一层次上的同名函上的同名函数的问题。是数的问题。是横向重载横向重载。虚函数虚函数解决的是解决的是不同派生层次不同派生层次上的同名函上的同名函数的问题。数的问题。相当相当于于纵向重载纵向重载。同一类族的虚函数的首部是相同的;而重同一类族的虚函数的首部是相同的;而重载函数的

33、首部不相同载函数的首部不相同(参数表不能相同参数表不能相同)。面向对象程序设计面向对象程序设计29Date: 2022年5月9日星期一6.3.26.3.2 静态关联与动态关联静态关联与动态关联 静态关联与动态关联静态关联与动态关联C+C+在编译或运行时,对多个同名函数究竟调用在编译或运行时,对多个同名函数究竟调用哪一个函数,需要一定的机制来确定。这种确定调用哪一个函数,需要一定的机制来确定。这种确定调用的具体对象的过程称为的具体对象的过程称为“关联关联( binding )( binding )”,即把,即把函函数名与某一个类对象捆绑在一起数名与某一个类对象捆绑在一起。函数重载,在编译时就可确

34、定其调用的函数是哪函数重载,在编译时就可确定其调用的函数是哪一个;通过对象名调用虚函数,在编译时也可确定其一个;通过对象名调用虚函数,在编译时也可确定其调用的虚函数属于哪一个类调用的虚函数属于哪一个类。其过程称为。其过程称为“静态关联静态关联( static binding )( static binding ),因为是在运行前进行关联的,因为是在运行前进行关联的,又称又称为为早期关联早期关联( early binding )( early binding )。面向对象程序设计面向对象程序设计30Date: 2022年5月9日星期一通过指针和虚函数的结合,在编译阶段是没法进行关联的通过指针和虚

35、函数的结合,在编译阶段是没法进行关联的,因为编译只作静态的语法检查,光从语句形式因为编译只作静态的语法检查,光从语句形式pt-display( )pt-display( )无法无法确定调用的对象,也就没法关联。确定调用的对象,也就没法关联。出现这种情况,我们可以在运行阶段来处理关联。在运行阶出现这种情况,我们可以在运行阶段来处理关联。在运行阶段,基类指针先指向某一个对象,然后通过指针调用该对象的成段,基类指针先指向某一个对象,然后通过指针调用该对象的成员函数。此时调用哪个函数是确定的,没有不确定因素。例如语员函数。此时调用哪个函数是确定的,没有不确定因素。例如语句句 ptpt = &g

36、1; pt-display( ); = &g1; pt-display( ); 非常确定的是调用非常确定的是调用g1g1对象的成员函数对象的成员函数displaydisplay。这种情况由于是这种情况由于是在运行阶段将虚函数与某一类对象在运行阶段将虚函数与某一类对象“绑定绑定”在一起的在一起的,因此称为,因此称为“动态关联动态关联( dynamic binding )( dynamic binding ),或,或“滞后滞后关联关联( late binding )( late binding )”。面向对象程序设计面向对象程序设计31Date: 2022年5月9日星期一 使用虚函数,要注

37、意使用虚函数,要注意 只能用只能用virtual virtual 声明类的成员函数声明类的成员函数,类外的普通函数不,类外的普通函数不能声明成虚函数,因为它没有继承的操作。能声明成虚函数,因为它没有继承的操作。 一个成员函数被声明成虚函数后,在同一类族中的类一个成员函数被声明成虚函数后,在同一类族中的类就不能再定义一个非就不能再定义一个非virtualvirtual的的、但与该函数具有相同、但与该函数具有相同参数表和返回类型的参数表和返回类型的同名函数同名函数。 使用虚函数,系统要有一定的空间开销使用虚函数,系统要有一定的空间开销。当一个类带。当一个类带有虚函数时,编译系统会为该类构造一个虚函

38、数表有虚函数时,编译系统会为该类构造一个虚函数表( virtual function table, ( virtual function table, vtablevtable ) ),它是一个指针数组,它是一个指针数组,存放每个虚函数的入口地址。存放每个虚函数的入口地址。系统在进行动态关联时的时间开销是很少的,所以多系统在进行动态关联时的时间开销是很少的,所以多态性运行效率非常高。态性运行效率非常高。面向对象程序设计面向对象程序设计32Date: 2022年5月9日星期一6.3.36.3.3 在什么情况下应当声明虚函数在什么情况下应当声明虚函数 什么情况下使用虚函数?什么情况下使用虚函数?

39、成员函数所在的类成员函数所在的类是否会是否会作为基类作为基类?成员函数被继承后成员函数被继承后有没有可能有没有可能发生功能变化发生功能变化,如果两个因素都具备,就,如果两个因素都具备,就应应该将它声明成虚函数该将它声明成虚函数。 如果成员函数被继承后如果成员函数被继承后功能不变功能不变,或,或派生类用不到派生类用不到该函该函数,就数,就不要声明成虚函数不要声明成虚函数。 应考虑应考虑对成员函数的访问对成员函数的访问是通过对象名还是基类指针,是通过对象名还是基类指针,如果如果是通过基类指针或引用访问是通过基类指针或引用访问,则应当,则应当声明为虚函数声明为虚函数。 有时基类中定义虚函数时并不定义

40、它的函数体有时基类中定义虚函数时并不定义它的函数体,即,即函数函数体为空体为空。其作用只是定义了一个虚函数名,具体功能留。其作用只是定义了一个虚函数名,具体功能留给派生类添加给派生类添加(6.4 6.4 节会讨论这种情况节会讨论这种情况)。面向对象程序设计面向对象程序设计33Date: 2022年5月9日星期一6.3.4 6.3.4 虚析构函数虚析构函数 虚析构函数虚析构函数 当派生类对象撤消时,系统当派生类对象撤消时,系统先调用派生类析构函数先调用派生类析构函数,再调再调用基类析构函数用基类析构函数。但是,如果。但是,如果用用new new 运算符建立了一个运算符建立了一个派生类临时对象派生

41、类临时对象,但,但用一个基类指针指向它用一个基类指针指向它,当程序,当程序用带用带指针参数的指针参数的delete delete 撤消对象时撤消对象时,会发生让人不能接受的情,会发生让人不能接受的情况:况:系统只析构基类对象,而不析构派生类对象系统只析构基类对象,而不析构派生类对象:class pointpublic: point( ) point( ) cout 析构基类对象析构基类对象 endl; ;class circle : public pointpublic: circle( ) circle ( ) cout 析构派生类对象析构派生类对象 endl; private: int r

42、adius;int main( ) point *p = new circle; / 指针为指向基类对象指针,指针为指向基类对象指针, delete p; return 0;面向对象程序设计面向对象程序设计34Date: 2022年5月9日星期一实际上,程序只析构了基类对象,而没有析构派实际上,程序只析构了基类对象,而没有析构派生类对象,为什么呢?因为指针生类对象,为什么呢?因为指针p p 为基类指针为基类指针,系统,系统认为它只有基类对象,而与派生类对象无关。认为它只有基类对象,而与派生类对象无关。 解决的办法:解决的办法:可以将基类的析构函数声明为虚析可以将基类的析构函数声明为虚析构函数。

43、如:构函数。如: virtual virtual point( ) point( ) coutcout 析构基类对象析构基类对象 endlendl; ; 程序其它部分不动,就行了。程序其它部分不动,就行了。当基类的析构函数被定义成当基类的析构函数被定义成virtualvirtual,无论指针指,无论指针指向同一类族中的哪一个对象,当撤消对象时,系统向同一类族中的哪一个对象,当撤消对象时,系统会采用会采用动态关联动态关联,调用相应的析构函数,清理该对,调用相应的析构函数,清理该对象,然后再析构基类对象。象,然后再析构基类对象。面向对象程序设计面向对象程序设计35Date: 2022年5月9日星期

44、一6.4.1 6.4.1 纯虚函数纯虚函数6.4.2 6.4.2 抽象类抽象类6.4.3 6.4.3 应用实例应用实例6.4 6.4 纯虚函数与抽象类纯虚函数与抽象类面向对象程序设计面向对象程序设计36Date: 2022年5月9日星期一6.4.1 6.4.1 纯虚函数纯虚函数前面已经提到,有时前面已经提到,有时在基类中将某一成员函数定为虚函数在基类中将某一成员函数定为虚函数并不是基类本身的需要,而是派生类的需要。并不是基类本身的需要,而是派生类的需要。在基类中预留一在基类中预留一个函数名,具体功能留给派生类根据需要去定义。个函数名,具体功能留给派生类根据需要去定义。在上一节中基类在上一节中基

45、类point point 中有定义面积中有定义面积areaarea函数,是因为函数,是因为“点点”没有面积的概念。但是,其直接派生类没有面积的概念。但是,其直接派生类circlecircle和间接派生类和间接派生类cylindercylinder却都需要却都需要area area 函数,而且这两个函数,而且这两个area area 函数的功能还不相函数的功能还不相同,一个是求圆面积,一个是求圆柱体表面积。同,一个是求圆面积,一个是求圆柱体表面积。也许会想到,在基类也许会想到,在基类point point 中加一个中加一个area area 函数,并声明为虚函数,并声明为虚函数:函数:virtu

46、al float area ( ) const virtual float area ( ) const return 0; return 0; 其返回其返回0 0表示表示“点点”没有面积。其实,在基类中并不使用这没有面积。其实,在基类中并不使用这个函数,其返回值也没有意义。个函数,其返回值也没有意义。面向对象程序设计面向对象程序设计37Date: 2022年5月9日星期一为简化起见,可以为简化起见,可以不写出不写出这种无意义的这种无意义的函数体函数体,只给出函,只给出函数的原型,并在后面加上数的原型,并在后面加上“=0=0”,如:,如:virtual float area( ) const

47、=0 ; / virtual float area( ) const =0 ; / 纯虚函数纯虚函数这就将这就将areaarea声明为一个纯虚函数声明为一个纯虚函数(pure virtual function)(pure virtual function) 纯虚函数的声明形式纯虚函数的声明形式virtual virtual 函数类型函数类型 函数名函数名( ( 参数表参数表 ) =0) =0;说明说明 纯虚函数没有函数体纯虚函数没有函数体; 最后的最后的“=0=0”不表示函数值返回不表示函数值返回0 0,它只是形式上的作用,它只是形式上的作用,告诉编译系统:这是告诉编译系统:这是纯虚函数纯虚函

48、数, 这是一个这是一个声明语句,以分号结尾声明语句,以分号结尾。 如果基类中声明了纯虚函数,但派生类中没有定义该函数,如果基类中声明了纯虚函数,但派生类中没有定义该函数,则该虚函数在派生类中仍为纯虚函数。则该虚函数在派生类中仍为纯虚函数。面向对象程序设计面向对象程序设计38Date: 2022年5月9日星期一 纯虚函数的作用纯虚函数的作用是在基类中为其派生类保留一个函数的名字,是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。以便派生类根据需要对它进行定义。如果基类如果基类中没有保留函数名,则无法实现多态性中没有保留函数名,则无法实现多态性。面向对象程序设计面向对象程序设

49、计39Date: 2022年5月9日星期一6.4.2 6.4.2 抽象类抽象类 抽象类抽象类 什么叫抽象类?什么叫抽象类?一般声明了一个类,用来定义若干对象。但一般声明了一个类,用来定义若干对象。但有些类并不是用来生成对象,而是作为基类去建立派生类。有些类并不是用来生成对象,而是作为基类去建立派生类。这种不用来定义对象,而只作为一种基本类型用做继承的类,这种不用来定义对象,而只作为一种基本类型用做继承的类,就叫抽象类就叫抽象类( abstract class )( abstract class )。由于抽象类常作为基类,我们。由于抽象类常作为基类,我们也称为抽象基类也称为抽象基类( abstr

50、act base class )( abstract base class )。比如,凡是包含纯虚函数的类都叫抽象类。因为纯虚函数比如,凡是包含纯虚函数的类都叫抽象类。因为纯虚函数不能被调用,包含纯虚函数的类无法建立对象。不能被调用,包含纯虚函数的类无法建立对象。 抽象类的作用抽象类的作用:是作为一个类族的共同基类。即,为一个类:是作为一个类族的共同基类。即,为一个类族提供一个公共接口。族提供一个公共接口。一个类层次结构中可以不包含任何抽象类,每一层次的类一个类层次结构中可以不包含任何抽象类,每一层次的类都可以建立对象。但是,许多系统的顶层是一个抽象类,甚都可以建立对象。但是,许多系统的顶层是

51、一个抽象类,甚至顶部有好几层都是抽象类。至顶部有好几层都是抽象类。面向对象程序设计面向对象程序设计40Date: 2022年5月9日星期一如果由抽象类所如果由抽象类所派生出的新类中对基类的所有纯虚函派生出的新类中对基类的所有纯虚函数都进行了定义数都进行了定义,可以被调用,这个,可以被调用,这个派生类就不是抽象类派生类就不是抽象类,而成为可以用来定义对象的具体类而成为可以用来定义对象的具体类( concrete class )( concrete class )。如果由抽象类如果由抽象类所所派生出的新类中对基类的所有纯虚函派生出的新类中对基类的所有纯虚函数都没有进行定义,这个数都没有进行定义,这

52、个派生类就仍然是抽象类派生类就仍然是抽象类。虽然虽然抽象类不能定义对象抽象类不能定义对象,但,但可以定义指向抽象类的可以定义指向抽象类的数据的指针数据的指针。当派生类成为具体类之后,就可以用这种指。当派生类成为具体类之后,就可以用这种指针向派生类对象,然后通过该指针调用虚函数,实现多态针向派生类对象,然后通过该指针调用虚函数,实现多态性操作。性操作。下面看一个示例,以下面看一个示例,以pointpoint为基类的点为基类的点- -圆圆- -圆柱体类的圆柱体类的层次结构。现对其改写,层次结构。现对其改写,类的顶层是抽象基类类的顶层是抽象基类shapeshape。点、。点、圆、圆柱体都是圆、圆柱体

53、都是shapeshape的直接或间接派生类。的直接或间接派生类。面向对象程序设计面向对象程序设计41Date: 2022年5月9日星期一#include class shapepublic: virtual float area( ) const return 0.0; / 虚函数 virtual float volume( ) const return 0.0; / 虚函数 virtual void shapeName( ) const =0; / 纯虚函数; / 抽象类不能也不必定义对象class point : public shapepublic: point (float =0, f

54、loat =0); void setPoint (float, float); float getX( ) return x; float getY( ) return y; virtual void shapeName ( ) const coutpoint:; / 对虚函数再定义 friend ostream &operator ( ostream &, const point &);protected: float x,y;point : point (float a, float b) x =a, y = b; void point : setPoint (fl

55、oat a, float b) x = a; y = b; ostream &operator ( ostream &output, const point &p)output p.x , p.y ; return output;面向对象程序设计面向对象程序设计42Date: 2022年5月9日星期一class circle : public pointpublic: circle (float x=0, float y=0, float r=0); float getRadius( ) const; virtual float area ( ) const; / 因为

56、基类此函数定义为虚函数,所以在这里,不管有没有virtual, 只要函数名、参数表相同,都是虚函数 virtual void shapeName ( ) const coutcircle:; / 对虚函数再定义 friend ostream &operator( ostream &, const circle &);protected: float radius;/ 定义circle类成员函数circle : circle (float x, float y, float r) : point (x,y),radius ( r ) float circle : getR

57、adius ( ) const return radius; float circle : area( ) const return 3.14159 * radius * radius; / 重新定义了重新定义了area 函数函数ostream &operator ( ostream &output, const circle &c)output c.x , c.y , r= c.radius; return output;/ volume 对于对于point、circle类没有意义,类没有意义,只是简单继承过来,而没有重新定义只是简单继承过来,而没有重新定义面向对象程

58、序设计面向对象程序设计43Date: 2022年5月9日星期一class cylinder : public circlepublic: cylinder ( float x=0, float y=0, float r=0, float h=0 ); void setHeight (float); virtual float area ( ) const; virtual float volume ( ) const; virtual void shapeName ( ) const coutcylinder:; / 对虚函数再定义 friend ostream &operator( ostream &, const cylinder &);protected: float height;/ 定义cylinder类成员函数cylinder : cylinder ( float a, float b, float r, float h ) : circle (a,b,r), height (h) void cylinder : setHeight (float h) height= h; float cylinder : area( ) const return 2*circle:area() +2*3.14159 * radius *

温馨提示

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

评论

0/150

提交评论