1、单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,单击此处编辑母版标题样式,*,C+,程序设计,多态性与虚函数,1,第,5,章 多态性与虚函数,什么是多态性,向上类型转换,功能早绑定和晚绑定,实现功能晚绑定,虚函数,纯虚函数和抽象类,学生信息管理系统中的多态性,本章学习要点,2,第,5,章 多态性与虚函数,多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作,从而可以使用相同的调用方式来调用这些具有
2、不同功能的同名函数。,实现“一个接口,多种方法”,3,5.1,什么是多态性,多态性可以分为,4,类:,参数多态,包含多态,重载多态,强制多态。,4,5.1,什么是多态性,参数多态如函数模板和类模板。,由函数模板实例化的各个函数都具有相同的操作,而这些函数的参数类型却各不相同。同样地,由类模板实例化的各个类都具有相同的操作,而操作对象的类型是各不相同的。,5,5.1,什么是多态性,包含多态是研究类族中定义于不同类中的同名成员函数的多态行为,主要是通过虚函数来实现的。,6,5.1,什么是多态性,重载多态如函数重载、运算符重载等。前面我们学习过的普通函数及类的成员函数的重载都属于重载多态。,7,5.
3、1,什么是多态性,强制多态是指将一个变元的类型加以变化,以符合一个函数(或操作)的要求,例如加法运算符在进行浮点数与整型数相加时,首先进行类型强制转换,把整型数变为浮点数再相加的情况,就是强制多态的实例。,8,5.2,向上类型转换,向上类型转换是指把一个派生类的对象作为基类的对象来使用。,向上转型中有三点需要我们特别注意。,向上类型转换是安全的。,向上类型转换可以自动完成。,向上类型转换的过程中会丢失子类型信息。,9,5.2,向上类型转换,【,例,5-1】,一个向上类型转换的例子。,#include,using namespace std;,class B0 /,基类,B0,声明,public
4、void,display()cout,B0:display(),endl,;/,公有成员函数,class B1:public B0,public:,void,display()cout,B1:display(),endl,;,class D1:public B1,public:,void,display()cout,D1:display()display();/,通过“对象指针,-,成员名”的形式调用,display(),成员函数,int,main(),B0,b0,;/,声明,B0,类对象,B1,b1,;/,声明,B1,类对象,D1,d1,;/,声明,D1,类对象,B0*p;/,声明,B0
5、类指针,p,p=/B0,类指针,p,指向,B0,类对象,fun(p,);,p=/B0,类指针,p,指向,B1,类对象,fun(p,);,p=/B0,类指针,p,指向,D1,类对象,fun(p,);,return 0;,10,5.3,功能早绑定和晚绑定,功能早绑定,(,编译时的多态性,):,绑定是在程序编译阶段完成。,功能早绑定时,系统用实参与形参进行匹配,对于同名的重载函数便根据参数上的差异进行区分,然后进行绑定,从而实现了编译时的多态性。,功能晚绑定,(,运行时的多态性,):,绑定是在程序运行阶段完成的。即当程序调用到某一函数名时,才去寻找和连接其程序代码,对面向对象程序设计而言,就是当对
6、象接收到某一消息时,才去寻找和连接相应的方法。,11,5.4,实现功能晚绑定,虚函数,成员函数的定义语法是:,virtual,函数类型 函数名,(,形参表,),函数体,12,5.2,向上类型转换,【,例,5-3】,有一个交通工具类,vehicle,,将它作为基类派生出汽车类,motor_ vehicle,,再将汽车类,motor_ vehicle,作为基类派生出小汽车类,car,和卡车类,truck,,声明这些类并定义一个虚函数用来显示各类信息。程序如下:,#include,using namespace std;,class vehicle/,基类,vehicle,声明,public:,vi
7、rtual void message()/,虚成员函数,coutvehicle message endl;,private:,int wheels;/,车轮个数,float weight;/,车重,;,13,5.2,向上类型转换,class motor_vehicle:public vehicle/vehicle,派生类,motor_vehicle,声明,public:,void message()cout motor_ vehicle message endl;,private:,int passengers;/,承载人数,;,class car:public motor_vehicle /
8、motor_ vehicle,的派生类,car,声明,public:,void message()coutcar message endl;,private:,float engine;/,发动机的马力数,;,class truck:public motor_vehicle/motor_vehicle,的派生类,truck,声明,public:,void message(),cout,truck message message();/,调用基类成员函数,p=/vehicle,类指针,p,指向,motor_ vehicle,类对象,p-message();/,调用,motor_ vehicle,
9、类成员函数,p=/vehicle,类指针,p,指向,car,类对象,p-message();/,调用,car,类成员函数,p=/vehicle,类指针,p,指向,truck,类对象,p-message();/,调用,truck,类成员函数,return 0;,15,5.4,实现功能晚绑定,虚函数,C+,规定,如果在派生类中,没有用,virtual,显式地给出虚函数声明,这时系统就会遵循以下的规则来判断一个成员函数是不是虚函数:,(,1,)该函数与基类的虚函数有相同的名称。,(,2,)该函数与基类的虚函数有相同的参数个数及相同的对应参数类型。,(,3,)该函数与基类的虚函数有相同的返回类型或者满
10、足赋值兼容规则的指针、引用型的返回类型。,16,5.4,实现功能晚绑定,虚函数,说明:,通过定义虚函数来使用,C+,提供的多态性机制时,派生类应该从它的基类公用派生。之所以有这个要求,是因为我们是在赋值兼容规则的基础上来使用虚函数的,而赋值兼容规则成立的前提条件是派生类从其基类公用派生。,必须首先在基类中定义虚函数。由于“基类”与“派生类”是相对的,因此,这项说明并不表明必须在类等级的最高层类中声明虚函数。在实际应用中,应该在类等级内需要具有动态多态性的几个层次中的最高层类内首先声明虚函数。,在派生类对基类中声明的虚函数进行重新定义时,关键字,virtual,可以写也可以不写。但为了增强程序的
11、可读性,最好在对派生类的虚函数进行重新定义时也加上关键字,virtual,。,如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。,虽然使用对象名和点运算符的方式也可以调用虚函数,例如语句:,c.message,();,可以调用虚函数,car:message(),。但是这种调用在编译时进行的是功能早绑定,它没有充分利用虚函数的特性。只有通过基类指针或引用访问虚函数时才能获得运行时的多态性。,17,5.4,实现功能晚绑定,虚函数,一个虚函数无论被公用继承多少次,它仍然保持其虚函数的特性。,虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函
12、数调用要靠特定的对象来决定该激活哪个函数。但是虚函数可以在另一个类中被声明为友元函数。,内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时仍将其看作是非内联的。,构造函数不能是虚函数。因为虚函数作为运行过程中多态的基础,主要是针对对象的,而构造函数是在对象产生之前运行的,因此虚构造函数是没有意义的。,析构函数可以是虚函数,而且通常说明为虚函数。,18,5.4,实现功能晚绑定,虚函数,在析构函数前面加上关键字,virtual,进行说明,则称该析构函数为虚析构函数。虚析构函数的声明语法为:,virtual,类名,();,19,5.2,向上类型转换,【
13、例,5-4】,在交通工具类,vehicle,中使用虚析构函数。,#include,using namespace std;,class vehicle /,基类,vehicle,声明,public:,vehicle(),/,构造函数,virtual,vehicle()/,虚析构函数,cout,vehicle:,vehicle(),endl,;,private:,int,wheels;,float weight;,class,motor_vehicle:public,vehicle /,声明,vehicle,的公用派生类,motor_ vehicle,public:,motor_vehicle
14、)/,派生类构造函数,motor_vehicle,()/,派生类析构函数,cout,motor_ vehicle:,motor_ vehicle(),endl,;,private:,int passengers;,int main(),vehicle*p;/,声明,vehicle,类指针,p,p=new motor_vehicle;,delete p;,return 0;,20,5.4,实现功能晚绑定,虚函数,在一个派生类中重新定义基类的虚函数不同于一般的函数重载:,函数重载处理的是同一层次上的同名函数问题,而虚函数处理的是同一类族中不同派生层次上的同名函数问题,前者是横向重载,后者可以理
15、解为纵向重载。但与重载不同的是,:,同一类族的虚函数的首部是相同的,而函数重载时函数的首部是不同的,(,参数个数或类型不同,),。,重载函数可以是成员函数或普通函数,而虚函数只能是成员函数;,重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据;虚函数是根据对象的不同去调用不同类的虚函数;,虚函数在运行时表现出多态功能,这是,C+,的精髓;而重载函数则在编译时表现出多态性。,21,5.5,纯虚函数和抽象类,纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义,但要求在它的派生类中必须定义自己的版本,或重新说明为纯虚函数。,纯虚函数的定义形式如下:,class,类名,virtual,
16、函数类型 函数名,(,参数表,)=0;,;,22,5.2,向上类型转换,【,例,5-5】,定义一个公共基类,Shape,,它表示一个封闭平面几何图形。然后,从,Shape,类派生出三角形类,Trianglet,、矩形类,Rectangle,和圆类,Circle,,在基类中定义纯虚函数,show(),和,area(),,分别用于显示图形信息和求相应图形的面积,并在派生类中根据不同的图形实现相应的函数。要求实现运行时的多态性。,#include,#include,using namespace std;,const double PI=3.1415926535;,class Shape/,形状类,
17、public:,virtual void show()=0;,virtual double area()=0;,;,23,5.2,向上类型转换,class,Rectangle:public,Shape/,矩形类,public:,Rectangle()length,=0;width=0;,Rectangle(double,len,double,wid,)length=,len;width,=,wid,;,double,area()return,length*width;/,求面积,void show(),cout,length=lengthtwidth=width,endl,;,private:
18、double length,width;/,长宽,class Circle:public Shape/,圆类,public:,Circle()radius,=0;,Circle(double,r)radius,=r;,double,area()return,PI*radius*,radius,;/,求面积,void,show()cout,radius=radius,endl,;,private:,double radius;,24,5.2,向上类型转换,class,Triangle:public,Shape/,三角形类,public:,Triangle()a,=0;b=0;c=0;,Tria
19、ngle(double,x,double,y,double,z)a,=,x;b,=,y;c,=z;,double area()/,求面积,double s=(a+b+c)/2.0;,return,sqrt(s,*(s-a)*(,s-b,)*(,s-c,);,void show(),cout,a=atb=btc=c,endl,;,private:,double a,b,c,;/,三角形三边长,;,25,5.2,向上类型转换,int,main(),Shape*s;,Circle c(10);,Rectangle r(6,8);,Triangle t(3,4,5);,c.show();/,静态多态,
20、cout,圆面积:,c.area()show();,cout,矩形面积:,area()show();,cout,三角形面积:,area()endl;,return 0;,26,5.5,纯虚函数和抽象类,如果一个类至少有一个纯虚函数,那么就称该类为抽象类。,对于抽象类的使用有以下几点规定:,(,1,)由于抽象类中至少包含一个没有定义功能的纯虚函数。因此,抽象类只能作为其他类的基类来使用,不能建立抽象类对象,这只能用来为派生类提供一个接口规范,其纯虚函数的实现由派生类给出。,(,2,)不允许从具体类派生出抽象类,。所谓具体类,就是不包含纯虚函数的普通类。,(,3,)抽象类不能用作参数类型、函数返回
21、类型或显式转换的类型。,(,4,)可以声明指向抽象类的指针或引用,此指针可以指向它的派生类,进而实现动态多态性。,(,5,)如果派生类中没有重新定义纯虚函数,则派生类只是简单继承基类的纯虚函数,则这个派生类仍然是一个抽象类。如果派生类中给出了基类所有纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以创建对象的具体类。,(,6,)在抽象类中也可以定义普通成员函数或虚函数,虽然不能为抽象类声明对象,但仍然可以通过派生类对象来调用这些不是纯虚函数的函数。,27,小结与复习建议,主要内容,多态性的概念、虚函数、纯虚函数、抽象类,达到的目标,理解多态的概念,学会运用多态机制。,28,Thank You!,29,






