《虚函数与多态性》PPT课件.ppt

上传人:sh****n 文档编号:13664520 上传时间:2020-06-23 格式:PPT 页数:66 大小:1.52MB
收藏 版权申诉 举报 下载
《虚函数与多态性》PPT课件.ppt_第1页
第1页 / 共66页
《虚函数与多态性》PPT课件.ppt_第2页
第2页 / 共66页
《虚函数与多态性》PPT课件.ppt_第3页
第3页 / 共66页
资源描述:

《《虚函数与多态性》PPT课件.ppt》由会员分享,可在线阅读,更多相关《《虚函数与多态性》PPT课件.ppt(66页珍藏版)》请在装配图网上搜索。

1、1,第二部分 面向对象的程序设计,第3章 类和对象(一) 第4章 类和对象(二) 第5章 继承和派生 第6章 虚函数与多态性 第7章 运算符重载 第8章 模板 第9章 标准模板库STL 第10章 C+语言的输入和输出,2,第6章 虚函数与多态性,本章要点: 多态性的概念 虚函数的定义与应用 多继承与虚函数 纯虚函数与抽象类,3,多态性(Polymorphism)是面向对象程序设计的一个非常重要的特性,如果不支持多态性,C+就不是真正的面向对象程序设计语言。 多态性指的是不同的对象对于同样的消息会产生不同的行为,而消息在C+语言中指的就是函数的调用,不同的函数可以具有多种不同的功能,而多态就是允

2、许用一个函数名的调用来执行不同的功能。,4,6.1多态性概述,6.1.1多态的类型 多态性不仅限于C+语言,从面向对象技术的角度来看,多态性可以分为四类: (1)重载多态,前面学习的函数重载就属于此概念,运算符重载也是重载多态(第7章将详细介绍)。 (2)强制多态,指将一个变量类型加以变化,以符合一个函数或者操作的要求,例如加法运算符在进行浮点数与整型数相加时,首先要对整型数进行强制类型转换为浮点数再相加的情况,就是强制多态的实例。 (3)包含多态,同样的操作可用于一个类型及其子类型。包含多态一般需要进行运行时的类型检查,主要是通过虚函数来实现。 (4)参数多态,采用参数化模板,通过给出不同的

3、类型参数,使得一个结构可以适用多种数据类型,C+提供的函数模板和类模板即为典型的参数多态(第8章将详细介绍)。,5,对于多态性,一个要解决的主要问题就是何时把具体的操作和对象进行绑定(binding),也称联编、关联,绑定也指的是程序如何为类的对象找到执行操作函数的程序入口的过程。 从系统实现的角度来看,多态可以分为两类: 编译时多态 运行时多态,6,编译时多态,指的在程序编译过程中时决定同名操作与对象的绑定关系,也称静态绑定、静态联编,典型的技术有函数重载、运算符重载、模板。 由于这种方式是在程序运行前就确定了对象要调用的具体函数,因此程序运行的时候函数调用速度快、效率较高。 其缺点是编程不

4、够灵活。,7,运行时多态,指的是在程序运行过程中动态地确定同名操作与具体对象的绑定关系,也称动态绑定、动态联编等,主要通过使用继承和虚函数来实现。 在编译、连接过程中确定绑定关系,程序运行之后才能确定。 动态绑定的优点是编程更加灵活、系统易于扩展。 由于内部增加了实现虚函数调用的机制,因此要比静态绑定的函数调用速度慢些。,8,6.1.2 基类指针指向派生类对象,【例6 .1】 函数重载在多态性中的应用。 /* 06_01.cpp */ #include using namespace std; class Base/基类 public : void Print() cout”Base Clas

5、s Print.”endl; ;,9,class Derived:public Base /公有派生类 public: void Print() coutPrint(); pb= ,10,Base Class Print. Derived Class Print. Base Class Print. Base Class Print. Base Class Print.,程序的运行结果为:,11,在派生类中重写了成员函数Print(),使得不同的对象对同一函数名的调用产生了不同的结果,这就是多态性的具体表现,不过却是编译时多态。 在程序中可以看到,要想输出对应的字符串,必须明确地指出调用哪个对

6、象的成员函数,即通过类的对象调用,或者通过加类名限定进行调用,如例6.l 中的语句: bl.Print(); /调用基类对象b1 的Print 函数 d1.Print(); /调用派生类对象d1 的Print 函数 d1.Base:Print() ; /调用派生类对象d1中的继承基类的Print()函数,12,这样调用成员函数的方法是显示调用,对于结构复杂的程序,每个对象都显式地写出要调用的成员函数是不切实际的,往往需要通过对象指针或引用来实现对成员函数的调用。 程序结果可以看出,在例6.1中定义了一个基类的指针pb,当把这个指针指向派生类的对象时,希望调用派生类对象d1的Print()函数,

7、但却仍然调用了d1对象包含的基类成员函数Print()。即本程序希望运行结果为: Base Class Print. Sub Class Print. Base Class Print. Base Class Print. Sub Class Print.,13,6.2 虚函数,6.2.1虚函数的定义格式 虚函数必须存在于类的继承环境之中才有意义,声明虚函数的方法很简单,只要在基类的成员函数名前加关键字virtual即可,格式如下: class 类名 virtual 类型 成员函数名(参数表); ;,14,定义虚函数要注意以下的问题: (1)虚函数必须声明为类的成员函数,全局函数及静态成员函数

8、不能声明为虚函数; (2)虚函数与一般成员函数一样,可定义在类体内,也可以定义在类体外; (3)虚函数的声明只能出现在类函数声明语句中,而不能在成员函数实现的部分声明; (4)构造函数不能声明为虚函数; (5)析构函数可以是虚函数; (6)当一个基类中声明了虚函数,则虚函数特性会在其直接派生类和间接派生类中一直保持下去,并且其派生类不必再用virtual关键字声明。,15,【例6.2】 虚函数的定义与应用(对例6.1的改进)。,/* 06_02.cpp */ #include using namespace std; class Base/基类 public : virtual void Pr

9、int() cout”Base Class Print.”endl; ; class Derived1:public Base /公有派生类1 public: void Print() cout” Derived1 Class Print.”endl; ;,16,class Derived2:public Base /公有派生类2 public: void Print() coutPrint(); /用指针调用成员函数 rb.Print(); /用基类引用调用基类对象成员函数 pb= ,17,Base Class Print. Base Class Print. Derived1 Class

10、Print. Derived2 Class Print. Derived1 Class Print.,程序的运行结果为:,18,6.2.2多继承与虚函数,前面的内容介绍了在一个基类中定义虚函数,然后定义派生类的使用情况,那么在C+多继承机制当中,虚函数问题该如何处理呢? 见例6.3中的程序:,19,【例6.3】 多继承中虚函数的定义与应用。,/* 06_03.cpp */ #include using namespace std; class Base1 public: virtual void TestA() cout”Base1 TestA()”endl; ; class Base2 pu

11、blic: virtual void TestB() cout”Base2 TestB()”TestB(); / 调用类Derived的TestB()函数 return 0; ,21,Derived TestA() Derived TestB(),程序的运行结果为:,22,图6.1 多继承与虚函数,23,【例6.4】 多继承基类中有同名虚函数。,/* 06_04.cpp */ #include using namespace std; class Base1 public: virtual void Test() cout”Base1 Test()”endl; ; class Base2 pu

12、blic: virtual void Test() cout”Base2 Test()”Test(); /用基类指针pB2调用类Derived的TestB()函数 return 0; ,30,Derived TestA() Derived TestB(),程序的运行结果为:,31,图6.2 中间类的作用,32,6.2.3 虚析构函数,析构函数的作用是在对象撤销之前做必要的“清理现场”的工作。 当派生类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数。但是,如果用new 运算符动态生成一个派生类的堆对象,并让基类指针指向该派生类对象。 当程序用delete运算符通过基类指

13、针删除派生类对象时,会发生一种情况:系统会只执行基类的析构函数,而不执行派生类的析构函数。,33,【例6.6】基类中有非虚析构函数时的执行情况。,/* 06_06.cpp */ #include using namespace std; class Base /定义基类Base public: Base() /Base类构造函数 coutConstruct Base.endl; Base() /Base类析构函数 coutDeconstruct Base.endl; ;,34,class Derived:public Base /定义公有派生类Derived public: Derived()

14、 /Derived类构造函数 coutConstruct Derived.endl; Derived() /Derived类析构函数 coutDeconstruct Derived.endl; ; int main() /主函数测试 Base *pb; /定义基类指针 pb=new Derived(); /基类指针指向新生成的派生类堆对象 delete pb ; return 1; ,35,Construct Base. Construct Derived. Destruct Base.,程序的运行结果为:,36,在程序的main()函数中,pb是基类的指针,指向了一个派生类Derived的堆

15、对象。希望用delete释放pb所指向的空间。但运行结果为: Destruct Base. 表示只执行了基类Base的析构函数,而没有执行派生类Derived的析构函数。如果希望执行派生类Derived的析构函数,则应将基类的析构函数声明为虚析构函数,例如: virtual Base() /Base类析构函数 coutDeconstruct Base.endl; 程序其他部分不改动,再运行程序,其结果为: Destruct Derived. Destruct Base.,37,如果将基类的析构函数声明为虚函数时,由该基类所派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类

16、的析构函数名字不同。 最好把基类的析构函数声明为虚函数。这将使所有派生类的析构函数自动成为虚函数。这样,如果程序中显式地用了delete 运算符准备删除一个对象,而delete 运算符的操作对象用了指向派生类对象的基类指针,则系统会调用相应类的析构函数。,38,虚析构函数的概念和用法很简单,但它在面向对象程序设计中却是很重要的技巧。专业人员一般都习惯声明虚析构函数,即使基类并不需要析构函数,也显式地定义一个函数体为空的虚析构函数,以保证在撤销动态分配空间时能得到正确的处理。 构造函数不能声明为虚函数。这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上函数与类对象的绑定。,39,6 .

17、3 纯虚函数和抽象类,6.3.1纯虚函数 有时在基类中将某一成员函数声明为虚函数,并不是类本身的要求,而是考虑到派生类的需要,在基类中只定义一个函数名,具体功能留给派生类根据需要去实现。 因此可以对这种虚函数只在基类中说明函数原型,用来定义继承体系中的统一接口形式,然后在派生类的虚函数中重新定义具体实现代码,而这种基类中的虚函数就是纯虚函数,其声明一般形式为: virtual 函数类型 函数名(参数表)=0 ;,40,关于纯虚函数,有以下问题需要说明:,(1)纯虚函数没有函数体; (2)最后面的“= 0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”; (3)这是一个

18、声明语句,后面应有分号; (4)纯虚函数只有函数的名字而不具备函数的功能,不能被调用,它只是通知编译器在这时声明一个虚函数,留待派生类中定义。在派生类中对此函数提供定义后,它才能具备函数的功能,可以被调用。 如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数。,41,【例6.7】 分析下列程序的输出的结果。,/* 06_07.cpp */ #include using namespace std; class Vehicle /定义交通工具类 protected: int pos,speed; /定义成员变量位置和速度 public: Vehicl

19、e(int ps=0,int spd=0) /构造函数 pos=ps ;speed=spd ; void SetSpeed(int spd) /设置速度值 speed=spd ; void Show() /显示交通工具位置 cout”Position at ”posendl; virtual void Run()=0; /声明纯虚函数Run() ;,42,class Car:public Vehicle /小汽车类 public: void Run() /重写虚函数Run() pos+=speed; /位置变化 ; int main() /主函数 Vehicle *pvh; Car car;

20、pvh= ,43,Position at 0 Position at 5 Position at 10,程序的运行结果为:,程序分析:该程序在基类Vehicle中定义了一个纯虚函数Run(),其形式如下: virtual void Run()= 0; 程序中用Vehicle类定义的指针指向了一个Car类的实例car,然后就可以调用纯虚函数Run(),从而实现动态绑定的效果。,44,6.3.2抽象类,包含有纯虚函数的类是抽象类。 由于抽象类常用作基类,通常称为抽象基类。 抽象基类的主要作用是: 通过它为一个类族建立一个公共的接口,使它们能够更有效地发挥多态特性。 抽象基类声明了一族派生类的共同接

21、口,而接口的具体实现代码,即纯虚函数的函数体,要由派生类自己定义。,45,抽象类派生出新的类之后,如果派生类给出所有纯虚函数的函数实现,这个派生类就可以定义自己的对象,因而不再是抽象类;反之,如果派生类没有给出全部纯虚函数的实现,这时的派生类仍然是一个抽象类。 抽象类不能实例化,即不能定义一个抽象类的对象,但是,可以声明一个抽象类的指针或引用。通过指针或引用,就可以指向并访问派生类对象。进而访问派生类的成员,这种访问是具有多态特征的。用抽象类实现多态性。,46,【例6.8】设计一个抽象类Shape,用来表示形状的抽象概念,并定义求面积Area()和打印Print()两个纯虚函数,然后设计圆类和

22、矩形两个派生类,各自重写基类中的虚函数。,/* 06_08.cpp */ #include using namespace std; class Shape protected: double x,y; public: Shape(double a,double b) /构造函数 x=a; y=b; virtual double Area()=0; /求面积函数,声明为虚函数 virtual void Print() =0; /打印输出形状信息,声明为纯虚函数 ;,47,/定义圆派生类 class Circle : public Shape private: double radius; /半

23、径 public: Circle(double r=0,double a=0, double b=0); double Area(); void Print(); ; Circle:Circle(double r, double a, double b) : Shape(a,b) radius=r; double Circle:Area() /实现求圆形面积 return 3.1416*radius*radius; void Circle:Print() /打印输出圆形信息 coutCircle Center=(x,y),Radius=radiusendl; ,48,/定义矩形派生类 clas

24、s Rectangle: public Shape private: double width,height; /矩形宽和高 public: Rectangle(double a=0, double b=0,double w=0,double h=0); double Area(); void Print(); ; Rectangle:Rectangle(double a, double b,double w,double h) : Shape(a,b) width=w; height=h; double Rectangle:Area() /实现求矩形面积 return width*heigh

25、t; void Rectangle:Print() /打印输出矩形信息 coutRectangle Position=(x,y),Size=( width,height)endl; ,49,int main() Shape *ps1,*ps2; /定义抽象类的指针变量 /Shape s1(5,10); /如果不注释该语句,编译程序时将出错 Circle c1(10, 30, 15); /圆对象 Rectangle r1(20,20,100,40); /矩形对象 ps1= ,50,Circle Center=(30,15),Radius=10 Area=314.16 Rectangle Posi

26、tion=(20,20),Size=(100,40) Area=4000,程序的运行结果为:,51,6.4 综合应用举例,本节以一个小型的汽车信息处理程序为例,说明虚函数、抽象类应用。有一个ASCII文件存储不同类型汽车信息: 普通汽车Car 卡车Truck 吊车Crane 编写三个类实现这三种汽车类,并且设计虚函数Input()读入对应车型的有关信息,然后按照指定的格式输出到屏幕,文件名设为autos.txt。 文件中的汽车信息如表6.1所示。,52,表6.1 文件中的汽车信息,53,读入以上文件实例内容后,将以如下格式输出: Style: Car Manufacturer: Volkswa

27、gen Passenger: 5 Style: Truck Manufacturer: GM Passenger: 2 Load: 5 Style: Crane Manufacture: Ford Passenger: 2 Load: 4 Height: 20,54,【例6.9】 汽车信息处理程序。,/* 06_09.cpp */ #include #include using namespace std; /定义汽车抽象基类 class Auto protected: string stypename; /类型名 int npassengers; /乘客数量 string smanufact

28、urer; /厂商名称 public: Auto() /构造函数 stypename=Auto; npassengers=0; smanufacturer=no manufacturer; ,55,virtual Auto() /虚析构函数 /静态函数TrimLine(),用于整理字符串,去掉串尾部换行字符 static void TrimLine(char *sbuf) while(sbuf!=0) if(*sbuf=r|*sbuf=n) *sbuf=0; break; sbuf+; virtual bool Input(FILE *fp)=0;/纯虚函数Input(),输入数据 virtu

29、al void Show()=0; /按照指定的格式输出车的信息 ;,56,/普通汽车Car类 class Car:public Auto public: Car() /构造函数 stypename=Car; /车型名称为Car /重写虚函数Input() bool Input(FILE *fp) char sbuf100; fgets(sbuf,100,fp); /读入一行字符串(包括换行符) TrimLine(sbuf); /去掉换行符 smanufacturer=sbuf; /生产厂商字符串 fgets(sbuf,100,fp); /再读一行 npassengers=atoi(sbuf)

30、;/atoi()函数实现把字符串内容转换为整数 return true; ,57,/重写显示车信息的虚函数Show() void Show() coutStyle: stypenameendl; coutManufacturer: smanufacturerendl; coutPassenger: npassengersendl; ;,58,/卡车Truck类,从Car类派生 class Truck:public Car protected: float fload; /载重量 public: Truck() /卡车类构造函数 stypename=Truck; /车型名称 fload=0; /

31、重写虚函数Input() bool Input(FILE *fp) char sbuf100; Car:Input(fp); /调用基类的Input()函数 fgets(sbuf,100,fp); /读入一行数据,载重值 fload=atof(sbuf); /atof()函数实现把字符串内容转换为浮点数 return true; ,59,/重写显示卡车信息的虚函数Show() void Show() Car:Show(); /调用基类的输出函数Show() coutLoad: floadendl; /输出卡车的载重值 ; /吊车Crane类,从Car类派生 class Crane:public

32、 Truck protected: float fheight; /吊车的举物高度 public: Crane() stypename=Crane; fheight=0; ,60,/重写虚函数Input() bool Input(FILE *fp) char sbuf100; Truck:Input(fp); fgets(sbuf,100,fp); fheight=atof(sbuf); return true; /重写显示吊车信息的虚函数Show() void Show() Truck:Show(); coutHeight: fheightendl; ;,61,/主函数 int main()

33、 FILE *stream; /定义文件指针 stream=fopen(autos.txt,r);/以只读方式打开数据文件autos.txt if(stream=NULL)/打开文件失败 coutCant open the file.endl; return 0; Auto *autos3;/定义对象指针数组,包含三个指针变量元素 char sbuf100; /字符缓冲区 int index=0; /序号,62,/循环处理每一组车型信息 while(fgets(sbuf,100,stream)!=NULL ,63,6.5本章小结,多态性指的是不同的对象对于同样的消息会产生不同的行为,是对类的特

34、定成员函数的再抽象。这里的消息是指对类的成员函数的调用,不同的行为是指不同的实现,也就是调用了不同的函数。 1 多态的种类 C+支持的多态又可分为四类,重载多态、强制多态、包含多态和参数多态。 前面两种统称为专用多态,而后面两种统称为通用多态。我们学习过的普通函数及类的成员函数的重载都属于重载多态。 强制多态是通过语义操作把一个变元的类型加以变化,以符合一个函数或操作的要求。 包含多态是研究类族中定义于不同类中的同名成员函数的多态行为主要是通过虚函数来实现。 参数多态是采用参数化模板,通过给出不同的类型参数,使得一个结构可以适用多种数据类型,C+提供的函数模板和类模板即为典型的参数多态。,64

35、,2 多态的实现 多态从系统实现的角度来看可分为两类,编译时多态和运行时多态。 前者是在编译过程中确定了同名操作的具体操作对象,而后者则是在程序运行过程中才动态地确定操作所针对的具体对象这种确定操作的具体对象的过程就是绑定。,65,3 虚函数 虚函数是用virtual关键字声明的成员函数。 析构函数可以声明为虚函数而构造函数不能声明为虚函数,静态成员函数不能声明为虚函数。根据赋值兼容规则,可以用基类类型的指针指向派生类对象,如果这个对象的成员函数是普通的成员函数,通过基类类型的指针访问到的只能是基类的同名成员。 若将基类的同名成员函数设置为虚函数使用基类类型指针就可以访问到该指针正在指向的派生

36、类的同名函数。 这样,通过基类类型的指针,就可以导致属于不同派生类的不同对象产生不同的行为,从而实现了运行过程的多态。,66,4 纯虚函数与抽象类 纯虚函数是指被标明为不具体实现的虚函数。 纯虚函数的声明形式是在虚函数声明原型后跟“=0”即可,包含有纯虚函数的类就是抽象类。 抽象类的作用是:在由该类派生出来的类体系中,它可对类体系中的任何一个子类提供一个统一的接口,即用相同的方法对该类体系中的任一子类实例进行各种操作。并可把接口和实现分开。 由于抽象类只能用作其他类的基类,所以抽象类也称为抽象基类。对于抽象类的使用有以下几点规定 (1) 不能建立抽象类的实例。 (2) 抽象类不能用作参数类型、函数返回类型或显式类型转换。 (3) 可以声明抽象类的指针或引用,通过指针或引用指向派生类对象,而实现动态绑定。,

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

copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!