1、2023-11-121 1第第5章章 多态性与虚函数多态性与虚函数 什么是多态性什么是多态性 向上类型转换向上类型转换 功能早绑定和晚绑定功能早绑定和晚绑定 实现功能晚绑定实现功能晚绑定虚函数虚函数 纯虚函数和抽象类纯虚函数和抽象类 图书馆图书借阅管理系统中的多态性图书馆图书借阅管理系统中的多态性本章学习要点本章学习要点2023-11-122 2本章学习目标本章学习目标理解多态性的概念掌握虚函数的使用理解纯虚函数和抽象类的概念掌握纯虚函数和抽象类的使用第第6章章 面向对象的妥协面向对象的妥协2023-11-123 3第第5章章 多态性与虚函数多态性与虚函数在面向对象方法中在面向对象方法中一般是
2、这样表述一般是这样表述多态性多态性的:的:向不同的对象发送同一个消息,不同的对象在接向不同的对象发送同一个消息,不同的对象在接收时会有不同的反应,产生不同的动作。收时会有不同的反应,产生不同的动作。也就是也就是说,每个对象可以用自己的方式去响应共同的消说,每个对象可以用自己的方式去响应共同的消息。息。在在C+程序设计中程序设计中,多态性多态性是指用一个名字定是指用一个名字定义不同的函数,这些函数执行不同但又类似的操义不同的函数,这些函数执行不同但又类似的操作,从而可以使用相同的调用方式来调用这些具作,从而可以使用相同的调用方式来调用这些具有不同功能的同名函数。有不同功能的同名函数。实现实现“一
3、个接口,多种方法一个接口,多种方法”2023-11-124 45.1 什么是多态性什么是多态性C+中的多态性可以分为中的多态性可以分为4类:类:参数多态参数多态包含多态包含多态重载多态重载多态强制多态强制多态2023-11-125 55.1 什么是多态性什么是多态性 参数多态参数多态 如函数模板和类模板。如函数模板和类模板。l由函数模板实例化的各个函数都具有相由函数模板实例化的各个函数都具有相同的操作,而这些函数的参数类型却各不同的操作,而这些函数的参数类型却各不相同。同样地,由类模板实例化的各个类相同。同样地,由类模板实例化的各个类都具有相同的操作,而操作对象的类型是都具有相同的操作,而操作
4、对象的类型是各不相同的。各不相同的。2023-11-126 65.1 什么是多态性什么是多态性包含多态包含多态l是研究类族中定义于不同类中的同名成是研究类族中定义于不同类中的同名成员函数的多态行为,主要是通过虚函数来员函数的多态行为,主要是通过虚函数来实现的。实现的。2023-11-127 75.1 什么是多态性什么是多态性重载多态重载多态l如函数重载、运算符重载等。前面我们如函数重载、运算符重载等。前面我们学习过的普通函数及类的成员函数的重载学习过的普通函数及类的成员函数的重载都属于重载多态。都属于重载多态。2023-11-128 85.1 什么是多态性什么是多态性强制多态强制多态l是指将一
5、个变元的类型加以变化,以符是指将一个变元的类型加以变化,以符合一个函数(或操作)的要求,例如加法合一个函数(或操作)的要求,例如加法运算符在进行浮点数与整型数相加时,首运算符在进行浮点数与整型数相加时,首先进行类型强制转换,把整型数变为浮点先进行类型强制转换,把整型数变为浮点数再相加的情况,就是强制多态的实例。数再相加的情况,就是强制多态的实例。2023-11-129 95.2 向上类型转换向上类型转换向上类型转换向上类型转换是指把一个派生类的对象作是指把一个派生类的对象作为基类的对象来使用为基类的对象来使用。向上转型中有三点需要我们特别向上转型中有三点需要我们特别注意:注意:向上类型转换是安
6、全的。向上类型转换是安全的。向上类型转换可以自动完成。向上类型转换可以自动完成。向上类型转换的过程中会丢失子类型信向上类型转换的过程中会丢失子类型信息。息。【例例5-1】一个向上类型转换的例子。一个向上类型转换的例子。2023-11-1210105.2 向上类型转换向上类型转换#include using namespace std;class Point public:Point(double a=0,double b=0)x=a;y=b;double Area()cout Call Points Area function.endl;return 0.0;protected:double
7、x,y;class Rectangle:public Point public:Rectangle(double a=0,double b=0,double c=0,double d=0):Point(a,b)x1=c;y1=d;double Area()cout Call Rectangles Area function.endl;return(x-x1)*(y-y1);protected:double x1,y1;class Circle:public Point public:Circle(double a=0,double b=0,double c=0):Point(a,b)r=c;d
8、ouble Area()cout Call Circles Area function.endl;return 3.14*r*r;protected:double r;double CalcArea(Point&ref)return(ref.Area();int main()Point p(0,0);Rectangle r(0,0,1,1);Circle c(0,0,1);cout CalcArea(p)endl;cout CalcArea(r)endl;cout CalcArea(c)endl;return 0;程序运行结果如下:程序运行结果如下:Call Points Area funct
9、ion.0Call Points Area function.0Call Points Area function.0virtual程序运行结果如下:程序运行结果如下:Call Points Area function.0Call Rectangles Area function.1Call Circles Area function.3.142023-11-1211115.3 功能早绑定和晚绑定功能早绑定和晚绑定v多态性从实现的角度来讲可以划分为两多态性从实现的角度来讲可以划分为两类:类:编译时的多态性编译时的多态性和和运行时的多态性运行时的多态性。v在在C+C+中,中,编译时的多态性编译时
10、的多态性主要是主要是通过函通过函数重载和运算符重载实现数重载和运算符重载实现的。的。运行时的运行时的多态性多态性主要是主要是通过虚函数来实现通过虚函数来实现的。的。2023-11-1212125.3 功能早绑定和晚绑定功能早绑定和晚绑定v功能早绑定功能早绑定(编译时的多态性编译时的多态性):):绑定是在程序绑定是在程序编译阶段完成。编译阶段完成。功能早绑定时,系统用实参与功能早绑定时,系统用实参与形参进行匹配,对于同名的重载函数便根据参形参进行匹配,对于同名的重载函数便根据参数上的差异进行区分,然后进行绑定,从而实数上的差异进行区分,然后进行绑定,从而实现了编译时的多态性。现了编译时的多态性。
11、v功能晚绑定功能晚绑定(运行时的多态性运行时的多态性):):绑定是在程序绑定是在程序运行阶段完成的。即当程序调用到某一函数名运行阶段完成的。即当程序调用到某一函数名时,才去寻找和连接其程序代码,对面向对象时,才去寻找和连接其程序代码,对面向对象程序设计而言,就是当对象接收到某一消息时,程序设计而言,就是当对象接收到某一消息时,才去寻找和连接相应的方法。才去寻找和连接相应的方法。2023-11-1213135.4.1 虚函数的定义与使用虚函数的定义与使用v虚成员函数的定义语法是:虚成员函数的定义语法是:virtualvirtual 函数类型函数类型 函数名函数名(形参表形参表)函数体函数体 虚函
12、数的定义是在基类中进行的虚函数的定义是在基类中进行的,在成员函数原型的声,在成员函数原型的声明语句之前冠以关键字明语句之前冠以关键字virtual,从而提供一种接口。,从而提供一种接口。2023-11-121414v当基类中的某个成员函数被声明为虚函数后,当基类中的某个成员函数被声明为虚函数后,此虚函数就可以在一个或多个派生类中被重此虚函数就可以在一个或多个派生类中被重新定义。新定义。在派生类中重新定义时在派生类中重新定义时,其函数原其函数原型,包括返回类型、函数名、参数个数、参型,包括返回类型、函数名、参数个数、参数类型的顺序,都必须与基类中的原型完全数类型的顺序,都必须与基类中的原型完全相
13、同。相同。5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-121515v虚函数的作用虚函数的作用是允许在派生类中重新定义与是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数引用来访问基类和派生类中的同名函数。v具体做法是,首先在基类中声明这个成员函具体做法是,首先在基类中声明这个成员函数为虚函数,也就是在这个成员函数前面缀数为虚函数,也就是在这个成员函数前面缀上关键字上关键字virtualvirtual,并在派生类中被重新定义,并在派生类中被重新定义,就能实现动态调用的功能。就能实现动态调用的
14、功能。5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-121616【例例5-2】虚函数的作用。虚函数的作用。#include using namespace std;class Pointpublic:Point(double a=0,double b=0)x=a;y=b;virtual double Area()cout Call Points Area function.endl;return 0.0;protected:double x,y;5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-121717class Rectangle:public Point
15、public:Rectangle(double a=0,double b=0,double c=0,double d=0):Point(a,b)x1=c;y1=d;double Area()cout Call Rectangles Area function.endl;return(x1-x)*(y1-y);protected:double x1,y1;5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-121818class Circle:public Pointpublic:Circle(double a=0,double b=0,double c=0):Point(a,b)r
16、=c;double Area()cout Call Circles Area function.endl;return 3.14*r*r;protected:double r;5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-121919double CalcArea(Point&ref)return(ref.Area();int main()Point p(0,0);Rectangle r(0,0,1,1);Circle c(0,0,1);cout CalcArea(p)endl;cout CalcArea(r)endl;cout CalcArea(c)endl;return
17、0;5.4.1 虚函数的定义与使用虚函数的定义与使用程序运行结果如下:程序运行结果如下:Call Points Area function.0Call Rectangles Area function.1Call Circles Area function.3.142023-11-1220205.4.1 虚函数的定义与使用虚函数的定义与使用v为什么把基类中的为什么把基类中的AreaArea函数定义为虚函数时,程序的函数定义为虚函数时,程序的运行结果就正确了呢?这是因为,关键字运行结果就正确了呢?这是因为,关键字“virtual”virtual”指示指示C+C+编译器,函数调用编译器,函数调用“
18、ref.Area()”ref.Area()”要在运要在运行时确定所要调用的函数,即要对该调用进行功能晚行时确定所要调用的函数,即要对该调用进行功能晚绑定。因此,程序在运行时根据引用绑定。因此,程序在运行时根据引用refref所引用的实所引用的实际对象,调用该对象的成员函数。际对象,调用该对象的成员函数。2023-11-1221215.2 向上类型转换向上类型转换【例例5-35-3】有一个交通工具类有一个交通工具类vehiclevehicle,将它作为基类派,将它作为基类派生出汽车类生出汽车类motor_ vehicle motor_ vehicle,再将汽车类,再将汽车类motor_ moto
19、r_ vehicle vehicle 作为基类派生出小汽车类作为基类派生出小汽车类carcar和卡车类和卡车类trucktruck,声明这些类并定义一个虚函数用来显示各类信息。程序声明这些类并定义一个虚函数用来显示各类信息。程序如下:如下:#include#include using namespace std;using namespace std;class class vehiclevehicle/基类基类vehiclevehicle声明声明public:public:virtual virtual void message()/void message()/虚成员函数虚成员函数 cou
20、tvehicle message endl;coutvehicle message endl;private:private:int wheels;/int wheels;/车轮个数车轮个数 float weight;/float weight;/车重车重;2023-11-1222225.2 向上类型转换向上类型转换class class motor_vehiclemotor_vehicle:public:public vehicle vehicle public:public:void message()void message()cout motor_ vehicle message en
21、dl;cout motor_ vehicle message endl;private:int passengers;private:int passengers;/承载人数承载人数;class class carcar:public:public motor_vehiclemotor_vehicle public:public:void message()coutcar message endl;void message()coutcar message endl;private:float engine;private:float engine;/发动机的马力数发动机的马力数;class
22、class trucktruck:public:public motor_vehiclemotor_vehicle public:public:void message()cout truck message endl;void message()cout truck message message();p-message();p=&m;p=&m;p-message();p-message();p=&c;p=&c;p-message();p-message();p=&t;p=&t;p-message();p-message();return 0;return 0;2023-11-122424v
23、C+C+规定,如果在派生类中,没有用规定,如果在派生类中,没有用virtualvirtual显式地给出虚函数声明,这时系统就会遵循以显式地给出虚函数声明,这时系统就会遵循以下的规则来判断一个成员函数是不是虚函数:下的规则来判断一个成员函数是不是虚函数:(1 1)该函数与基类的虚函数)该函数与基类的虚函数有相同的名称有相同的名称。(2 2)该函数与基类的虚函数)该函数与基类的虚函数有相同的参数有相同的参数个数及相同的对应参数类型个数及相同的对应参数类型。(3 3)该函数与基类的虚函数)该函数与基类的虚函数有相同的返回有相同的返回类型或者满足赋值兼容规则的指针、引用型类型或者满足赋值兼容规则的指针
24、、引用型的返回类型的返回类型。5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-122525说明:说明:v(1)(1)通过定义虚函数来使用通过定义虚函数来使用C+C+提供的多态性提供的多态性机制时,派生类应该从它的基类公用派生。机制时,派生类应该从它的基类公用派生。之所以有这个要求,是因为我们是在赋值兼之所以有这个要求,是因为我们是在赋值兼容规则的基础上来使用虚函数的,而赋值兼容规则的基础上来使用虚函数的,而赋值兼容规则成立的前提条件是派生类从其基类公容规则成立的前提条件是派生类从其基类公用派生。用派生。5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-122626
25、说明:说明:v(2)(2)必须首先在基类中定义虚函数。由于必须首先在基类中定义虚函数。由于“基类基类”与与“派生类派生类”是相对的,因此,是相对的,因此,这项说明并不表明必须在类等级的最高层这项说明并不表明必须在类等级的最高层类中声明虚函数。在实际应用中,应该在类中声明虚函数。在实际应用中,应该在类等级内需要具有动态多态性的几个层次类等级内需要具有动态多态性的几个层次中的最高层类内首先声明虚函数。中的最高层类内首先声明虚函数。5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-122727说明:说明:v(3)(3)在派生类对基类中声明的虚函数进行重新在派生类对基类中声明的虚函数进行
26、重新定义时,关键字定义时,关键字virtualvirtual可以写也可以不写。可以写也可以不写。但为了增强程序的可读性,最好在对派生类但为了增强程序的可读性,最好在对派生类的虚函数进行重新定义时也加上关键字的虚函数进行重新定义时也加上关键字virtualvirtual。v如果在派生类中没有对基类的虚函数重新定如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函义,则派生类简单地继承其直接基类的虚函数。数。5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-122828说明:说明:v(4)(4)虽然使用对象名和点运算符的方式也可以虽然使用对象名和点运算符的方式
27、也可以调用虚函数,例如语句:调用虚函数,例如语句:c.message();c.message();可以调用虚函数可以调用虚函数car:message()car:message()。但是这。但是这种调用是在编译时进行的功能早绑定,它没有种调用是在编译时进行的功能早绑定,它没有充分利用虚函数的特性。只有通过基类指针或充分利用虚函数的特性。只有通过基类指针或引用访问虚函数时才能获得运行时的多态性。引用访问虚函数时才能获得运行时的多态性。5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-122929说明:说明:v(5)(5)一个虚函数无论被公用继承多少次,它仍一个虚函数无论被公用继承多少
28、次,它仍然保持其虚函数的特性。然保持其虚函数的特性。v(6)(6)虚函数必须是其所在类的成员函数,而不虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为能是友元函数,也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活哪个虚函数调用要靠特定的对象来决定该激活哪个函数。但是虚函数可以在另一个类中被声明为函数。但是虚函数可以在另一个类中被声明为友元函数。友元函数。5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-123030说明:说明:v(7 7)内联函数不能是虚函数,因为内联函数)内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的。
29、即使虚函是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时仍将其看作是非内数在类的内部定义,编译时仍将其看作是非内联的。联的。5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-123131说明:说明:v(8 8)构造函数不能是虚函数。因为虚函数作)构造函数不能是虚函数。因为虚函数作为运行过程中多态的基础,主要是针对对象的,为运行过程中多态的基础,主要是针对对象的,而构造函数是在对象产生之前运行的,因此虚而构造函数是在对象产生之前运行的,因此虚构造函数是没有意义的。构造函数是没有意义的。v(9 9)析构函数可以是虚函数,而且通常说明)析构函数可以是虚函数,而且通常说明
30、为虚函数。为虚函数。5.4.1 虚函数的定义与使用虚函数的定义与使用2023-11-123232v在析构函数前面加上关键字在析构函数前面加上关键字virtualvirtual进进行说明,则称该析构函数为虚析构函行说明,则称该析构函数为虚析构函数。虚析构函数的声明语法为:数。虚析构函数的声明语法为:virtual virtual 类名类名();();5.4.2 虚析构函数虚析构函数2023-11-1233335.2 向上类型转换向上类型转换【例例5-45-4】在交通工具类在交通工具类vehiclevehicle中使用虚析构函数。中使用虚析构函数。#include#include using na
31、mespace std;using namespace std;class class vehiclevehicle /基类基类vehiclevehicle声明声明public:public:vehicle()vehicle()/构造函数构造函数 virtual virtual vehiclevehicle()/()/虚析构函数虚析构函数 coutvehicle:coutvehicle:vehicle()endl;vehicle()endl;private:private:int wheels;int wheels;float weight;float weight;2023-11-12343
32、45.2 向上类型转换向上类型转换/声明声明vehiclevehicle的公用派生类的公用派生类motor_ vehiclemotor_ vehicleclass class motor_vehiclemotor_vehicle:public:public vehiclevehiclepublic:public:motor_vehicle()/motor_vehicle()/派生类构造函数派生类构造函数 motor_vehiclemotor_vehicle()/()/派生类析构函数派生类析构函数 coutmotor_ vehicle:coutmotor_ vehicle:motor_ vehi
33、cle()endl;motor_ vehicle()endl;private:private:int passengers;int passengers;int main()int main()vehicle vehicle*p;p;/声明声明vehiclevehicle类指针类指针p p p=new motor_vehicle;p=new motor_vehicle;delete p;delete p;return 0;return 0;程序运行结果如下:程序运行结果如下:motor_ vehicle:motor_ vehicle()vehicle:vehicle()程序运行结果如下:程序运
34、行结果如下:vehicle:vehicle()2023-11-123535v当基类的析构函数为虚函数时,无论指针指向的是同当基类的析构函数为虚函数时,无论指针指向的是同一类族中的哪一个类对象,系统都会采用动态关联,一类族中的哪一个类对象,系统都会采用动态关联,调用相应的析构函数,对该对象所涉及的额外内存空调用相应的析构函数,对该对象所涉及的额外内存空间进行清理工作。最好把基类的析构函数声明为虚函间进行清理工作。最好把基类的析构函数声明为虚函数,这将使所有派生类的析构函数自动成为虚函数。数,这将使所有派生类的析构函数自动成为虚函数。这样,如果程序中显式地用了这样,如果程序中显式地用了delete
35、delete运算符准备删除运算符准备删除一个对象,而一个对象,而deletedelete运算符的操作对象用了指向派生运算符的操作对象用了指向派生类对象的基类指针,则系统会首先调用派生类的析构类对象的基类指针,则系统会首先调用派生类的析构函数,再调用基类的析构函数,这样整个派生类的对函数,再调用基类的析构函数,这样整个派生类的对象被完全释放。象被完全释放。5.4.2 虚析构函数虚析构函数特别提醒特别提醒2023-11-1236365.4.35.4.3虚函数与重载函数的比较虚函数与重载函数的比较 在一个派生类中重新定义基类的虚函数不同于一般的函在一个派生类中重新定义基类的虚函数不同于一般的函数重载
36、:数重载:v函数重载处理的是同一层次上的同名函数问题,而虚函数重载处理的是同一层次上的同名函数问题,而虚函数处理的是同一类族中不同派生层次上的同名函数函数处理的是同一类族中不同派生层次上的同名函数问题,前者是横向重载,后者可以理解为纵向重载。问题,前者是横向重载,后者可以理解为纵向重载。但与重载不同的是但与重载不同的是:同一类族的虚函数的首部是相同同一类族的虚函数的首部是相同的,而函数重载时函数的首部是不同的的,而函数重载时函数的首部是不同的(参数个数或参数个数或类型不同类型不同)。v重载函数可以是成员函数或普通函数,而虚函数只能重载函数可以是成员函数或普通函数,而虚函数只能是成员函数;是成员
37、函数;v重载函数的调用是以所传递参数序列的差别作为调用重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据;虚函数是根据对象的不同去调用不不同函数的依据;虚函数是根据对象的不同去调用不同类的虚函数;同类的虚函数;v虚函数在运行时表现出多态功能,这是虚函数在运行时表现出多态功能,这是C+C+的精髓;的精髓;而重载函数则在编译时表现出多态性。而重载函数则在编译时表现出多态性。2023-11-1237375.5 纯虚函数和抽象类纯虚函数和抽象类v纯虚函数纯虚函数是一个在基类中说明的虚函数,是一个在基类中说明的虚函数,它在该基类中没有定义,但要求在它的派它在该基类中没有定义,但要求在它的派生类
38、中必须定义自己的版本生类中必须定义自己的版本。v纯虚函数的定义形式如下:纯虚函数的定义形式如下:class class 类名类名 virtualvirtual 函数类型函数类型 函数名函数名(参数表参数表)=0=0;2023-11-1238385.2 向上类型转换向上类型转换【例例5-55-5】定义一个公共基类定义一个公共基类ShapeShape,它表示一个封闭平面,它表示一个封闭平面几何图形。然后,从几何图形。然后,从ShapeShape类派生出三角形类类派生出三角形类TriangletTrianglet、矩形类矩形类RectangleRectangle和圆类和圆类CircleCircle,
39、在基类中定义纯虚函,在基类中定义纯虚函数数show()show()和和area()area(),分别用于显示图形信息和求相应图,分别用于显示图形信息和求相应图形的面积,并在派生类中根据不同的图形实现相应的函形的面积,并在派生类中根据不同的图形实现相应的函数。要求实现运行时的多态性。数。要求实现运行时的多态性。#include#include#include#includeusing namespace std;using namespace std;const double PI=3.1415926535;const double PI=3.1415926535;class Shape/cla
40、ss Shape/形状类形状类 public:public:virtual void virtual void showshow()=0;()=0;virtual double virtual double areaarea()=0;()=0;2023-11-1239395.2 向上类型转换向上类型转换class Rectangle:public Shape/class Rectangle:public Shape/矩形类矩形类public:public:Rectangle()length=0;width=0;Rectangle()length=0;width=0;Rectangle(doub
41、le len,double wid)Rectangle(double len,double wid)length=len;width=wid;length=len;width=wid;double double areaarea()return length()return length*width;/width;/求面积求面积 void void showshow()()coutlength=lengthtwidth=widthendcoutlength=lengthtwidth=widthendl;l;private:private:double length,width;/double
42、length,width;/长宽长宽;2023-11-1240405.2 向上类型转换向上类型转换class Triangle:public Shape/class Triangle:public Shape/三角形类三角形类public:public:Triangle()a=0;b=0;c=0;Triangle()a=0;b=0;c=0;Triangle(double x,double y,double z)Triangle(double x,double y,double z)a=x;b=y;c=z;a=x;b=y;c=z;double double areaarea()/()/求面积求面
43、积 double s=(a+b+c)/2.0;double s=(a+b+c)/2.0;return sqrt(s return sqrt(s*(s-a)(s-a)*(s-b)(s-b)*(s-c);(s-c);void void showshow()()couta=atb=btc=cendl;couta=atb=btc=cendl;private:private:double a,b,c;/double a,b,c;/三角形三边长三角形三边长;2023-11-1241415.2 向上类型转换向上类型转换class Circle:public Shape/class Circle:public
44、 Shape/圆类圆类public:public:Circle()radius=0;Circle()radius=0;Circle(double r)radius=r;Circle(double r)radius=r;double double areaarea()return PI()return PI*radiusradius*radius;radius;void void showshow()coutradius=radiusendl;()coutradius=radiusendl;private:private:double radius;double radius;2023-11-1
45、242425.2 向上类型转换向上类型转换int main()int main()Shape Shape*s;s;Circle c(10);Circle c(10);Rectangle r(6,8);Rectangle r(6,8);Triangle t(3,4,5);Triangle t(3,4,5);c.c.showshow();/();/静态多态静态多态 coutcout圆面积:圆面积:c.area()endl;c.area()s-showshow();();cout cout矩形面积:矩形面积:area()endl;area()s-showshow();();cout cout三角形面
46、积:三角形面积:area()endl;area()endl;return 0;return 0;2023-11-1243435.5 纯虚函数和抽象类纯虚函数和抽象类v如果一个类至少有一个纯虚函数,那么就称该类为抽如果一个类至少有一个纯虚函数,那么就称该类为抽象类。象类。v对于抽象类的使用有以下几点规定:对于抽象类的使用有以下几点规定:(1 1)由于抽象类中至少包含一个没有定义功能的)由于抽象类中至少包含一个没有定义功能的纯虚函数。因此,抽象类只能作为其他类的基类来纯虚函数。因此,抽象类只能作为其他类的基类来使用,不能建立抽象类对象,这只能用来为派生类使用,不能建立抽象类对象,这只能用来为派生类
47、提供一个接口规范,其纯虚函数的实现由派生类给提供一个接口规范,其纯虚函数的实现由派生类给出。出。2023-11-1244445.5 纯虚函数和抽象类纯虚函数和抽象类v对于抽象类的使用有以下几点规定:对于抽象类的使用有以下几点规定:(2 2)不允许从具体类派生出抽象类,。所谓具体)不允许从具体类派生出抽象类,。所谓具体类,就是不包含纯虚函数的普通类。类,就是不包含纯虚函数的普通类。(3 3)抽象类不能用作参数类型、函数返回类型或)抽象类不能用作参数类型、函数返回类型或显式转换的类型。显式转换的类型。(4 4)可以声明指向抽象类的指针或引用,此指针)可以声明指向抽象类的指针或引用,此指针可以指向它
48、的派生类,进而实现动态多态性。可以指向它的派生类,进而实现动态多态性。2023-11-1245455.5 纯虚函数和抽象类纯虚函数和抽象类v对于抽象类的使用有以下几点规定:对于抽象类的使用有以下几点规定:(5 5)如果派生类中没有重新定义纯虚函数,则派)如果派生类中没有重新定义纯虚函数,则派生类只是简单继承基类的纯虚函数,则这个派生类生类只是简单继承基类的纯虚函数,则这个派生类仍然是一个抽象类。如果派生类中给出了基类所有仍然是一个抽象类。如果派生类中给出了基类所有纯虚函数的实现,则该派生类就不再是抽象类了,纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以创建对象的具体类。它是一个可以创
49、建对象的具体类。(6 6)在抽象类中也可以定义普通成员函数或虚函)在抽象类中也可以定义普通成员函数或虚函数,虽然不能为抽象类声明对象,但仍然可以通过数,虽然不能为抽象类声明对象,但仍然可以通过派生类对象来调用这些不是纯虚函数的函数。派生类对象来调用这些不是纯虚函数的函数。2023-11-1246465.5 纯虚函数和抽象类纯虚函数和抽象类voverride override 虚函数的声明需要虚函数的声明需要virtualvirtual关键字,如果一个成员关键字,如果一个成员函数是虚函数,那么在后续派生类里的同名函数都函数是虚函数,那么在后续派生类里的同名函数都会是虚函数,无须再使用会是虚函数,
50、无须再使用virtualvirtual修饰。修饰。但当继承关系较复杂或者派生类里的成员函数很多但当继承关系较复杂或者派生类里的成员函数很多时,很难分辨出哪些函数继承自基类,哪些函数是时,很难分辨出哪些函数继承自基类,哪些函数是派生类特有的,增加了代码的维护成本。而且还有派生类特有的,增加了代码的维护成本。而且还有一个潜在的隐患,派生类可能无意使用了一个同名一个潜在的隐患,派生类可能无意使用了一个同名但函数原型不同的函数但函数原型不同的函数“覆盖覆盖”了基类的虚函数。了基类的虚函数。2023-11-1247475.5 纯虚函数和抽象类纯虚函数和抽象类【例【例5-65-6】虚函数重写与】虚函数重写