版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
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,编译时多态,指的在程序编译过程中时决定同名操作与对象的绑定关系,也称静态绑定、静态联编,典型的技术有函数重载、运算符重载、模板。 由于这种方式是在程序运行前就确定了对象要调用的具体函数,因此程序运行的时候函数调用速度快、效率较高。 其缺点是编程不够灵活。,7,运
4、行时多态,指的是在程序运行过程中动态地确定同名操作与具体对象的绑定关系,也称动态绑定、动态联编等,主要通过使用继承和虚函数来实现。 在编译、连接过程中确定绑定关系,程序运行之后才能确定。 动态绑定的优点是编程更加灵活、系统易于扩展。 由于内部增加了实现虚函数调用的机制,因此要比静态绑定的函数调用速度慢些。,8,6.1.2 基类指针指向派生类对象,【例6 .1】 函数重载在多态性中的应用。 /* 06_01.cpp */ #include using namespace std; class Base/基类 public : void Print() cout”Base Class Print.
5、”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()函数,但却仍然调用了d
7、1对象包含的基类成员函数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 Print() co
9、ut”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 Print. Der
10、ived2 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 public: virt
11、ual void TestB() cout”Base2 TestB()”endl; ;,20,class Derived :public Base1, public Base2 public: void TestA() / 重写基类Base1中的虚函数TestA() coutTestA(); / 调用类Derived 的TestA()函数 pB2-TestB(); / 调用类Derived的TestB()函数 return 0; ,21,Derived TestA() Derived TestB(),程序的运行结果为:,22,图6.1 多继承与虚函数,23,【例6.4】 多继承基类中有同名虚函
12、数。,/* 06_04.cpp */ #include using namespace std; class Base1 public: virtual void Test() cout”Base1 Test()”endl; ; class Base2 public: virtual void Test() cout”Base2 Test()”endl; ;,24,class Derived :public Base1, public Base2 public: void Test() / 直接重写虚函数Test() coutTest(); /用基类指针pB1调用类Derived 的TestA
13、()函数 pB2-Test(); /用基类指针pB2调用类Derived的TestB()函数 return 0; ,25,Derived Test() Derived Test(),程序的运行结果为:,26,如果按照例6.4的方式,在派生类中直接重写虚函数Test(),则2个基类的Test()虚函数都将被覆盖。 通过两种基类的指针指向派生类对象并调用虚函数,就只能有一个Test()函数可以调用,而不是像例6.3那样有不同的实现结果。 为了实现例6.3中的效果,在派生类Derived中重写不同基类中相同原型的虚函数Test(),可以使用下面的方法,不要修改最初的两个基类,而是增加两个中间类,具体
14、实现如下:,27,【例6.5】多继承基类中有同名虚函数的解决方法。,/* 06_05.cpp */ #include using namespace std; /第一个基类Base1 class Base1 public: virtual void Test()/虚函数声明,函数实现略 ; /第二个基类Base2 class Base2 public: virtual void Test()/虚函数声明,函数实现略 ;,28,/定义针对Base1的中间类 class MiddleBase1 : public Base1 protected: virtual void Base1_Test()
15、/空的函数体,什么也不做,留在派生类中实现 virtual void Test() /重写虚函数Test() Base1_Test(); ; /定义针对Base2的中间类 class MiddleBase2: public Base2 private: virtual void Base2_Test()/空的函数体,什么也不做,留在派生类中实现 virtual void Test() /重写虚函数Test() Base2_Test(); ; /定义最后的派生类,由两个中间类派生而来,29,class Derived :public MiddleBase1, public MiddleBase2
16、 public: / 重写从中间类继承下来的虚函数 /重写中间类的虚函数Base1_Test(),实际上是重写了基类Base1的Test()函数 void Base1_Test () coutTest(); /用基类指针pB1调用类Derived 的TestA()函数 pB2-Test(); /用基类指针pB2调用类Derived的TestB()函数 return 0; ,30,Derived TestA() Derived TestB(),程序的运行结果为:,31,图6.2 中间类的作用,32,6.2.3 虚析构函数,析构函数的作用是在对象撤销之前做必要的“清理现场”的工作。 当派生类的对象
17、从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数。但是,如果用new 运算符动态生成一个派生类的堆对象,并让基类指针指向该派生类对象。 当程序用delete运算符通过基类指针删除派生类对象时,会发生一种情况:系统会只执行基类的析构函数,而不执行派生类的析构函数。,33,【例6.6】基类中有非虚析构函数时的执行情况。,/* 06_06.cpp */ #include using namespace std; class Base/定义基类Base public: Base()/Base类构造函数 coutConstruct Base.endl; Base()/Base类析构函数
18、 coutDeconstruct Base.endl; ;,34,class Derived:public Base /定义公有派生类Derived public: Derived()/Derived类构造函数 coutConstruct Derived.endl; Derived()/Derived类析构函数 coutDeconstruct Derived.endl; ; int main() /主函数测试 Base *pb;/定义基类指针 pb=new Derived();/基类指针指向新生成的派生类堆对象 delete pb ; return 1; ,35,Construct Base.
19、 Construct Derived. Destruct Base.,程序的运行结果为:,36,在程序的main()函数中,pb是基类的指针,指向了一个派生类Derived的堆对象。希望用delete释放pb所指向的空间。但运行结果为: Destruct Base. 表示只执行了基类Base的析构函数,而没有执行派生类Derived的析构函数。如果希望执行派生类Derived的析构函数,则应将基类的析构函数声明为虚析构函数,例如: virtual Base()/Base类析构函数 coutDeconstruct Base.endl; 程序其他部分不改动,再运行程序,其结果为: Destruct
20、 Derived. Destruct Base.,37,如果将基类的析构函数声明为虚函数时,由该基类所派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类的析构函数名字不同。 最好把基类的析构函数声明为虚函数。这将使所有派生类的析构函数自动成为虚函数。这样,如果程序中显式地用了delete 运算符准备删除一个对象,而delete 运算符的操作对象用了指向派生类对象的基类指针,则系统会调用相应类的析构函数。,38,虚析构函数的概念和用法很简单,但它在面向对象程序设计中却是很重要的技巧。专业人员一般都习惯声明虚析构函数,即使基类并不需要析构函数,也显式地定义一个函数体为空的虚析
21、构函数,以保证在撤销动态分配空间时能得到正确的处理。 构造函数不能声明为虚函数。这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上函数与类对象的绑定。,39,6 . 3 纯虚函数和抽象类,6.3.1纯虚函数 有时在基类中将某一成员函数声明为虚函数,并不是类本身的要求,而是考虑到派生类的需要,在基类中只定义一个函数名,具体功能留给派生类根据需要去实现。 因此可以对这种虚函数只在基类中说明函数原型,用来定义继承体系中的统一接口形式,然后在派生类的虚函数中重新定义具体实现代码,而这种基类中的虚函数就是纯虚函数,其声明一般形式为: virtual 函数类型 函数名(参数表)=0 ;,40,关于
22、纯虚函数,有以下问题需要说明:,(1)纯虚函数没有函数体; (2)最后面的“= 0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”; (3)这是一个声明语句,后面应有分号; (4)纯虚函数只有函数的名字而不具备函数的功能,不能被调用,它只是通知编译器在这时声明一个虚函数,留待派生类中定义。在派生类中对此函数提供定义后,它才能具备函数的功能,可以被调用。 如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数。,41,【例6.7】 分析下列程序的输出的结果。,/* 06_07.cpp */ #include using name
23、space std; class Vehicle/定义交通工具类 protected: int pos,speed;/定义成员变量位置和速度 public: Vehicle(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
24、Run()/重写虚函数Run() pos+=speed;/位置变化 ; int main()/主函数 Vehicle *pvh; Carcar; 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抽象类,包含有纯虚函数的类是抽象类。 由于抽象类常用作基类,
25、通常称为抽象基类。 抽象基类的主要作用是: 通过它为一个类族建立一个公共的接口,使它们能够更有效地发挥多态特性。 抽象基类声明了一族派生类的共同接口,而接口的具体实现代码,即纯虚函数的函数体,要由派生类自己定义。,45,抽象类派生出新的类之后,如果派生类给出所有纯虚函数的函数实现,这个派生类就可以定义自己的对象,因而不再是抽象类;反之,如果派生类没有给出全部纯虚函数的实现,这时的派生类仍然是一个抽象类。 抽象类不能实例化,即不能定义一个抽象类的对象,但是,可以声明一个抽象类的指针或引用。通过指针或引用,就可以指向并访问派生类对象。进而访问派生类的成员,这种访问是具有多态特征的。用抽象类实现多态
26、性。,46,【例6.8】设计一个抽象类Shape,用来表示形状的抽象概念,并定义求面积Area()和打印Print()两个纯虚函数,然后设计圆类和矩形两个派生类,各自重写基类中的虚函数。,/* 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;/打印输出形状信息,声明为纯虚
27、函数 ;,47,/定义圆派生类 class Circle : public Shape private: double radius; /半径 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() /
28、打印输出圆形信息 coutCircle Center=(x,y),Radius=radiusendl; ,48,/定义矩形派生类 class 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) w
29、idth=w; height=h; double Rectangle:Area()/实现求矩形面积 return width*height; 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);/矩形对象 ps
30、1= ,50,Circle Center=(30,15),Radius=10 Area=314.16 Rectangle Position=(20,20),Size=(100,40) Area=4000,程序的运行结果为:,51,6.4 综合应用举例,本节以一个小型的汽车信息处理程序为例,说明虚函数、抽象类应用。有一个ASCII文件存储不同类型汽车信息: 普通汽车Car 卡车Truck 吊车Crane 编写三个类实现这三种汽车类,并且设计虚函数Input()读入对应车型的有关信息,然后按照指定的格式输出到屏幕,文件名设为autos.txt。 文件中的汽车信息如表6.1所示。,52,表6.1 文
31、件中的汽车信息,53,读入以上文件实例内容后,将以如下格式输出: Style: Car Manufacturer: Volkswagen 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 protecte
32、d: string stypename;/类型名 intnpassengers;/乘客数量 string smanufacturer;/厂商名称 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; s
33、buf+; virtual bool Input(FILE *fp)=0;/纯虚函数Input(),输入数据 virtual 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;/
34、生产厂商字符串 fgets(sbuf,100,fp);/再读一行 npassengers=atoi(sbuf);/atoi()函数实现把字符串内容转换为整数 return true; ,57,/重写显示车信息的虚函数Show() void Show() coutStyle: stypenameendl; coutManufacturer: smanufacturerendl; coutPassenger: npassengersendl; ;,58,/卡车Truck类,从Car类派生 class Truck:public Car protected: floatfload;/载重量 publi
35、c: Truck()/卡车类构造函数 stypename=Truck; /车型名称 fload=0; /重写虚函数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;
36、/输出卡车的载重值 ; /吊车Crane类,从Car类派生 class Crane:public Truck protected: floatfheight;/吊车的举物高度 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() FILE *stream;/定义文件指针 stream=fopen(autos.txt
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 高二地理教学设计:产业转型地区结构优化的机制与路径
- 高二地理选择性必修1教学设计:海绵城市建设与区域水安全保障
- 小学四年级综合实践活动《生命至上·科学避雷》主题班会教学设计
- 八年级数学《其他判定三角形全等条件》教学设计:AAS与HL模型的构建与迁移
- 掩膜版制造工保密意识考核试卷含答案
- 原料乳处理工班组考核考核试卷含答案
- 蒸煮熏烤制品加工工测试验证评优考核试卷含答案
- 铁合金焙烧操作工班组建设竞赛考核试卷含答案
- 石膏粉生产工安全风险水平考核试卷含答案
- 装饰美工基础管理竞赛考核试卷含答案
- 新版2026年部编版新教材道德与法治五年级上册全套单元、期中、期末检测题(共6份有答案)合集
- 2026年重庆市安全员A证考试模拟题及答案详解
- 2026-2027学年人教版八年级上册数学第一次月考重难点突破学情自测卷(含答案)
- 2026年公卫执业医师《医学微生物学》试题及答案
- 2026年江苏省人教版小学四年级语文上册第1单元测试卷
- 雨课堂在线学堂《大学生国家安全教育》作业单元考核答案
- 会议摄影合同
- 铁路货车轮轴组装检修及管理规则
- 《概念验证服务规范》
- 酶工程与发酵工程创新创业项目商业计划书
- CJ/T 414-2012城市轨道交通钢铝复合导电轨技术要求
评论
0/150
提交评论