谭浩强《C++程序设计》课件 第12章.ppt
《谭浩强《C++程序设计》课件 第12章.ppt》由会员分享,可在线阅读,更多相关《谭浩强《C++程序设计》课件 第12章.ppt(49页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、第第12章章 多态性与虚函数多态性与虚函数12.1 多态性的概念多态性的概念12.2 一个典型的例子一个典型的例子12.3 虚函数虚函数12.4 纯虚函数与抽象类纯虚函数与抽象类多态性多态性(polymorphism)是面向对象程序设计的一个重是面向对象程序设计的一个重要特征。利用多态性可以设计和实现一个易于扩展的要特征。利用多态性可以设计和实现一个易于扩展的系统。系统。在在C+程序设计中,多态性是指具有不同功能的函数程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述
2、不同内容的函数。在面向对象方法中一般是这样表述多态性的多态性的:向不同的对象发送同一个消息,不同的对向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为象在接收时会产生不同的行为(即方法即方法)。也就是说,。也就是说,每个对象可以用自己的方式去响应共同的消息。每个对象可以用自己的方式去响应共同的消息。12.1 多态性的概念多态性的概念在在C+程序设计中,在不同的类中定义了其响应消息程序设计中,在不同的类中定义了其响应消息的方法,那么使用这些类时,不必考虑它们是什么类的方法,那么使用这些类时,不必考虑它们是什么类型,只要发布消息即可。型,只要发布消息即可。从系统实现的角度看,多态性分为
3、两类从系统实现的角度看,多态性分为两类:静态多态性静态多态性和动态多态性。以前学过的函数重载和运算符重载实和动态多态性。以前学过的函数重载和运算符重载实现的多态性属于静态多态性,在程序编译时系统就能现的多态性属于静态多态性,在程序编译时系统就能决定调用的是哪个函数,因此静态多态性又称编译时决定调用的是哪个函数,因此静态多态性又称编译时的多态性。静态多态性是通过函数的重载实现的的多态性。静态多态性是通过函数的重载实现的(运运算符重载实质上也是函数重载算符重载实质上也是函数重载)。动态多态性是在程。动态多态性是在程序运行过程中才动态地确定操作所针对的对象。它又序运行过程中才动态地确定操作所针对的对
4、象。它又称运行时的多态性。动态多态性是通过虚函数称运行时的多态性。动态多态性是通过虚函数(virtual function)实现的。实现的。有关静态多态性的应用已经介绍过了,在本章中主要有关静态多态性的应用已经介绍过了,在本章中主要介绍动态多态性和虚函数。介绍动态多态性和虚函数。要研究的问题是要研究的问题是:当一个基类被继承为不同的派生类当一个基类被继承为不同的派生类时,各派生类可以使用与基类成员相同的成员名,如时,各派生类可以使用与基类成员相同的成员名,如果在运行时用同一个成员名调用类对象的成员,会调果在运行时用同一个成员名调用类对象的成员,会调用哪个对象的成员?也就是说,通过继承而产生了相
5、用哪个对象的成员?也就是说,通过继承而产生了相关的不同的派生类,与基类成员同名的成员在不同的关的不同的派生类,与基类成员同名的成员在不同的派生类中有不同的含义。也可以说,多态性是派生类中有不同的含义。也可以说,多态性是“一个一个接口,多种方法接口,多种方法”。下面是一个承上启下的例子。一方面它是有关继承和下面是一个承上启下的例子。一方面它是有关继承和运算符重载内容的综合应用的例子,通过这个例子可运算符重载内容的综合应用的例子,通过这个例子可以进一步融会贯通前面所学的内容,另一方面又是作以进一步融会贯通前面所学的内容,另一方面又是作为讨论多态性的一个基础用例。为讨论多态性的一个基础用例。12.2
6、 一个典型的例子一个典型的例子例例12.1 先建立一个先建立一个Point(点点)类,包含数据成员类,包含数据成员x,y(坐坐标点标点)。以它为基类,派生出一个。以它为基类,派生出一个Circle(圆圆)类,增加类,增加数据成员数据成员r(半径半径),再以,再以Circle类为直接基类,派生出类为直接基类,派生出一个一个Cylinder(圆柱体圆柱体)类,再增加数据成员类,再增加数据成员h(高高)。要。要求编写程序,重载运算符求编写程序,重载运算符“”,使之能用,使之能用于输出以上类对象。于输出以上类对象。对于一个比较大的程序,应当分成若干步骤进行。先对于一个比较大的程序,应当分成若干步骤进行
7、。先声明基类,再声明派生类,逐级进行,分步调试。声明基类,再声明派生类,逐级进行,分步调试。(1)声明基类声明基类Point类类可写出声明基类可写出声明基类Point的部分如下的部分如下:#include/声明类声明类Pointclass Pointpublic:Point(float x=0,float y=0);/有默认参数的构造函数有默认参数的构造函数 void setPoint(float,float);/设置坐标值设置坐标值 float getX()const return x;/读读x坐标坐标 float getY()const return y;/读读y坐标坐标 friend o
8、stream&operator(ostream&,const Point&);/重载运算符重载运算符“”protected:/受保护成员受保护成员 float x,y;/下面定义下面定义Point类的成员函数类的成员函数/Point的构造函数的构造函数Point:Point(float a,float b)/对对x,y初始化初始化x=a;y=b;/设置设置x和和y的坐标值的坐标值void Point:setPoint(float a,float b)/为为x,y赋新值赋新值x=a;y=b;/重载运算符重载运算符“”,使之能输出点的坐标,使之能输出点的坐标ostream&operator(ost
9、ream&output,const Point&p)outputp.x,p.yendl;return output;以上完成了基类以上完成了基类Point类的声明。类的声明。现在要对上面写的基类声明进行调试,检查它是否有现在要对上面写的基类声明进行调试,检查它是否有错,为此要写出错,为此要写出main函数。实际上它是一个测试程函数。实际上它是一个测试程序。序。int main()Point p(3.5,6.4);/建立建立Point类对象类对象p coutx=p.getX(),y=p.getY()endl;/输出输出p的坐标值的坐标值 p.setPoint(8.5,6.8);/重新设置重新设置
10、p的坐标值的坐标值 coutp(new):pendl;/用重载运算符用重载运算符“”输出输出p点坐标点坐标程序编译通过,运行结果为程序编译通过,运行结果为x=3.5,y=6.4p(new):8.5,6.8测试程序检查了基类中各函数的功能,以及运算符重测试程序检查了基类中各函数的功能,以及运算符重载的作用,证明程序是正确的。载的作用,证明程序是正确的。(2)声明派生类声明派生类Circle在上面的基础上,再写出声明派生类在上面的基础上,再写出声明派生类Circle的部分:的部分:class Circle:public Point/circle是是Point类的公用派生类类的公用派生类public
11、:Circle(float x=0,float y=0,float r=0);/构造函数构造函数 void setRadius(float);/设置半径值设置半径值 float getRadius()const;/读取半径值读取半径值 float area()const;/计算圆面积计算圆面积 friend ostream&operator(ostream&,const Circle&);/重载运算符重载运算符“”private:float radius;/定义构造函数,对圆心坐标和半径初始化定义构造函数,对圆心坐标和半径初始化Circle:Circle(float a,float b,flo
12、at r):Point(a,b),radius(r)/设置半径值设置半径值void Circle:setRadius(float r)radius=r;/读取半径值读取半径值float Circle:getRadius()const return radius;/计算圆面积计算圆面积float Circle:area()constreturn 3.14159*radius*radius;/重载运算符重载运算符“”,使之按规定的形式输出圆的信息,使之按规定的形式输出圆的信息ostream&operator(ostream&output,const Circle&c)outputCenter=c.
13、x,c.y,r=c.radius,area=c.area()endl;return output;为了测试以上为了测试以上Circle类的定义,可以写出下面的主函类的定义,可以写出下面的主函数数:int main()Circle c(3.5,6.4,5.2);/建立建立Circle类对象类对象c,并给定圆心坐标和半径并给定圆心坐标和半径 coutoriginal circle:nx=c.getX(),y=c.getY(),r=c.getRadius(),area=c.area()endl;/输出圆心坐标、半径和面积输出圆心坐标、半径和面积 c.setRadius(7.5);/设置半径值设置半径
14、值 c.setPoint(5,5);/设置圆心坐标值设置圆心坐标值x,y coutnew circle:nc;/用重载运算符用重载运算符“”输出圆对象的信息输出圆对象的信息 Point&pRef=c;/pRef是是Point类的引用变量,被类的引用变量,被c初始化初始化 coutpRef:pRef;/输出输出pRef的信息的信息 return 0;程序编译通过,运行结果为程序编译通过,运行结果为original circle:(输出原来的圆的数据输出原来的圆的数据)x=3.5,y=6.4,r=5.2,area=84.9486new circle:(输出修改后的圆的数据输出修改后的圆的数据)Ce
15、nter=5,5,r=7.5,area=176.714pRef:5,5 (输出圆的圆心输出圆的圆心“点点”的数据的数据)(3)声明声明Circle的派生类的派生类Cylinder前面已从基类前面已从基类Point派生出派生出Circle类,现在再从类,现在再从Circle派生出派生出Cylinder类。类。class Cylinder:public Circle/Cylinder是是Circle的公用派生类的公用派生类public:Cylinder(float x=0,float y=0,float r=0,float h=0);/构造函数构造函数 void setHeight(float);
16、/设置圆柱高设置圆柱高 float getHeight()const;/读取圆柱高读取圆柱高 float area()const;/计算圆表面积计算圆表面积 float volume()const;/计算圆柱体积计算圆柱体积 friend ostream&operator(ostream&,const Cylinder&);/重载运算符重载运算符“”protected:float height;/圆柱高圆柱高;/定义构造函数定义构造函数Cylinder:Cylinder(float a,float b,float r,float h):Circle(a,b,r),height(h)/设置圆柱高
17、设置圆柱高void Cylinder:setHeight(float h)height=h;/读取圆柱高读取圆柱高float Cylinder:getHeight()const return height;/计算圆表面积计算圆表面积float Cylinder:area()const return 2*Circle:area()+2*3.14159*radius*height;/计算圆柱体积计算圆柱体积float Cylinder:volume()constreturn Circle:area()*height;/重载运算符重载运算符“”ostream&operator(ostream&out
18、put,const Cylinder&cy)outputCenter=cy.x,cy.y,r=cy.radius,h=cy.height narea=cy.area(),volume=cy.volume()endl;return output;可以写出下面的主函数可以写出下面的主函数:int main()Cylinder cy1(3.5,6.4,5.2,10);/定义定义Cylinder类对象类对象cy1 coutnoriginal cylinder:nx=cy1.getX(),y=cy1.getY(),r=cy1.getRadius(),h=cy1.getHeight()narea=cy1.
19、area(),volume=cy1.volume()endl;/用系统定义的运算符用系统定义的运算符“”输出输出cy1的数据的数据 cy1.setHeight(15);/设置圆柱高设置圆柱高 cy1.setRadius(7.5);/设置圆半径设置圆半径 cy1.setPoint(5,5);/设置圆心坐标值设置圆心坐标值x,y coutnnew cylinder:ncy1;/用重载运算符用重载运算符“”输出输出cy1的数据的数据 Point&pRef=cy1;/pRef是是Point类对象的引用变量类对象的引用变量 coutnpRef as a Point:pRef;/pRef作为一个作为一个“
20、点点”输出输出 Circle&cRef=cy1;/cRef是是Circle类对象的引用变量类对象的引用变量 coutncRef as a Circle:display();”可以调用不同派生层次中的可以调用不同派生层次中的display函数,只需在函数,只需在调用前给指针变量调用前给指针变量pt赋以不同的值赋以不同的值(使之指向不同的使之指向不同的类对象类对象)即可。即可。C+中的虚函数就是用来解决这个问题的。虚函数的中的虚函数就是用来解决这个问题的。虚函数的作用是允许在派生类中重新定义与基类同名的函数,作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类
21、中并且可以通过基类指针或引用来访问基类和派生类中的同名函数。的同名函数。请分析例请分析例12.2。这个例子开始时没有使用虚函数,然。这个例子开始时没有使用虚函数,然后再讨论使用虚函数的情况。后再讨论使用虚函数的情况。例例12.2 基类与派生类中有同名函数。基类与派生类中有同名函数。在下面的程序中在下面的程序中Student是基类,是基类,Graduate是派生类,是派生类,它们都有它们都有display这个同名的函数。这个同名的函数。#include#include using namespace std;/声明基类声明基类Studentclass Studentpublic:Student(
22、int,string,float);/声明构造函数声明构造函数 void display();/声明输出函数声明输出函数 protected:/受保护成员,派生类可以访问受保护成员,派生类可以访问 int num;string name;float score;/Student类成员函数的实现类成员函数的实现Student:Student(int n,string nam,float s)/定义构造函数定义构造函数 num=n;name=nam;score=s;void Student:display()/定义输出函数定义输出函数coutnum:numnname:namenscore:scor
23、enn;/声明公用派生类声明公用派生类Graduateclass Graduate:public Studentpublic:Graduate(int,string,float,float);/声明构造函数声明构造函数 void display();/声明输出函数声明输出函数private:float pay;/Graduate类成员函数的实现类成员函数的实现void Graduate:display()/定义输出函数定义输出函数 coutnum:numnname:namenscore:scorenpay=paydisplay();pt=&grad1;pt-display();return 0
24、;运行结果如下,请仔细分析。运行结果如下,请仔细分析。num:1001(stud1的数据的数据)name:Liscore:87.5num:2001 (grad1中基类部分的数据中基类部分的数据)name:wangscore:98.5下面对程序作一点修改,在下面对程序作一点修改,在Student类中声明类中声明display函数时,在最左面加一个关键字函数时,在最左面加一个关键字virtual,即即virtual void display();这样就把这样就把Student类的类的display函数声明为虚函数。程函数声明为虚函数。程序其他部分都不改动。再编译和运行程序,请注意分序其他部分都不改
25、动。再编译和运行程序,请注意分析运行结果析运行结果:num:1001(stud1的数据的数据)name:Liscore:87.5num:2001 (grad1中基类部分的数据中基类部分的数据)name:wangscore:98.5pay=1200 (这一项以前是没有的这一项以前是没有的)由虚函数实现的动态多态性就是由虚函数实现的动态多态性就是:同一类族中不同类同一类族中不同类的对象,对同一函数调用作出不同的响应。虚函数的的对象,对同一函数调用作出不同的响应。虚函数的使用方法是使用方法是:(1)在基类用在基类用virtual声明成员函数为虚函数。这样就声明成员函数为虚函数。这样就可以在派生类中重
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+程序设计 谭浩强C+程序设计课件 第12章 谭浩强 C+ 程序设计 课件 12
限制150内