多态性与虚函数学习教案

上传人:夏** 文档编号:569825676 上传时间:2024-07-31 格式:PPT 页数:53 大小:680KB
返回 下载 相关 举报
多态性与虚函数学习教案_第1页
第1页 / 共53页
多态性与虚函数学习教案_第2页
第2页 / 共53页
多态性与虚函数学习教案_第3页
第3页 / 共53页
多态性与虚函数学习教案_第4页
第4页 / 共53页
多态性与虚函数学习教案_第5页
第5页 / 共53页
点击查看更多>>
资源描述

《多态性与虚函数学习教案》由会员分享,可在线阅读,更多相关《多态性与虚函数学习教案(53页珍藏版)》请在金锄头文库上搜索。

1、会计学1多态性与虚函数多态性与虚函数(hnsh)第一页,共53页。多态性(polymorphism)是面向对象程序设计(shj)的一个重要特征。利用多态性可以设计(shj)和实现一个易于扩展的系统。在C+程序设计(shj)中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。6.1 6.1 多态性的概念多态性的概念多态性的概念多态性的概念(ginin)(ginin)第1页/共52页第二页,共

2、53页。在C+程序设计中,在不同的类中定义了其响应消息的方法,那么使用这些类时,不必考虑它们是什么类型,只要发布消息即可。从系统实现的角度看,多态性分为两类:静态多态性和动态多态性。以前学过的函数(hnsh)重载和运算符重载实现的多态性属于静态多态性,在程序编译时系统就能决定调用的是哪个函数(hnsh),因此静态多态性又称编译时的多态性。静态多态性是通过函数(hnsh)的重载实现的(运算符重载实质上也是函数(hnsh)重载)。动态多态性是在程序运行过程中才动态地确定操作所针对的对象。它又称运行时的多态性。动态多态性是通过虚函数(hnsh)(virtualfunction)实现的。有关静态多态性

3、的应用已经介绍过了,在本章中主要介绍动态多态性和虚函数(hnsh)。第2页/共52页第三页,共53页。要研究的问题是:当一个基类被继承为不同的派生类时,各派生类可以(ky)使用与基类成员相同的成员名,如果在运行时用同一个成员名调用类对象的成员,会调用哪个对象的成员?也就是说,通过继承而产生了相关的不同的派生类,与基类成员同名的成员在不同的派生类中有不同的含义。也可以(ky)说,多态性是“一个接口,多种方法”。第3页/共52页第四页,共53页。下面是一个承上启下的例子。一方面它是有关继承和运算符重载内容的综合应用(yngyng)的例子,通过这个例子可以进一步融会贯通前面所学的内容,另一方面又是作

4、为讨论多态性的一个基础用例。6.2 6.2 一个一个一个一个(y )(y )典型的例子典型的例子典型的例子典型的例子第4页/共52页第五页,共53页。例6.1先建立一个(y)Point(点)类,包含数据成员x,y(坐标点)。以它为基类,派生出一个(y)Circle(圆)类,增加数据成员r(半径),再以Circle类为直接基类,派生出一个(y)Cylinder(圆柱体)类,再增加数据成员h(高)。要求编写程序,重载运算符“”,使之能用于输出以上类对象。对于一个(y)比较大的程序,应当分成若干步骤进行。先声明基类,再声明派生类,逐级进行,分步调试。(1)声明基类Point类可写出声明基类Point

5、的部分如下:#include/声明类PointclassPointpublic:Point(floatx=0,floaty=0);/有默认参数的构造函数第5页/共52页第六页,共53页。voidsetPoint(float,float);/voidsetPoint(float,float);/设置设置(shzh)(shzh)坐标值坐标值floatgetX()constreturnx;/floatgetX()constreturnx;/读读x x坐标坐标floatgetY()constreturny;/floatgetY()constreturny;/读读y y坐标坐标friendostream

6、&operator(ostream&,constPoint&);/friendostream&operator(ostream&,constPoint&);/重载运算符重载运算符“”“”protected:/protected:/受保护成员受保护成员floatx,y;floatx,y;/下面定义下面定义PointPoint类的成员函数类的成员函数/Point/Point的构造函数的构造函数Point:Point(floata,floatb)/Point:Point(floata,floatb)/对对x,yx,y初始化初始化x=a;y=b;x=a;y=b;/设置设置(shzh)x(shzh)x和

7、和y y的坐标值的坐标值voidPoint:setPoint(floata,floatb)/voidPoint:setPoint(floata,floatb)/为为x,yx,y赋新值赋新值x=a;y=b;x=a;y=b;/重载运算符重载运算符“”“”,使之能输出点的坐标,使之能输出点的坐标ostream&operator(ostream&output,constPoint&p)ostream&operator(ostream&output,constPoint&p)outputp.x,p.yendl;outputp.x,p.yendl;returnoutput;returnoutput; 以上

8、完成了基类以上完成了基类PointPoint类的声明。类的声明。第6页/共52页第七页,共53页。现在要对上面写的基类声明进行调试,检查它是否有错,为此要写出main函数。实际上它是一个测试程序。intmain()Pointp(3.5,6.4);/建立Point类对象pcoutx=p.getX(),y=p.getY()endl;/输出p的坐标值p.setPoint(8.5,6.8);/重新设置p的坐标值coutp(new):pendl;/用重载运算符“”输出p点坐标程序编译通过,运行结果(jigu)为x=3.5,y=6.4p(new):8.5,6.8测试程序检查了基类中各函数的功能,以及运算符

9、重载的作用,证明程序是正确的。第7页/共52页第八页,共53页。(2)(2)声明派生类声明派生类CircleCircle在上面的基础上,再写出声明派生类在上面的基础上,再写出声明派生类CircleCircle的部分的部分(bfen)(bfen):classCircle:publicPoint/circleclassCircle:publicPoint/circle是是PointPoint类的公用派生类类的公用派生类public:public:Circle(floatx=0,floaty=0,floatr=0);/Circle(floatx=0,floaty=0,floatr=0);/构造函数构

10、造函数voidsetRadius(float);/voidsetRadius(float);/设置半径值设置半径值floatgetRadius()const;/floatgetRadius()const;/读取半径值读取半径值floatarea()const;/floatarea()const;/计算圆面积计算圆面积friendostream&operator(ostream&,constCircle&);/friendostream&operator(ostream&,constCircle&);/重载运算符重载运算符“”“”private:private:floatradius;float

11、radius;/定义构造函数,对圆心坐标和半径初始化定义构造函数,对圆心坐标和半径初始化Circle:Circle(floata,floatb,floatr):Point(a,b),radius(r)Circle:Circle(floata,floatb,floatr):Point(a,b),radius(r)/设置半径值设置半径值voidCircle:setRadius(floatr)voidCircle:setRadius(floatr)radius=r;radius=r;/读取半径值读取半径值floatCircle:getRadius()constreturnradius;floatCi

12、rcle:getRadius()constreturnradius;/计算圆面积计算圆面积floatCircle:area()constfloatCircle:area()const第8页/共52页第九页,共53页。return 3.14159*radius*radius;return 3.14159*radius*radius;/ /重载运算符重载运算符“”“”,使之按规定的形式输出圆的信息,使之按规定的形式输出圆的信息ostream &operator(ostream &output,const Circle &c)ostream &operator(ostream &output,con

13、st Circle &c)outputCenter=c.x,c.y,r=c.radius,area=c.area( )endl;outputCenter=c.x,c.y,r=c.radius,area=c.area( )endl; return output; return output; 为了测试为了测试(csh)(csh)以上以上CircleCircle类的定义,可以写出下面的主函数类的定义,可以写出下面的主函数: :int main( )int main( )Circle c(3.5,6.4,5.2);/Circle c(3.5,6.4,5.2);/建立建立CircleCircle类对象

14、类对象c c,并给定圆心坐标和半径,并给定圆心坐标和半径 coutoriginal circle:nx=c.getX(), y=c.getY(), r=c.getRadius( ) coutoriginal circle:nx=c.getX(), y=c.getY(), r=c.getRadius( ) , area=c.area( )endl; / , area=c.area( )endl; /输出圆心坐标、半径和面积输出圆心坐标、半径和面积 c.setRadius(7.5); / c.setRadius(7.5); /设置半径值设置半径值 c.setPoint(5,5); / c.setP

15、oint(5,5); /设置圆心坐标值设置圆心坐标值x,yx,y coutnew circle:nc; / coutnew circle:nc; /用重载运算符用重载运算符“”“”输出圆对象的信息输出圆对象的信息 Point &pRef=c; /pRef Point &pRef=c; /pRef是是PointPoint类的引用变量,被类的引用变量,被c c初始化初始化 coutpRef:pRef; / coutpRef:pRef; /输出输出pRefpRef的信息的信息 return 0; return 0; 程序编译通过,运行结果为程序编译通过,运行结果为original circle:(o

16、riginal circle:(输出原来的圆的数据输出原来的圆的数据) )x=3.5, y=6.4, r=5.2, area=84.9486x=3.5, y=6.4, r=5.2, area=84.9486new circle: (new circle: (输出修改后的圆的数据输出修改后的圆的数据) )Center=5,5, r=7.5, area=176.714Center=5,5, r=7.5, area=176.714pRef:5,5 (pRef:5,5 (输出圆的圆心输出圆的圆心“ “点点” ”的数据的数据) )第9页/共52页第十页,共53页。(3)(3)声明声明CircleCirc

17、le的派生类的派生类CylinderCylinder前面已从基类前面已从基类PointPoint派生出派生出CircleCircle类,现在再从类,现在再从CircleCircle派生出派生出CylinderCylinder类。类。classCylinder:publicCircle/CylinderclassCylinder:publicCircle/Cylinder是是CircleCircle的公用派生类的公用派生类public:public:Cylinder(floatx=0,floaty=0,floatr=0,floath=0);/Cylinder(floatx=0,floaty=0,

18、floatr=0,floath=0);/构造函数构造函数voidsetHeight(float);/voidsetHeight(float);/设置圆柱高设置圆柱高floatgetHeight()const;/floatgetHeight()const;/读取圆柱高读取圆柱高floatarea()const;/floatarea()const;/计算计算(jsun)(jsun)圆表面圆表面积积floatvolume()const;/floatvolume()const;/计算计算(jsun)(jsun)圆柱体圆柱体积积friendostream&operator(ostream&,constC

19、ylinder&);/friendostream&operator(ostream&,constCylinder&);/重载运算符重载运算符“”“”protected:protected:floatheight;/floatheight;/圆柱高圆柱高;/定义构造函数定义构造函数Cylinder:Cylinder(floata,floatb,floatr,floath)Cylinder:Cylinder(floata,floatb,floatr,floath):Circle(a,b,r),height(h):Circle(a,b,r),height(h)/设置圆柱高设置圆柱高voidCylin

20、der:setHeight(floath)height=h;voidCylinder:setHeight(floath)height=h;/读取圆柱高读取圆柱高floatCylinder:getHeight()constreturnheight;floatCylinder:getHeight()constreturnheight;第10页/共52页第十一页,共53页。/ /计算计算(j sun)(j sun)圆表面积圆表面积float Cylinder:area( ) constfloat Cylinder:area( ) const return 2*Circle:area( )+2*3.1

21、4159*radius*height; return 2*Circle:area( )+2*3.14159*radius*height;/ /计算计算(j sun)(j sun)圆柱体积圆柱体积float Cylinder:volume() constfloat Cylinder:volume() constreturn Circle:area()*height;return Circle:area()*height;/ /重载运算符重载运算符“”“”ostream &operator(ostream &output,const Cylinder& cy)ostream &operator(o

22、stream &output,const Cylinder& cy)outputCenter=cy.x,cy.y,r=cy.radius,h=cy.heightoutputCenter=cy.x,cy.y,r=cy.radius,h=cy.height narea=cy.area( ), volume=cy.volume( )endl; narea=cy.area( ), volume=cy.volume( )endl; return output; return output; 可以写出下面的主函数可以写出下面的主函数: : int main( )int main( )Cylinder cy

23、1(3.5,6.4,5.2,10);/Cylinder cy1(3.5,6.4,5.2,10);/定义定义CylinderCylinder类对象类对象cy1cy1 coutnoriginal cylinder:nx=cy1.getX( ), y=cy1.getY( ), r= coutnoriginal cylinder:nx=cy1.getX( ), y=cy1.getY( ), r= cy1.getRadius( ), h=cy1.getHeight( )narea=cy1.area() cy1.getRadius( ), h=cy1.getHeight( )narea=cy1.area(

24、) ,volume=cy1.volume()endl;/ ,volume=cy1.volume()endl;/用系统定义的运算符用系统定义的运算符“”“”输出输出cy1cy1的数据的数据 cy1.setHeight(15); / cy1.setHeight(15); /设置圆柱高设置圆柱高 cy1.setRadius(7.5); / cy1.setRadius(7.5); /设置圆半径设置圆半径 cy1.setPoint(5,5); / cy1.setPoint(5,5); /设置圆心坐标值设置圆心坐标值x,yx,y coutnnew cylinder:ncy1; / coutnnew cyl

25、inder:ncy1; /用重载运算符用重载运算符“”“”输出输出cy1cy1的数据的数据 Point &pRef=cy1; /pRef Point &pRef=cy1; /pRef是是PointPoint类对象的引用变量类对象的引用变量第11页/共52页第十二页,共53页。coutnpRefasaPoint:pRef;/pRefcoutnpRefasaPoint:pRef;/pRef作为一个作为一个“点点”输出输出Circle&cRef=cy1;/cRefCircle&cRef=cy1;/cRef是是CircleCircle类对象的引用变量类对象的引用变量coutncRefasaCircle

26、:cRef;/cRefcoutncRefasaCircle:display();”可以调用不同派生层次中的display函数,只需在调用前给指针变量(binling)pt赋以不同的值(使之指向不同的类对象)即可。C+中的虚函数就是用来解决这个问题的。虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。请分析例6.2。这个例子开始时没有使用虚函数,然后再讨论使用虚函数的情况。第14页/共52页第十五页,共53页。例例6.26.2基类与派生类中有同名函数。基类与派生类中有同名函数。在下面的程序中在下面的程序中StudentStudent是

27、基类,是基类,GraduateGraduate是派生类,它们都有是派生类,它们都有displaydisplay这个同名的函数。这个同名的函数。#include#include#include#includeusingnamespacestd;usingnamespacestd;/声明基类声明基类StudentStudentclassStudentclassStudentpublic:public:Student(int,string,float);/Student(int,string,float);/声明构造函数声明构造函数voiddisplay();/voiddisplay();/声明输出

28、函数声明输出函数protected:/protected:/受保护受保护(boh)(boh)成员,派生类可成员,派生类可以访问以访问 intnum;intnum;stringname;stringname;floatscore;floatscore;/Student/Student类成员函数的实现类成员函数的实现Student:Student(intn,stringnam,floats)/Student:Student(intn,stringnam,floats)/定义构造函数定义构造函数num=n;name=nam;score=s;num=n;name=nam;score=s;voidStu

29、dent:display()/voidStudent:display()/定义输出函数定义输出函数coutnum:numnname:namenscore:scorenn;coutnum:numnname:namenscore:scorenn;第15页/共52页第十六页,共53页。/ /声明声明(shngmng)(shngmng)公用派生类公用派生类GraduateGraduateclass Graduate:public Studentclass Graduate:public Studentpublic:public: Graduate(int, string, float, float);

30、 / Graduate(int, string, float, float); /声明声明(shngmng)(shngmng)构造函数构造函数 void display( ); / void display( ); /声明声明(shngmng)(shngmng)输出函数输出函数private:private: float pay; float pay; ;/ Graduate/ Graduate类成员函数的实现类成员函数的实现void Graduate:display( ) /void Graduate:display( ) /定义输出函数定义输出函数 coutnum:numnname:nam

31、enscore:scorenpay=payendl; coutnum:numnname:namenscore:scorenpay=paydisplay( ); pt-display( ); pt=&grad1; pt=&grad1; pt-display( ); pt-display( ); return 0; return 0; 第16页/共52页第十七页,共53页。运行结果如下,请仔细分析。运行结果如下,请仔细分析。num:1001(stud1num:1001(stud1的数据的数据) )name:Liname:Liscore:87.5score:87.5num:2001(grad1num

32、:2001(grad1中基类部分中基类部分(bfen)(bfen)的数的数据据) )name:wangname:wangscore:98.5score:98.5下面对程序作一点修改,在下面对程序作一点修改,在StudentStudent类中声明类中声明displaydisplay函数时,函数时,在最左面加一个关键字在最左面加一个关键字virtualvirtual,即,即virtualvoiddisplay();virtualvoiddisplay();这样就把这样就把StudentStudent类的类的displaydisplay函数声明为虚函数。程序其他部函数声明为虚函数。程序其他部分分(b

33、fen)(bfen)都不改动。再编译和运行程序,请注意分析运行结都不改动。再编译和运行程序,请注意分析运行结果果:num:1001(stud1num:1001(stud1的数据的数据) )name:Liname:Liscore:87.5score:87.5第17页/共52页第十八页,共53页。num:2001 (grad1num:2001 (grad1中基类部分的数据中基类部分的数据) )name:wangname:wangscore:98.5score:98.5pay=1200 (pay=1200 (这一项以前是没有的这一项以前是没有的) )由虚函数由虚函数(hnsh)(hnsh)实现的动态

34、多态性就是实现的动态多态性就是: : 同一类族中不同类的对象,对同一函数同一类族中不同类的对象,对同一函数(hnsh)(hnsh)调用作出不同调用作出不同的响应。虚函数的响应。虚函数(hnsh)(hnsh)的使用方法是的使用方法是: : (1) (1) 在基类用在基类用virtualvirtual声明成员函数声明成员函数(hnsh)(hnsh)为虚函数为虚函数(hnsh)(hnsh)。这样就可以在派生类中重新定义此函数。这样就可以在派生类中重新定义此函数(hnsh)(hnsh),为它赋予新的功能,并能方便地被调用。,为它赋予新的功能,并能方便地被调用。在类外定义虚函数在类外定义虚函数(hnsh

35、)(hnsh)时,不必再加时,不必再加virtualvirtual。(2) (2) 在派生类中重新定义此函数在派生类中重新定义此函数(hnsh)(hnsh),要求函数,要求函数(hnsh)(hnsh)名、函数名、函数(hnsh)(hnsh)类型、函数类型、函数(hnsh)(hnsh)参数参数个数和类型全部与基类的虚函数个数和类型全部与基类的虚函数(hnsh)(hnsh)相同,并根据派生类的需要重新定义函数相同,并根据派生类的需要重新定义函数(hnsh)(hnsh)体。体。第18页/共52页第十九页,共53页。C+规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在

36、派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。(3) 定义一个指向基类对象的指针变量,并使它指向同一类族中需要(xyo)调用该函数的对象。(4) 通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。第19页/共52页第二十页,共53页。通过虚函数与指向基类对象的指针变量的配合使用,就能方便地调用同一类族中不同类的同名函数,只要先用基类指针指向即可。如果指针不断地指向同一类族中不同类的对象,就能不断地调用这些对象中的

37、同名函数。这就如同前面说的,不断地告诉出租车司机要去的目的地,然后司机把你送到你要去的地方。需要说明;有时在基类中定义的非虚函数会在派生类中被重新(chngxn)定义(如例6.1中的area函数),如果用基类指针调用该成员函数,则系统会调用对象中基类部分的成员函数;如果用派生类指针调用该成员函数,则系统会调用派生类对象中的成员函数,这并不是多态性行为(使用的是不同类型的指针),没有用到虚函数的功能。第20页/共52页第二十一页,共53页。以前介绍的函数重载处理的是同一层次(cngc)上的同名函数问题,而虚函数处理的是不同派生层次(cngc)上的同名函数问题,前者是横向重载,后者可以理解为纵向重

38、载。但与重载不同的是:同一类族的虚函数的首部是相同的,而函数重载时函数的首部是不同的(参数个数或类型不同)。第21页/共52页第二十二页,共53页。编译系统要根据已有的信息,对同名函数的调用作出判断。对于调用同一类(yli)族中的虚函数,应当在调用时用一定的方式告诉编译系统,你要调用的是哪个类对象中的函数。这样编译系统在对程序进行编译时,即能确定调用的是哪个类对象中的函数。确定调用的具体对象的过程称为关联(binding)。在这里是指把一个函数名与一个类对象捆绑在一起,建立关联。一般地说,关联指把一个标识符和一个存储地址联系起来。6.3.2 6.3.2 静态静态静态静态(jngti)(jngt

39、i)关联与动态关联关联与动态关联关联与动态关联关联与动态关联 第22页/共52页第二十三页,共53页。前面所提到的函数重载和通过对象名调用的虚函数,在编译时即可确定其调用的虚函数属于哪一个类,其过程称为静态(jngti)关联(staticbinding),由于是在运行前进行关联的,故又称为早期关联(earlybinding)。函数重载属静态(jngti)关联。在上一小节程序中看到了怎样使用虚函数,在调用虚函数时并没有指定对象名,那么系统是怎样确定关联的呢?是通过基类指针与虚函数的结合来实现多态性的。先定义了一个指向基类的指针变量,并使它指向相应的类对象,然后通过这个基类指针去调用虚函数(例如“

40、pt-display()”)。显然,对这样的调用方式,编译系统在编译该行时是无法确定调用哪一个类对象的虚函数的。因为编译只作静态(jngti)的语法检查,光从语句形式是无法确定调用对象的。第23页/共52页第二十四页,共53页。在这样的情况下,编译系统把它放到运行阶段处理,在运行阶段确定关联关系。在运行阶段,基类指针变量先指向了某一个类对象,然后通过此指针变量调用该对象中的函数。此时调用哪一个对象的函数无疑是确定的。例如,先使pt指向grad1,再执行“pt-display()”,当然是调用grad1中的display函数。由于是在运行阶段把虚函数和类对象“绑定”在一起的,因此,此过程称为动态

41、关联(dynamicbinding)。这种多态性是动态的多态性,即运行阶段的多态性。在运行阶段,指针可以先后指向不同的类对象,从而(cngr)调用同一类族中不同类的虚函数。由于动态关联是在编译以后的运行阶段进行的,因此也称为滞后关联(latebinding)。第24页/共52页第二十五页,共53页。使用虚函数时,有两点要注意:(1)只能用virtual声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。因为虚函数的作用是允许在派生类中对基类的虚函数重新定义。显然,它只能用于类的继承层次结构中。(2)一个(y)成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个(y)非v

42、irtual的但与该虚函数具有相同的参数(包括个数和类型)和函数返回值类型的同名函数。根据什么考虑是否把一个(y)成员函数声明为虚函数呢?主要考虑以下几点:6.3.3 6.3.3 在什么情况在什么情况在什么情况在什么情况(qngkung)(qngkung)下应当声明虚函数下应当声明虚函数下应当声明虚函数下应当声明虚函数第25页/共52页第二十六页,共53页。(1)首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。(2)如果成员函数在类被继承后功能不需修改,或派生类用不到(bdo)该函数,则不要把它声明为虚函数。不

43、要仅仅考虑到要作为基类而把类中的所有成员函数都声明为虚函数。(3)应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问,如果是通过基类指针或引用去访问的,则应当声明为虚函数。第26页/共52页第二十七页,共53页。(4)有时,在定义虚函数时,并不定义其函数体,即函数体是空的。它的作用只是定义了一个虚函数名,具体功能留给派生类去添加。在6.4节中将详细讨论此问题。需要(xyo)说明的是:使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译系统会为该类构造一个虚函数表(virtualfunctiontable,简称vtable),它是一个指针数组,存放每个虚函数的入口地址。系统

44、在进行动态关联时的时间开销是很少的,因此,多态性是高效的。第27页/共52页第二十八页,共53页。析构函数的作用是在对象撤销之前(zhqin)做必要的“清理现场”的工作。当派生类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数。但是,如果用new运算符建立了临时对象,若基类中有析构函数,并且定义了一个指向该基类的指针变量。在程序用带指针参数的delete运算符撤销对象时,会发生一个情况:系统会只执行基类的析构函数,而不执行派生类的析构函数。6.3.4 6.3.4 虚析构函数虚析构函数虚析构函数虚析构函数(hnsh)(hnsh)第28页/共52页第二十九页,共53页。例例

45、6.36.3基类中有非虚析构函数时的执行情况。基类中有非虚析构函数时的执行情况。为简化程序,只列出最必要为简化程序,只列出最必要(byo)(byo)的部分。的部分。#include#includeusingnamespacestd;usingnamespacestd;classPoint/classPoint/定义基类定义基类PointPoint类类public:public:Point()/PointPoint()/Point类构造函数类构造函数Point()coutexecutingPointdestructorendl;/PointPoint()coutexecutingPointdes

46、tructorendl;/Point类析构函数类析构函数;classCircle:publicPoint/classCircle:publicPoint/定义派生类定义派生类CircleCircle类类public:public:Circle()/CircleCircle()/Circle类构造函数类构造函数Circle()coutexecutingCircledestructorendl;/CircleCircle()coutexecutingCircledestructorendl;/Circle类析构函数类析构函数private:private:intradius;intradius;i

47、ntmain()intmain()Point*p=newCircle;/Point*p=newCircle;/用用newnew开辟动态存储空间开辟动态存储空间deletep;/deletep;/用用deletedelete释放动态存储空间释放动态存储空间return0;return0; 第29页/共52页第三十页,共53页。这只是一个示意的程序。p是指向基类的指针变量,指向new开辟的动态存储空间,希望(xwng)用detele释放p所指向的空间。但运行结果为executingPointdestructor表示只执行了基类Point的析构函数,而没有执行派生类Circle的析构函数。原因是以前

48、介绍过的。如果希望(xwng)能执行派生类Circle的析构函数,可以将基类的析构函数声明为虚析构函数,如virtualPoint()coutexecutingPointdestructorendl;程序其他部分不改动,再运行程序,结果为executingCircledestructorexecutingPointdestructor第30页/共52页第三十一页,共53页。先调用了派生类的析构函数,再调用了基类的析构函数,符合人们的愿望。当基类的析构函数为虚函数时,无论指针指的是同一类族中的哪一个类对象,系统会采用动态关联,调用相应的析构函数,对该对象进行清理工作。如果将基类的析构函数声明为虚

49、函数时,由该基类所派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类的析构函数名字不相同。最好把基类的析构函数声明为虚函数。这将使所有派生类的析构函数自动成为虚函数。这样,如果程序中显式地用了delete运算符准备删除(shnch)一个对象,而delete运算符的操作对象用了指向派生类对象的基类指针,则系统会调用相应类的析构函数。第31页/共52页第三十二页,共53页。虚析构函数的概念和用法很简单,但它在面向对象程序设计中却是很重要的技巧。专业人员一般都习惯声明虚析构函数,即使基类并不需要析构函数,也显式地定义一个函数体为空的虚析构函数,以保证在撤销动态分配空间(kngj

50、in)时能得到正确的处理。构造函数不能声明为虚函数。这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上函数与类对象的绑定。第32页/共52页第三十三页,共53页。有时在基类中将某一成员函数定为虚函数,并不是基类本身的要求,而是考虑到派生类的需要,在基类中预留了一个函数名,具体功能留给派生类根据需要去定义。例如在本章的例6.1程序(chngx)中,基类Point中没有求面积的area函数,因为“点”是没有面积的,也就是说,基类本身不需要这个函数,所以在例6.1程序(chngx)中的Point类中没有定义area函数。但是,在其直接派生类Circle和间接派生类Cylinder中都需要有a

51、rea函数,而且这两个area函数的功能不同,一个是求圆面积,一个是求圆柱体表面积。6.4 6.4 纯虚函数纯虚函数纯虚函数纯虚函数(hnsh)(hnsh)与抽象类与抽象类与抽象类与抽象类 6.4.1 6.4.1 纯虚函数纯虚函数纯虚函数纯虚函数(hnsh)(hnsh)第33页/共52页第三十四页,共53页。有的读者自然会想到(xindo),在这种情况下应当将area声明为虚函数。可以在基类Point中加一个area函数,并声明为虚函数:virtualfloatarea()constreturn0;其返回值为0,表示“点”是没有面积的。其实,在基类中并不使用这个函数,其返回值也是没有意义的。为

52、简化,可以不写出这种无意义的函数体,只给出函数的原型,并在后面加上“=0”,如virtualfloatarea()const=0;/纯虚函数这就将area声明为一个纯虚函数(purevirtualfunction)。纯虚函数是在声明虚函数时被“初始化”为0的函数。声明纯虚函数的一般形式是virtual函数类型函数名(参数表列)=0;第34页/共52页第三十五页,共53页。注意:纯虚函数没有函数体;最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”;这是一个声明语句,最后应有分号。纯虚函数只有函数的名字而不具备函数的功能,不能被调用。它只是通知编译系统:“在

53、这里(zhl)声明一个虚函数,留待派生类中定义”。在派生类中对此函数提供定义后,它才能具备函数的功能,可被调用。纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。如果在基类中没有保留函数名字,则无法实现多态性。如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数。第35页/共52页第三十六页,共53页。如果声明了一个类,一般可以用它定义对象。但是在面向对象程序设计中,往往有一些类,它们不用来(ynli)生成对象。定义这些类的惟一目的是用它作为基类去建立派生类。它们作为一种基本类型提供给用户,用户在这个基础上根据自己

54、的需要定义出功能各异的派生类。用这些派生类去建立对象。一个优秀的软件工作者在开发一个大的软件时,决不会从头到尾都由自己编写程序代码,他会充分利用已有资源(例如类库)作为自己工作的基础。这种不用来(ynli)定义对象而只作为一种基本类型用作继承的类,称为抽象类(abstractclass),由于它常用作基类,通常称为抽象基类(abstractbaseclass)。6.4.2 6.4.2 抽象类抽象类抽象类抽象类第36页/共52页第三十七页,共53页。凡是包含纯虚函数的类都是抽象类。因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的。抽象类的作用是作为一个类族的共同基类,或者说,为一个类

55、族提供(tgng)一个公共接口。一个类层次结构中当然也可不包含任何抽象类,每一层次的类都是实际可用的,可以用来建立对象的。但是,许多好的面向对象的系统,其层次结构的顶部是一个抽象类,甚至顶部有好几层都是抽象类。如果在抽象类所派生出的新类中对基类的所有纯虚函数进行了定义,那么这些函数就被赋予了功能,可以被调用。这个派生类就不是抽象类,而是可以用来定义对象的具体类(concreteclass)。如果在派生类中没有对所有纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。第37页/共52页第三十八页,共53页。虽然抽象类不能定义对象(或者说抽象类不能实例化),但是可以定义指向抽象类数据的指针

56、变量。当派生类成为具体类之后,就可以用这种指针指向派生类对象,然后通过该指针调用虚函数,实现(shxin)多态性的操作。第38页/共52页第三十九页,共53页。例6.4虚函数和抽象基类的应用。在本章例6.1介绍了以Point为基类的点圆圆柱体类的层次结构。现在要对它进行改写,在程序中使用虚函数和抽象基类。类的层次结构的顶层是抽象基类Shape(形状(xngzhun)。Point(点),Circle(圆),Cylinder(圆柱体)都是Shape类的直接派生类和间接派生类。下面是一个完整的程序,为了便于阅读,分段插入了一些文字说明。程序如下:6.4.3 6.4.3 应用应用应用应用(yngyng

57、)(yngyng)实例实例实例实例第39页/共52页第四十页,共53页。第第(1)(1)部分部分(bfen)(bfen)#include#includeusingnamespacestd;usingnamespacestd;/声明抽象基类声明抽象基类ShapeShapeclassShapeclassShapepublic:public:virtualfloatarea()constreturnvirtualfloatarea()constreturn0.0;/0.0;/虚函数虚函数virtualfloatvolume()constreturnvirtualfloatvolume()constr

58、eturn0.0;/0.0;/虚函数虚函数virtualvoidshapeName()const=0;virtualvoidshapeName()const=0;/纯虚函数纯虚函数;第40页/共52页第四十一页,共53页。第第(2)(2)部分部分/声明声明PointPoint类类classPoint:publicShape/PointclassPoint:publicShape/Point是是ShapeShape的公用派生类的公用派生类 public:public:Point(float=0,float=0);Point(float=0,float=0);voidsetPoint(float,

59、float);voidsetPoint(float,float);floatgetX()constreturnx;floatgetX()constreturnx;floatgetY()constreturny;floatgetY()constreturny;virtualvoidshapeName()constcoutPoint:;/virtualvoidshapeName()constcoutPoint:;/对虚函数对虚函数(hnsh)(hnsh)进行再定义进行再定义friendostream&operator(ostream&,constPoint&);friendostream&oper

60、ator(ostream&,constPoint&);protected:protected:floatx,y;floatx,y;/定义定义PointPoint类成员函数类成员函数(hnsh)(hnsh)Point:Point(floata,floatb)Point:Point(floata,floatb)x=a;y=b;x=a;y=b;第41页/共52页第四十二页,共53页。void Point:setPoint(float a,float b)void Point:setPoint(float a,float b)x=a;y=b;x=a;y=b;ostream & operator(ost

61、ream &output,const Point &p)ostream & operator(ostream &output,const Point &p) outputp.x,p.y; outputp.x,p.y; return output; return output; 第第(3)(3)部分部分/ /声明声明(shngmng)Circle(shngmng)Circle类类class Circle:public Pointclass Circle:public Pointpublic:public: Circle(float x=0,float y=0,float r=0); Circle

62、(float x=0,float y=0,float r=0); void setRadius(float); void setRadius(float); float getRadius( ) const; float getRadius( ) const; virtual float area( ) const; virtual float area( ) const; virtual void shapeName( ) const coutCircle:;/ virtual void shapeName( ) const coutCircle:;/对虚函数进行再定义对虚函数进行再定义 f

63、riend ostream &operator(ostream &,const Circle &); friend ostream &operator(ostream &,const Circle &);第42页/共52页第四十三页,共53页。 protected: protected: float radius; float radius; ;/ /声明声明(shngmng)Circle(shngmng)Circle类成员函数类成员函数Circle:Circle(float a,float b,float r):Point(a,b),radius(r) Circle:Circle(float

64、 a,float b,float r):Point(a,b),radius(r) void Circle:setRadius(float r):radius(r) void Circle:setRadius(float r):radius(r) float Circle:getRadius( ) const return radius;float Circle:getRadius( ) const return radius;float Circle:area( ) const return 3.14159*radius*radius;float Circle:area( ) const re

65、turn 3.14159*radius*radius;ostream &operator(ostream &output,const Circle &c)ostream &operator(ostream &output,const Circle &c) outputc.x,c.y, r=c.radius; outputc.x,c.y, r=c.radius; return output; return output; 第43页/共52页第四十四页,共53页。第第(4)(4)部分部分(bfen)(bfen)/声明声明CylinderCylinder类类classCylinder:publicC

66、ircleclassCylinder:publicCirclepublic:public:Cylinder(floatx=0,floaty=0,floatCylinder(floatx=0,floaty=0,floatr=0,floath=0);r=0,floath=0);voidsetHeight(float);voidsetHeight(float);virtualfloatarea()const;virtualfloatarea()const;virtualfloatvolume()const;virtualfloatvolume()const;virtualvoidshapeName(

67、)constvirtualvoidshapeName()constcoutCylinder:;coutCylinder:;/对虚函数进行再定义对虚函数进行再定义friendostream&operator(ostream&,constfriendostream&operator(ostream&,constCylinder&);Cylinder&);protected:protected:floatheight;floatheight;第44页/共52页第四十五页,共53页。/ /定义定义CylinderCylinder类成员类成员(chngyun)(chngyun)函数函数Cylinder:

68、Cylinder(float a,float b,float r,float h)Cylinder:Cylinder(float a,float b,float r,float h) :Circle(a,b,r),height(h) :Circle(a,b,r),height(h) void Cylinder:setHeight(float h)height=h;void Cylinder:setHeight(float h)height=h;float Cylinder:area( ) constfloat Cylinder:area( ) const return 2*Circle:are

69、a( )+2*3.14159*radius*height; return 2*Circle:area( )+2*3.14159*radius*height;float Cylinder:volume( ) constfloat Cylinder:volume( ) constreturn Circle:area( )*height;return Circle:area( )*height;ostream &operator(ostream &output,const Cylinder& cy)ostream &operator(ostream &output,const Cylinder& c

70、y) outputcy.x,cy.y, r=cy.radius outputcy.x,cy.y, r=cy.radius , h=cy.height; , h=cy.height; return output; return output; 第45页/共52页第四十六页,共53页。第第(5)(5)部分部分/main/main函数函数intmain()intmain()Pointpoint(3.2,4.5);/Pointpoint(3.2,4.5);/建立建立(jinl)Point(jinl)Point类对象类对象pointpointCirclecircle(2.4,1.2,5.6);/Circ

71、lecircle(2.4,1.2,5.6);/建立建立(jinl)Circle(jinl)Circle类对象类对象circlecircleCylindercylinder(3.5,6.4,5.2,10.5);/Cylindercylinder(3.5,6.4,5.2,10.5);/建立建立(jinl)Cylinder(jinl)Cylinder类对象类对象cylindercylinderpoint.shapeName();/point.shapeName();/静态关联静态关联coutpointendl;coutpointendl;circle.shapeName();/circle.shap

72、eName();/静态关联静态关联coutcircleendl;coutcircleendl;cylinder.shapeName();/cylinder.shapeName();/静态关联静态关联coutcylinderendlendl;coutcylinderendlshapeName( ); / pt-shapeName( ); /动态动态(dngti)(dngti)关联关联 coutx=point.getX( ),y=point.getY( )narea=area( ) coutx=point.getX( ),y=point.getY( )narea=area( ) nvolume=v

73、olume()nn; nvolume=volume()shapeName( ); /pt-shapeName( ); /动态动态(dngti)(dngti)关联关联 coutx=circle.getX( ),y=circle.getY( )narea= coutx=circle.getX( ),y=circle.getY( )narea= area( ) nvolume=volume( )nn; area( ) nvolume=volume( )shapeName( ); / pt-shapeName( ); /动态动态(dngti)(dngti)关联关联 coutx=cylinder.get

74、X( ),y=cylinder.getY( )narea= coutx=cylinder.getX( ),y=cylinder.getY( )narea= area( ) nvolume=volume( )nn; area( ) nvolume=volume( )nn; return 0; return 0; 第47页/共52页第四十八页,共53页。程序程序(chngx)(chngx)运行结果如下。请读者对照程序运行结果如下。请读者对照程序(chngx)(chngx)分析。分析。Point:3.2,4.5(PointPoint:3.2,4.5(Point类对象类对象pointpoint的数据的

75、数据:点的坐标点的坐标) )Circle:2.4,1.2,r=5.6(CircleCircle:2.4,1.2,r=5.6(Circle类对象类对象circlecircle的数的数据据:圆心和半径圆心和半径) )Cylinder:3.5,6.4,r=5.5,h=10.5(CylinderCylinder:3.5,6.4,r=5.5,h=10.5(Cylinder类对象类对象cylindercylinder的数据:的数据: 圆心、半径和高圆心、半径和高) )Point:x=3.2,y=4.5(Point:x=3.2,y=4.5(输出输出PointPoint类对象类对象pointpoint的的数据

76、数据:点的坐标点的坐标) )area=0(area=0(点的面积点的面积) )volume=0(volume=0(点的体积点的体积) )Circle:x=2.4,y=1.2(Circle:x=2.4,y=1.2(输出输出CircleCircle类对象类对象circlecircle的数据的数据:圆心坐标圆心坐标) )area=98.5203(area=98.5203(圆的面积圆的面积) )volume=0(volume=0(圆的体积圆的体积) )Cylinder:x=3.5,y=6.4(Cylinder:x=3.5,y=6.4(输出输出CylinderCylinder类对象类对象cylinder

77、cylinder的数据的数据:圆心坐标圆心坐标) )area=56.595(area=56.595(圆的面积圆的面积) )volume=891.96(volume=891.96(圆柱的体积圆柱的体积) )第48页/共52页第四十九页,共53页。从本例可以进一步明确以下(yxi)结论:(1)一个基类如果包含一个或一个以上纯虚函数,就是抽象基类。抽象基类不能也不必要定义对象。(2)抽象基类与普通基类不同,它一般并不是现实存在的对象的抽象(例如圆形Circle类就是千千万万个实际的圆的抽象),它可以没有任何物理上的或其他实际意义方面的含义。(3)在类的层次结构中,顶层或最上面的几层可以是抽象基类。抽

78、象基类体现了本类族中各类的共性,把各类中共有的成员函数集中在抽象基类中声明。(4)抽象基类是本类族的公共接口。或者说,从同一基类派生出的多个类有同一接口。(5)区别静态关联和动态关联。第49页/共52页第五十页,共53页。(6)如果在基类声明了虚函数,则在派生类中凡是与该函数有相同(xintn)的函数名、函数类型、参数个数和类型的函数,均为虚函数(不论在派生类中是否用virtual声明)。(7)使用虚函数提高了程序的可扩充性。把类的声明与类的使用分离。这对于设计类库的软件开发商来说尤为重要。开发商设计了各种各样的类,但不向用户提供源代码,用户可以不知道类是怎样声明的,但是可以使用这些类来派生出

79、自己的类。利用虚函数和多态性,程序员的注意力集中在处理普遍性,而让执行环境处理特殊性。第50页/共52页第五十一页,共53页。多态性把操作(cozu)的细节留给类的设计者(他们多为专业人员)去完成,而让程序人员(类的使用者)只需要做一些宏观性的工作,告诉系统做什么,而不必考虑怎么做,极大地简化了应用程序的编码工作,大大减轻了程序员的负担,也降低了学习和使用C+编程的难度,使更多的人能更快地进入C+程序设计的大门。第51页/共52页第五十二页,共53页。内容(nirng)总结会计学。动态多态性是在程序运行过程中才动态地确定(qudng)操作所针对的对象。c.setPoint(5,5)。cy1.setPoint(5,5)。可以看到,在编译时编译系统即可以判定应调用哪个重载运算符函数。或者说,从同一基类派生出的多个类有同一接口。这对于设计类库的软件开发商来说尤为重要第五十三页,共53页。

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 高等教育 > 研究生课件

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号