C++程序设计PPT电子教案课件虚函数与多态性.ppt
多态性(Polymorphism)是指一个名字,多种语义;或界面 相同,多种实现。重载函数是多态性的一种简单形式。虚函数允许函数调用与函数体的联系在运行时才进行,称为 动态联编。,第8章 虚函数与多态性,8.1 静态联编,8.2 类指针的关系,8.3 虚函数与动态联编,8.4 纯虚函数与抽象类,8.5 虚函数和多态性的应用,小结,第8章 虚函数与多态性,联编是指一个程序模块、代码之间互相关联的过程。静态联编,是程序的匹配、连接在编译阶段实现,也称为早期匹配。重载函数使用静态联编。动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编。switch 语句和 if 语句是动态联编的例子。,8.1 静态联编,8.1 静态联编,普通成员函数重载可表达为两种形式:,1.在一个类说明中重载,例如:void Show(int,char);void Show(char*,float);,8.1 静态联编,普通成员函数重载可表达为两种形式:,1.在一个类说明中重载,例如:void Show(int,char);与void Show(char*,float);不是同一函数,编译能够区分,2.基类的成员函数在派生类重载。有 3 种编译区分方法:,(1)根据参数的特征加以区分,8.1 静态联编,普通成员函数重载可表达为两种形式:,1.在一个类说明中重载,2.基类的成员函数在派生类重载。有 3 种编译区分方法:,(1)根据参数的特征加以区分,例如:A:Show();有别于B:Show();,(2)使用“:”加以区分,8.1 静态联编,普通成员函数重载可表达为两种形式:,1.在一个类说明中重载,2.基类的成员函数在派生类重载。有 3 种编译区分方法:,(1)根据参数的特征加以区分,(2)使用“:”加以区分,例如:Aobj.Show()调用A:Show()Bobj.Show()调用B:Show(),(3)根据类对象加以区分,根据this指针类型区分,基类指针和派生类指针与基类对象和派生类对象4种可能匹配:直接用基类指针引用基类对象;直接用派生类指针引用派生类对象;用基类指针引用一个派生类对象;用派生类指针引用一个基类对象。,8.2 类指针的关系,8.2 类指针的关系,例如:A*p;/指向类型 A 的对象的指针A A_obj;/类型 A 的对象B B_obj;/类型 B 的对象p=/p 指向类型 B 的对象,它是 A 的派生类,利用 p,可以通过 B_obj 访问所有从 A 类继承的元素,但不能用 p访问 B 类自定义的元素(除非用了显式类型转换),8.2.1 基类指针引用派生类对象,8.2.1 基类指针引用派生类对象,#include#includeclass A_class char name20;public:void put_name(char*s)strcpy(name,s);void show_name()cout put_name(Wang xiao hua);A_p-show_name();A_p=,例8-1 使用基类指针引用派生类对象,8.2.1 基类指针引用派生类对象,#include#includeclass A_class char name20;public:void put_name(char*s)strcpy(name,s);void show_name()cout put_name(Wang xiao hua);A_p-show_name();A_p=,基类指针,8.2.1 基类指针引用派生类对象,例8-1 使用基类指针引用派生类对象,#include#includeclass A_class char name20;public:void put_name(char*s)strcpy(name,s);void show_name()cout put_name(Wang xiao hua);A_p-show_name();A_p=,基类指针指向基类对象,8.2.1 基类指针引用派生类对象,例8-1 使用基类指针引用派生类对象,#include#includeclass A_class char name20;public:void put_name(char*s)strcpy(name,s);void show_name()cout put_name(Wang xiao hua);A_p-show_name();A_p=,基类指针调用基类成员函数,8.2.1 基类指针引用派生类对象,例8-1 使用基类指针引用派生类对象,#include#includeclass A_class char name20;public:void put_name(char*s)strcpy(name,s);void show_name()cout put_name(Wang xiao hua);A_p-show_name();A_p=,基类指针指向派生类对象,8.2.1 基类指针引用派生类对象,例8-1 使用基类指针引用派生类对象,#include#includeclass A_class char name20;public:void put_name(char*s)strcpy(name,s);void show_name()cout put_name(Wang xiao hua);A_p-show_name();A_p=,调用从基类继承的成员函数,8.2.1 基类指针引用派生类对象,例8-1 使用基类指针引用派生类对象,#include#includeclass A_class char name20;public:void put_name(char*s)strcpy(name,s);void show_name()cout put_name(Wang xiao hua);A_p-show_name();A_p=,用派生类对象调用派生类的成员函数,8.2.1 基类指针引用派生类对象,例8-1 使用基类指针引用派生类对象,#include#includeclass A_class char name20;public:void put_name(char*s)strcpy(name,s);void show_name()cout put_name(Wang xiao hua);A_p-show_name();A_p=,对基类指针强类型转换调用派生类的成员函数,8.2.1 基类指针引用派生类对象,例8-1 使用基类指针引用派生类对象,#include#includeclass A_class char name20;public:void put_name(char*s)strcpy(name,s);void show_name()cout put_name(Wang xiao hua);A_p-show_name();A_p=,8.2.1 基类指针引用派生类对象,例8-1 使用基类指针引用派生类对象,派生类指针只有经过强制类型转换之后,才能引用基类对象,8.2.2 派生类指针引用基类对象,8.2.2 派生类指针引用基类对象,#include#includeclass Date public:Date(int y,int m,int d)SetDate(y,m,d);void SetDate(int y,int m,int d)year=y;month=m;day=d;void Print()cout Print();cout hours:minutes:seconds n;private:int hours,minutes,seconds;void main()DateTime dt(2003,1,1,12,30,0);dt.Print();,例8-2 日期时间程序。在派生类中调用基类同名成员函数,8.2.2 派生类指针引用基类对象,#include#includeclass Date public:Date(int y,int m,int d)SetDate(y,m,d);void SetDate(int y,int m,int d)year=y;month=m;day=d;void Print()cout Print();cout hours:minutes:seconds n;private:int hours,minutes,seconds;void main()DateTime dt(2003,1,1,12,30,0);dt.Print();,(Date*)this)-Print();,对 this 指针作类型转换调用基类成员函数,8.2.2 派生类指针引用基类对象,例8-2 日期时间程序。在派生类中调用基类同名成员函数,#include#includeclass Date public:Date(int y,int m,int d)SetDate(y,m,d);void SetDate(int y,int m,int d)year=y;month=m;day=d;void Print()cout Print();cout hours:minutes:seconds n;private:int hours,minutes,seconds;void main()DateTime dt(2003,1,1,12,30,0);dt.Print();,(Date*)this)-Print();,对 this 指针作类型转换调用基类成员函数,8.2.2 派生类指针引用基类对象,例8-2 日期时间程序。在派生类中调用基类同名成员函数,#include#includeclass Date public:Date(int y,int m,int d)SetDate(y,m,d);void SetDate(int y,int m,int d)year=y;month=m;day=d;void Print()cout Print();cout hours:minutes:seconds n;private:int hours,minutes,seconds;void main()DateTime dt(2003,1,1,12,30,0);dt.Print();,8.2.2 派生类指针引用基类对象,例8-2 日期时间程序。在派生类中调用基类同名成员函数,Date:Print();,等价吗?为什么?,冠以关键字 virtual 的成员函数称为虚函数 实现运行时多态的关键首先是要说明虚函数,另外,必须用 基类指针调用派生类的不同实现版本,8.3 虚函数和动态联编,基类指针虽然获取派生类对象地址,却只能访问派生类从基类继承的成员,8.3.1 虚函数和基类指针,8.3.1 虚函数和基类指针,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,例8-3 演示基类指针的移动,8.3.1 虚函数和基类指针,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,p,定义基类指针,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,p,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,void who()cout First derived class:x,y n;,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,void who()cout First derived class:x,y n;,通过对象调用成员函数,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,void who()cout Second derived class:x,y,z n;,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,void who()cout Second derived class:x,y,z n;,基类指针做类型转换,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,通过基类指针只能访问从基类继承的成员,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;void who()cout who();p=,修改程序定义虚函数,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;virtual void who()cout who();p=,基类定义虚函数,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;virtual void who()cout who();p=,派生类的重定义版本默认为虚函数,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;virtual void who()cout who();p=,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;virtual void who()cout who();p=,p,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;virtual void who()cout who();p=,p,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;virtual void who()cout who();p=,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;virtual void who()cout who();p=,void who()cout First derived class:x,y n;,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;virtual void who()cout who();p=,void who()cout First derived class:x,y n;,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;virtual void who()cout who();p=,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;virtual void who()cout who();p=,void who()coutSecond derived class:x,y,zn;,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;virtual void who()cout who();p=,void who()coutSecond derived class:x,y,zn;,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,#includeclass Base public:Base(char xx)x=xx;virtual void who()cout who();p=,由于who()的虚特性随着p指向不同对象,this指针作类型转换执行不同实现版本,8.3.1 虚函数和基类指针,例8-3 演示基类指针的移动,注意:一个虚函数,在派生类层界面相同的重载函数都保持虚特性 虚函数必须是类的成员函数 不能将友员说明为虚函数,但虚函数可以是另一个类的友员 析构函数可以是虚函数,但构造函数不能是虚函数,8.3.1 虚函数和基类指针,8.3.1 虚函数和基类指针,在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同 如果仅仅返回类型不同,C+认为是错误重载 如果函数原型不同,仅函数名相同,丢失虚特性,8.3.2 虚函数的重载特性,8.3.2 虚函数的重载特性,例:class base public:virtual void vf1();virtual void vf2();virtual void vf3();void f();,void g()derived d;base*bp=,class derived:public base public:void vf1();/虚函数 void vf2(int);/重载,参数不同,虚特性丢失 char vf3();/error,仅返回类型不同 void f();/非虚函数重载;,8.3.2 虚函数的重载特性,8.3.2 虚函数的重载特性,构造函数不能是虚函数。建立一个派生类对象时,必须从类 层次的根开始,沿着继承路径逐个调用基类的构造函数 析构函数可以是虚的。虚析构函数用于指引 delete 运算符正 确析构动态对象,8.3.3 虚析构函数,8.3.3 虚析构函数,#includeclass A public:A()cout A:A()is called.n;class B:public A public:B()cout B:B()is called.n;void main()A*Ap=new B;B*Bp2=new B;cout delete first object:n;delete Ap;cout delete second object:n;delete Bp2;,例8-4 普通析构函数在删除动态派生类对象的调用情况,8.3.3 虚析构函数,#includeclass A public:A()cout A:A()is called.n;class B:public A public:B()cout B:B()is called.n;void main()A*Ap=new B;B*Bp2=new B;cout delete first object:n;delete Ap;cout delete second object:n;delete Bp2;,用基类指针建立派生类的动态对象,8.3.3 虚析构函数,例8-4 普通析构函数在删除动态派生类对象的调用情况,#includeclass A public:A()cout A:A()is called.n;class B:public A public:B()cout B:B()is called.n;void main()A*Ap=new B;B*Bp2=new B;cout delete first object:n;delete Ap;cout delete second object:n;delete Bp2;,用派生类指针建立派生类的动态对象,8.3.3 虚析构函数,例8-4 普通析构函数在删除动态派生类对象的调用情况,#includeclass A public:A()cout A:A()is called.n;class B:public A public:B()cout B:B()is called.n;void main()A*Ap=new B;B*Bp2=new B;cout delete first object:n;delete Ap;cout delete second object:n;delete Bp2;,析构由基类指针建立的派生类对象没有调用派生类析构函数,8.3.3 虚析构函数,例8-4 普通析构函数在删除动态派生类对象的调用情况,#includeclass A public:A()cout A:A()is called.n;class B:public A public:B()cout B:B()is called.n;void main()A*Ap=new B;B*Bp2=new B;cout delete first object:n;delete Ap;cout delete second object:n;delete Bp2;,析构由派生类指针建立的派生类对象正确调用派生类析构函数,8.3.3 虚析构函数,例8-4 普通析构函数在删除动态派生类对象的调用情况,#includeclass A public:A()cout A:A()is called.n;class B:public A public:B()cout B:B()is called.n;void main()A*Ap=new B;B*Bp2=new B;cout delete first object:n;delete Ap;cout delete second object:n;delete Bp2;,例8-5 虚析构函数在删除动态派生类对象的调用情况,virtual A()cout A:A()is called.n;,8.3.3 虚析构函数,#includeclass A public:A()cout A:A()is called.n;class B:public A public:B()cout B:B()is called.n;void main()A*Ap=new B;B*Bp2=new B;cout delete first object:n;delete Ap;cout delete second object:n;delete Bp2;,例8-5 虚析构函数在删除动态派生类对象的调用情况,virtual A()cout A:A()is called.n;,正确调用派生类构造函数释放所有资源,8.3.3 虚析构函数,#includeclass A public:A()cout A:A()is called.n;class B:public A public:B()cout B:B()is called.n;void main()A*Ap=new B;B*Bp2=new B;cout delete first object:n;delete Ap;cout delete second object:n;delete Bp2;,例8-5 虚析构函数在删除动态派生类对象的调用情况,virtual A()cout A:A()is called.n;,定义了基类虚析构函数,基类指针指向的 派生类动态对象也可以正确地用delete析构 设计类层次结构时,提供一个虚析构函数,能够使派生类对象在不同状态下正确调用 析构函数,8.3.3 虚析构函数,8.4 纯虚函数和抽象类,纯虚函数是一个在基类中说明的虚函数,在基类中没有定义,要求任何派生类都定义自己的版本 纯虚函数为各派生类提供一个公共界面 纯虚函数说明形式:virtual 类型 函数名(参数表)=0;一个具有纯虚函数的基类称为抽象类。,8.4 纯虚函数和抽象类,class point/*/;class shape;/抽象类 point center;public:point where()return center;void move(point p)enter=p;draw();virtual void rotate(int)=0;/纯虚函数 virtual void draw()=0;/纯虚函数;.,shape x;/error,抽象类不能建立对象shape*p;/ok,可以声明抽象类的指针shape f();/error,抽象类不能作为函数返回类型void g(shape);/error,抽象类不能作为参数类型shape/ok,可以声明抽象类的引用,例如:,8.4 纯虚函数和抽象类,class point/*/;class shape;/抽象类 point center;public:point where()return center;void move(point p)enter=p;draw();virtual void rotate(int)=0;/纯虚函数 virtual void draw()=0;/纯虚函数;.,class ab_circle:public shape int radius;public:void rotate(int);,例如:,ab_circle 类仍为抽象类ab_circle:draw()、ab_circle:rotate()也是纯虚函数,8.4 纯虚函数和抽象类,要使 ab_circle 成为非抽象类,必须作以下说明:class ab_circle:public shape int radius;public:void rotate(int);void draw();并提供 ab_circle:draw()和 ab_circle:rotate(int)的定义,/figure.hclass figure protected:double x,y;public:void set_dim(double i,double j=0)x=i;y=j;virtual void show_area()=0;class triangle:public figure public:void show_area()coutTriangle with high x and base y has an area of x*0.5*yn;class square:public figure public:void show_area()coutSquare with dimension x*y has an area of x*yn;class circle:public figure public:void show_area()coutCircle with radius x;cout has an area of 3.14*x*xn;,virtual void show_area()=0;/纯虚函数,例8-6 简单图形类,void show_area()coutTriangle with high x and base y has an area of x*0.5*yn;,void show_area()coutSquare with dimension x*y has an area of x*yn;,void show_area()coutCircle with radius x;cout has an area of 3.14*x*xn;,8.4 纯虚函数和抽象类,/figure.hclass figure protected:double x,y;public:void set_dim(double i,double j=0)x=i;y=j;virtual void show_area()=0;class triangle:public figure public:void show_area()coutTriangle with high x and base y has an area of x*0.5*yn;class square:public figure public:void show_area()coutSquare with dimension x*y has an area of x*yn;class circle:public figure public:void show_area()coutCircle with radius x;cout has an area of 3.14*x*xn;,virtual void show_area()=0;/纯虚函数,void show_area()coutTriangle with high x and base y has an area of x*0.5*yn;,void show_area()coutSquare with dimension x*y has an area of x*yn;,void show_area()coutCircle with radius x;cout has an area of 3.14*x*xn;,#include#includefigure.hvoid main()triangle t;/派生类对象 square s;circle c;t.set_dim(10.0,5.0);t.show_area();s.set_dim(10.0,5.0);s.show_area();c.set_dim(9.0);c.show_area();,8.4 纯虚函数和抽象类,例8-6 简单图形类,例8-6 简单图形类,8.4 纯虚函数和抽象类,/figure.hclass figure protected:double x,y;public:void set_dim(double i,double j=0)x=i;y=j;virtual void show_area()=0;class triangle:public figure public:void show_area()coutTriangle with high x and base y has an area of x*0.5*yn;class square:public figure public:void show_area()coutSquare with dimension x*y has an area of x*yn;class circle:public figure public:void show_area()coutCircle with radius x;cout has an area of 3.14*x*xn;,virtual void show_area()=0;/纯虚函数,void show_area()coutTriangle with high x and base y has an area of x*0.5*yn;,void show_area()coutSquare with dimension x*y has an area of x*yn;,void show_area()coutCircle with radius x;cout has an area of 3.14*x*xn;,#include#includefigure.hvoid main()figure*p;/声明抽象类指针 triangle t;square s;circle c;p=,#includeclass Number public:Number(int i)val=i;virtual void Show()=0;protected:int val;class Hex_type:public Number public:Hex_type(int i):Number(i)void Show()cout Hexadecimal:hex val endl;class Dec_type:public Number public:Dec_type(int i):Number(i)void Show()cout Decimal:dec val endl;class Oct_type:public Number public:Oct_type(int i):Number(i)void Show()cout Octal:oct val endl;,例8-7 使用抽象类引用,void fun(Number/Oct_type:Show(),8.4 纯虚函数和抽象类,#includeclass Number public:Number(int i)val=i;virtual void Show()=0;protected:int val;class Hex_type:public Number public:Hex_type(int i):Number(i)void Show()cout Hexadecimal:hex val endl;class Dec_type:public Number public:Dec_type(int i):Number(i)void Show()cout Decimal:dec val endl;class Oct_type:public Number public:Oct_type(int i):Number(i)void Show()cout Octal:oct val endl;,void fun(Number/Oct_type:Show(),抽象类引用,8.4 纯虚函数和抽象类,例8-7 使用抽象类引用,#includeclass Number public:Number(int i)val=i;virtual void Show()=0;protected:int val;class Hex_type:public Number public:Hex_type(int i):Numbe