第8章2━━多态性与虚函数课件.ppt

上传人(卖家):晟晟文业 文档编号:5067611 上传时间:2023-02-07 格式:PPT 页数:36 大小:206KB
下载 相关 举报
第8章2━━多态性与虚函数课件.ppt_第1页
第1页 / 共36页
第8章2━━多态性与虚函数课件.ppt_第2页
第2页 / 共36页
第8章2━━多态性与虚函数课件.ppt_第3页
第3页 / 共36页
第8章2━━多态性与虚函数课件.ppt_第4页
第4页 / 共36页
第8章2━━多态性与虚函数课件.ppt_第5页
第5页 / 共36页
点击查看更多>>
资源描述

1、C+C+程序设计程序设计第第8 8章章(2)(2)多态性与虚函数多态性与虚函数主要内容主要内容l C+C+的多态性的多态性l 动态多态性的实现条件动态多态性的实现条件l 虚函数的声明虚函数的声明l 虚函数的特性与调用虚函数的特性与调用l 静态关联、动态关联静态关联、动态关联l 虚析构函数虚析构函数l 纯虚函数纯虚函数l 抽象类抽象类l 综合实例综合实例C+C+的多态性的多态性l 多态性:多态性:指对不同类型的对象发送同样的消息(即调用同名的函数),不同类型的指对不同类型的对象发送同样的消息(即调用同名的函数),不同类型的对象在接收时会产生不同的行为(即执行各自同名的函数)。对象在接收时会产生不

2、同的行为(即执行各自同名的函数)。l 编译时多态性(静态多态性):编译时多态性(静态多态性):指在编译阶段,系统就可根据所操作的对象,确定指在编译阶段,系统就可根据所操作的对象,确定其具体的操作。其具体的操作。编译时多态性是通过函数重载、运算符重载来实现的。编译时多态性是通过函数重载、运算符重载来实现的。函数重载是函数重载是根据函数调用式中所给出的实参的类型或实参的个数,在编译阶段系统就可确定调根据函数调用式中所给出的实参的类型或实参的个数,在编译阶段系统就可确定调用的是同名函数中的哪一个。运算符重载是根据运算式中所给出的运算对象的类型,用的是同名函数中的哪一个。运算符重载是根据运算式中所给出

3、的运算对象的类型,在编译阶段系统就可确定执行的是同种运算中的哪一个。在编译阶段系统就可确定执行的是同种运算中的哪一个。l 运行时多态性(动态多态性):运行时多态性(动态多态性):指在编译阶段,系统仅根据函数调用式是无法确定指在编译阶段,系统仅根据函数调用式是无法确定调用的是同名函数中的哪一个;必须在程序运行过程中,动态确定所要调用函数的调用的是同名函数中的哪一个;必须在程序运行过程中,动态确定所要调用函数的当前对象,并根据当前对象的类型来确定调用的是同名函数中的哪一个。当前对象,并根据当前对象的类型来确定调用的是同名函数中的哪一个。运行时多运行时多态性是通过态性是通过“类的继承关系类的继承关系

4、”加上加上“虚函数虚函数”联合起来实现的。联合起来实现的。动态多态性的实现条件动态多态性的实现条件l 要有类的继承层次结构:要有类的继承层次结构:一个基类可以派生出不同的派生类,各派生类中可以新增一个基类可以派生出不同的派生类,各派生类中可以新增与基类中的函数名字相同、参数个数及类型也相同的成员,这些同名的成员函数在与基类中的函数名字相同、参数个数及类型也相同的成员,这些同名的成员函数在不同的派生类中就有不同的含义!这样,不同的派生类中就有不同的含义!这样,在类的继承结构中,不同的层次上出现了在类的继承结构中,不同的层次上出现了名字相同、参数个数及类型也相同、但功能不同的函数!名字相同、参数个

5、数及类型也相同、但功能不同的函数!l 引入虚函数:引入虚函数:作用是在由一个基类派生出的类体系中实现作用是在由一个基类派生出的类体系中实现“一个接口,多种方法一个接口,多种方法”,主要用于建立通用程序。对于同一类体系中的各层次派生类,使用虚函数可实现统主要用于建立通用程序。对于同一类体系中的各层次派生类,使用虚函数可实现统一的类接口,以便用相同的方式对各层次派生类的对象进行操作。一的类接口,以便用相同的方式对各层次派生类的对象进行操作。虚函数是基类的虚函数是基类的成员函数,是为了实现某一种功能而假设的虚拟函数,在该基类的各层次派生类中成员函数,是为了实现某一种功能而假设的虚拟函数,在该基类的各

6、层次派生类中对该虚函数可有各自不同的定义对该虚函数可有各自不同的定义!l 要能体现虚函数的特性:要能体现虚函数的特性:必须通过基类的对象指针、基类的对象引用来调用各层次必须通过基类的对象指针、基类的对象引用来调用各层次派生类对象的同名虚函数,才能体现虚函数的特性!派生类对象的同名虚函数,才能体现虚函数的特性!因为只有这样才能用相同的调因为只有这样才能用相同的调用方式去调用不同层次派生类对象的同名虚函数,从而实现动态多态性。用方式去调用不同层次派生类对象的同名虚函数,从而实现动态多态性。虚函数的声明虚函数的声明l 虚函数的声明:虚函数的声明:class 基类基类 virtual 返回值类型返回值

7、类型 成员函数名成员函数名 (形参表形参表)函数体函数体 ;当一个基类的某成员函数声明为虚函数,则在该基类的所有派生类中,与虚函数同当一个基类的某成员函数声明为虚函数,则在该基类的所有派生类中,与虚函数同名、参数个数及类型相同、且返回值类型也相同的,不论是否有关键字名、参数个数及类型相同、且返回值类型也相同的,不论是否有关键字 virtual 修饰,修饰,都是虚函数,反之不然。但要注意:若在派生类中只是与虚函数同名,而参数个数都是虚函数,反之不然。但要注意:若在派生类中只是与虚函数同名,而参数个数或类型有不同时,属于函数重载,不是虚函数!或类型有不同时,属于函数重载,不是虚函数!virtual

8、 只是用在类中声明虚函数,若在类外定义虚函数前面不要加只是用在类中声明虚函数,若在类外定义虚函数前面不要加 virtual。构造函数、静态成员函数不能声明为虚函数!析构函数可以声明为虚函数。构造函数、静态成员函数不能声明为虚函数!析构函数可以声明为虚函数。虚函数的特性与调用虚函数的特性与调用l 如何体现虚函数的特性?如何体现虚函数的特性?只有通过基类的对象指针、基类的对象引用来调用派生类只有通过基类的对象指针、基类的对象引用来调用派生类对象的虚函数时,才能体现虚函数的特性!而通过派生类对象的对象名、对象指针、对象的虚函数时,才能体现虚函数的特性!而通过派生类对象的对象名、对象指针、对象引用来调

9、用虚函数时,无法体现虚函数的特性!对象引用来调用虚函数时,无法体现虚函数的特性!l 派生类对象中派生类对象中一般成员函数一般成员函数的调用的调用方法:方法:可通过派生类对象的对象名、对象指针、对象引用来调用!调用过程:若派生类新可通过派生类对象的对象名、对象指针、对象引用来调用!调用过程:若派生类新增成员函数中存在该函数,则被调用;若不存在,则调用上一层基类中的该函数;增成员函数中存在该函数,则被调用;若不存在,则调用上一层基类中的该函数;若这一层基类中也不存在,就继续往上一层寻找若这一层基类中也不存在,就继续往上一层寻找 ,直至找到该函数并被调用。,直至找到该函数并被调用。l 派生类对象中派

10、生类对象中一般成员函数一般成员函数的调用的调用方法:方法:可通过基类的对象指针、基类的对象引用来调用派生类对象中的一般成员函数!但可通过基类的对象指针、基类的对象引用来调用派生类对象中的一般成员函数!但只能调用派生类中从该基类继承过来的那部分成员函数!只能调用派生类中从该基类继承过来的那部分成员函数!虚函数的特性与调用虚函数的特性与调用l 派生类对象中派生类对象中虚函数虚函数的调用的调用方法:方法:派生类对象的虚函数也是成员函数,可按一般成员函数的方式调用!即:可通过派派生类对象的虚函数也是成员函数,可按一般成员函数的方式调用!即:可通过派生类对象的对象名、对象指针、对象引用来调用!调用过程与

11、一般成员函数的调用生类对象的对象名、对象指针、对象引用来调用!调用过程与一般成员函数的调用过程相同!由此可见,这种调用方式无法体现虚函数的特性!过程相同!由此可见,这种调用方式无法体现虚函数的特性!l 派生类对象中派生类对象中虚函数虚函数的调用的调用方法:方法:可通过基类的对象指针、基类的对象引用来调用派生类对象的虚函数!调用过程:可通过基类的对象指针、基类的对象引用来调用派生类对象的虚函数!调用过程:调用的是派生类中的虚函数!若派生类中没有重新定义该虚函数,则调用的是上一调用的是派生类中的虚函数!若派生类中没有重新定义该虚函数,则调用的是上一层基类中的该虚函数;若在这一层基类中也没有重新定义

12、该虚函数,就继续往上一层基类中的该虚函数;若在这一层基类中也没有重新定义该虚函数,就继续往上一层寻找层寻找 ,直至,直至基类的对象指针、基类的对象引用它们本身所属的那一层基类!基类的对象指针、基类的对象引用它们本身所属的那一层基类!l 动态多态性的实现:动态多态性的实现:可以让基类的对象指针(或基类的对象引用)先后指向(或先可以让基类的对象指针(或基类的对象引用)先后指向(或先后引用)同一类族中不同派生类的对象,以便用相同的调用方式去调用不同派生类后引用)同一类族中不同派生类的对象,以便用相同的调用方式去调用不同派生类对象中的同名虚函数,从而实现动态多态性。对象中的同名虚函数,从而实现动态多态

13、性。【例】(派生类对象中【例】(派生类对象中一般成员函数一般成员函数的调用的调用方法:可通过派生类对象的对象名、方法:可通过派生类对象的对象名、对象指针、对象引用来调用!注意调用过程。)对象指针、对象引用来调用!注意调用过程。)#includeclass A int x;public:A(int a)x=a;void g()cout “A:x=”x endl;class B:public A int y;public:B(int a,int b):A(a)y=b;void f()cout “B:y=”y endl;class C:public B int m;public:C(int a,in

14、t b,int c):B(a,b)m=c;void g()cout “C:m=”m endl;class D:public C int n;public:D(int a,int b,int c,int d):C(a,b,c)n=d;void f()cout “D:n=”n f();pd-g();D&dd1=d1;dd1.f();dd1.g();运行:运行:D:n=4C:m=3D:n=4C:m=3D:n=4C:m=3 从从A类继承的成员:类继承的成员:int x;g();不是虚函数不是虚函数从从B类继承的成员:类继承的成员:int y;f();不是虚函数不是虚函数从从C类继承的成员:类继承的成员

15、:int m;g();不是虚函数不是虚函数D类新增的成员:类新增的成员:int n;f();不是虚函数不是虚函数 pdd1对象对象 dd1【例】(派生类对象中【例】(派生类对象中虚函数虚函数的调用的调用方法:可通过派生类对象的对象名、对象指方法:可通过派生类对象的对象名、对象指针、对象引用来调用!注意调用过程与一般成员函数相同!由此可见,这种调用方针、对象引用来调用!注意调用过程与一般成员函数相同!由此可见,这种调用方式无法体现虚函数的特性!)式无法体现虚函数的特性!)#includeclass A int x;public:A(int a)x=a;virtual void g()cout “

16、A:x=”x endl;class B:public A int y;public:B(int a,int b):A(a)y=b;virtual void f()cout “B:y=”y endl;class C:public B int m;public:C(int a,int b,int c):B(a,b)m=c;void g()cout “C:m=”m endl;class D:public C int n;public:D(int a,int b,int c,int d):C(a,b,c)n=d;void f()cout “D:n=”n f();pd-g();D&dd1=d1;dd1.

17、f();dd1.g();运行:运行:D:n=4C:m=3D:n=4C:m=3D:n=4C:m=3 从从A类继承的成员:类继承的成员:int x;g();虚函数虚函数从从B类继承的成员:类继承的成员:int y;f();虚函数虚函数从从C类继承的成员:类继承的成员:int m;g();虚函数虚函数D类新增的成员:类新增的成员:int n;f();虚函数虚函数 pdd1对象对象 dd1【例】(派生类对象中【例】(派生类对象中一般成员函数一般成员函数的调用的调用方法:可通过基类的对象指针、基类方法:可通过基类的对象指针、基类的对象引用来调用!但只能调用派生类中从该基类继承来的那部分成员函数!)的对象

18、引用来调用!但只能调用派生类中从该基类继承来的那部分成员函数!)#includeclass A int x;public:A(int a)x=a;void g()cout “A:x=”x endl;class B:public A int y;public:B(int a,int b):A(a)y=b;void g()cout “B:y=”y endl;class C:public B int z;public:C(int a,int b,int c):B(a,b)z=c;void g()cout “C:z=”z endl;void f()cout “Bye!”g();aa1.g();/pa-

19、f();为什么不行?为什么不行?B *pb=&c1,&bb1=c1;pb-g();bb1.g();/pb-f();为什么不行?为什么不行?C *pc=&c1,&cc1=c1;pc-g();cc1.g();pc-f();从从A类继承的成员:类继承的成员:int x;g();不是虚函数不是虚函数从从B类继承的成员:类继承的成员:int y;g();不是虚函数不是虚函数C类新增的成员:类新增的成员:int z;g();不是虚函数不是虚函数f();不是虚函数不是虚函数 pb pa pcC1对象对象运行:运行:A:x=1A:x=1B:y=2B:y=2C:z=3C:z=3 Bye!【例】(派生类对象中【例

20、】(派生类对象中虚函数虚函数的调用的调用方法:可通过基类的对象指针、基类的对象方法:可通过基类的对象指针、基类的对象引用来调用!注意:引用来调用!注意:调用的是派生类中的虚函数!若派生类中没有重新定义该虚函调用的是派生类中的虚函数!若派生类中没有重新定义该虚函数,则调用的是上一层基类中的该虚函数,数,则调用的是上一层基类中的该虚函数,)#includeclass A int x;public:A(int a)x=a;virtual void g()cout “A:x=”x endl;class B:public A int y;public:B(int a,int b):A(a)y=b;voi

21、d g()cout “B:y=”y endl;class C:public B int z;public:C(int a,int b,int c):B(a,b)z=c;void g()cout “C:z=”z endl;virtual void f()cout “Bye!”g();aa1.g();/pa-f();为什么不行?为什么不行?B *pb=&c1,&bb1=c1;pb-g();bb1.g();/pb-f();为什么不行?为什么不行?C *pc=&c1,&cc1=c1;pc-g();cc1.g();pc-f();从从A类继承的成员:类继承的成员:int x;g();虚函数虚函数从从B类继

22、承的成员:类继承的成员:int y;g();虚函数虚函数C类新增的成员:类新增的成员:int z;g();虚函数虚函数f();虚函数虚函数 pb pa pcC1对象对象运行:运行:C:z=3C:z=3C:z=3C:z=3C:z=3C:z=3 Bye!【例】(请注意调用过程。)【例】(请注意调用过程。)#includeclass A int x;public:A(int a)x=a;virtual void g()cout “A:x=”x endl;class B:public A int y;public:B(int a,int b):A(a)y=b;void g()cout “B:y=”y

23、endl;class C:public B int z;public:C(int a,int b,int c):B(a,b)z=c;virtual void f()cout “Bye!”g();aa1.g();/pa-f();为什么不行?为什么不行?B *pb=&c1,&bb1=c1;pb-g();bb1.g();/pb-f();为什么不行?为什么不行?C *pc=&c1,&cc1=c1;pc-g();cc1.g();pc-f();从从A类继承的成员:类继承的成员:int x;g();虚函数虚函数从从B类继承的成员:类继承的成员:int y;g();虚函数虚函数C类新增的成员:类新增的成员:i

24、nt z;f();虚函数虚函数 pb pa pcC1对象对象运行:运行:B:y=2B:y=2B:y=2B:y=2B:y=2B:y=2Bye!【例【例】(请注意调用过程。)(请注意调用过程。)#includeclass A int x;public:A(int a)x=a;void g()cout “A:x=”x endl;class B:public A int y;public:B(int a,int b):A(a)y=b;virtual void g()cout “B:y=”y endl;class C:public B int z;public:C(int a,int b,int c):

25、B(a,b)z=c;void g()cout “C:z=”z endl;void f()cout “Bye!”g();aa1.g();/pa-f();为什么不行?为什么不行?B *pb=&c1,&bb1=c1;pb-g();bb1.g();/pb-f();为什么不行?为什么不行?C *pc=&c1,&cc1=c1;pc-g();cc1.g();pc-f();从从A类继承的成员:类继承的成员:int x;g();不是虚函数不是虚函数从从B类继承的成员:类继承的成员:int y;g();虚函数虚函数C类新增的成员:类新增的成员:int z;g();虚函数虚函数f();不是虚函数不是虚函数 pb p

26、a pcC1对象对象运行:运行:A:x=1A:x=1C:z=3C:z=3 C:z=3C:z=3 Bye!【例【例】(注意,若派生类中的某函数只是与虚函数同名,但参数个数或类型有不同时,】(注意,若派生类中的某函数只是与虚函数同名,但参数个数或类型有不同时,则属于函数的重载,而不是虚函数!)则属于函数的重载,而不是虚函数!)#includeclass A int x;public:A(int a)x=a;virtual void g()cout “A:x=”x endl;class B:public A int y;public:B(int a,int b):A(a)y=b;void g()co

27、ut “B:y=”y endl;class C:public B int z;public:C(int a,int b,int c):B(a,b)z=c;void g(int a)cout “C:z=”z endl;/是重载是重载函数,不是虚函数。函数,不是虚函数。void f()cout “Bye!”g();aa1.g();/pa-f();为什么不行?为什么不行?B *pb=&c1,&bb1=c1;pb-g();bb1.g();/pb-f();为什么不行?为什么不行?C *pc=&c1,&cc1=c1;pc-g(1);cc1.g(1);pc-f();从从A类继承的成员:类继承的成员:int

28、x;g();虚函数虚函数从从B类继承的成员:类继承的成员:int y;g();虚函数虚函数C类新增的成员:类新增的成员:int z;g(int);不是不是虚函数虚函数f();不是虚函数不是虚函数 pb pa pcC1对象对象运行:运行:B:y=2B:y=2B:y=2B:y=2C:z=3C:z=3 Bye!【例】(【例】(动态多态性的实现:动态多态性的实现:可以让基类的对象指针,先后指向同一类族中不同派生类可以让基类的对象指针,先后指向同一类族中不同派生类的对象,这样就可以用相同的调用方式去调用不同派生类对象中的同名虚函数,从的对象,这样就可以用相同的调用方式去调用不同派生类对象中的同名虚函数,

29、从而实现动态多态性。)而实现动态多态性。)#includeclass A int x;public:A(int a=0)x=a;cout “调用调用A类构造了类构造了!n”;virtual void show()cout “A类类 x=”x endl;class B:public A int y;public:B(int a=0,int b=0):A(a)y=b;cout “调用调用B类构造了类构造了!n”;void show()cout “B类类 y=”y endl;class C:public B int z;public:C(int a=0,int b=0,int c=0):B(a,b)

30、z=c;cout “调用调用C类构造了类构造了!n”;void show()cout “C类类 z=”z show();p=&b1;p-show();p=&c1;p-show();注意:可以让基类的对象指针,先后指向同一类族注意:可以让基类的对象指针,先后指向同一类族中不同派生类的对象,这样就可以用相同的调用方中不同派生类的对象,这样就可以用相同的调用方式去调用不同派生类对象中的同名虚函数,从而实式去调用不同派生类对象中的同名虚函数,从而实现动态多态性。现动态多态性。运行:运行:调运调运A类构造了!类构造了!调运调运A类构造了!类构造了!调运调运B类构造了!类构造了!调运调运A类构造了!类构造

31、了!调运调运B类构造了!类构造了!调运调运C类构造了!类构造了!A类类 x=1B类类 y=30C类类 z=600 基类指针基类指针Pint x;show()虚函数虚函数对象对象c1int y;show()虚函数虚函数int z;show()虚函数虚函数int x;show()虚函数虚函数int y;show()虚函数虚函数对象对象b1int x;show()虚函数虚函数对象对象a1静态关联、动态关联静态关联、动态关联l 关联(绑定、联编):关联(绑定、联编):指程序自身彼此关联的过程,即程序中不同部分互相绑定的指程序自身彼此关联的过程,即程序中不同部分互相绑定的过程,以确定程序中的各个函数调用

32、式与所执行的相应函数代码的关系,简单的说,过程,以确定程序中的各个函数调用式与所执行的相应函数代码的关系,简单的说,关联就是把某个标识符与某个存储地址联系起来。关联就是把某个标识符与某个存储地址联系起来。l 静态关联(静态绑定、静态联编):静态关联(静态绑定、静态联编):指关联(绑定、联编)工作出现在编译阶段。指关联(绑定、联编)工作出现在编译阶段。例如,程序中使用对象名来调用成员函数时,在编译阶段系统就能根据对象的类型例如,程序中使用对象名来调用成员函数时,在编译阶段系统就能根据对象的类型确定所要调用的是哪一个类的成员函数并进行关联。再如,使用对象名来调用某个确定所要调用的是哪一个类的成员函

33、数并进行关联。再如,使用对象名来调用某个类族中的虚函数时,在编译阶段系统也能确定要调用的虚函数属于哪一个类并进行类族中的虚函数时,在编译阶段系统也能确定要调用的虚函数属于哪一个类并进行关联。关联。此外,函数重载和运算符重载也是在编译阶段进行关联的。此外,函数重载和运算符重载也是在编译阶段进行关联的。l 动态关联(动态绑定、动态联编):动态关联(动态绑定、动态联编):指绑定(联编、关联)工作出现在运行阶段。指绑定(联编、关联)工作出现在运行阶段。例如,程序中使用基类的对象指针(或基类的对象引用)来调用某个类族中的虚函例如,程序中使用基类的对象指针(或基类的对象引用)来调用某个类族中的虚函数时,只

34、有在程序运行过程中,才能根据其具体指向(或具体引用)该类族中的哪数时,只有在程序运行过程中,才能根据其具体指向(或具体引用)该类族中的哪一个类的对象,确定出调用的是哪一个类的虚函数。一个类的对象,确定出调用的是哪一个类的虚函数。静态关联、动态关联静态关联、动态关联l 虚函数的动态关联:虚函数的动态关联:只有通过基类的对象指针(或基类的对象引用)来调用同一类只有通过基类的对象指针(或基类的对象引用)来调用同一类族中派生类的虚函数时,才属于动态关联!原因是:只有在程序运行过程中,系统族中派生类的虚函数时,才属于动态关联!原因是:只有在程序运行过程中,系统才能根据该指针(或该引用)具体指向(或具体引

35、用)的是同一类族中的哪一个派才能根据该指针(或该引用)具体指向(或具体引用)的是同一类族中的哪一个派生类的对象,确定出调用的是哪一个派生类的虚函数。生类的对象,确定出调用的是哪一个派生类的虚函数。l 注意:注意:虚函数与一般成员函数比较,调用时执行速度要慢一些。使用虚函数,系统要有一虚函数与一般成员函数比较,调用时执行速度要慢一些。使用虚函数,系统要有一定的空间和时间开销。定的空间和时间开销。当一个类中有虚函数时,编译系统会为该类构造一个虚函数表,它是一个指针数组,当一个类中有虚函数时,编译系统会为该类构造一个虚函数表,它是一个指针数组,存放该类的每个虚函数的入口地址。存放该类的每个虚函数的入

36、口地址。由于虚函数的调用机制是间接实现的,且动态关联是在程序运行阶段,相对会降低由于虚函数的调用机制是间接实现的,且动态关联是在程序运行阶段,相对会降低程序的运行效率。程序的运行效率。但通用性也是程序追求的主要目标之一。但通用性也是程序追求的主要目标之一。l 虚函数必须是非静态的成员函数:虚函数必须是非静态的成员函数:因为静态成员函数不受限于某个对象。因为静态成员函数不受限于某个对象。【例】(分别通过对象名、基类的对象指针去调用派生类的虚函数。【例】(分别通过对象名、基类的对象指针去调用派生类的虚函数。)#includeclass A int x;public:A()x=10;virtual

37、void show()cout “x=”x endl;class B:public A int y;public:B()y=20;void show()cout “y=”y endl;class C:public A int z;public:C()z=30;void show()cout “z=”z show();p=&b;p-show();/此处调用属于动态关联!此处调用属于动态关联!p=&c;p-show();/此处调用属于动态关联!此处调用属于动态关联!运行:运行:x=10y=20z=30 x=10y=20z=30基类指针基类指针pint x;show()虚函数虚函数int y;sho

38、w()虚函数虚函数对象对象bint x;show()虚函数虚函数对象对象aint x;show()虚函数虚函数int z;show()虚函数虚函数对象对象c【例】(【例】(在成员函数中调用虚函数:在成员函数中调用虚函数:关键是分析在调用该成员函数时,其关键是分析在调用该成员函数时,其 this 指针是指针是基类的对象指针还是派生类的对象指针!基类的对象指针还是派生类的对象指针!)#includeclass A public:virtual void f1()cout f2();void f2()cout f3();virtual void f3()cout f4();virtual void

39、f4()cout f5();void f5()cout “A:f5n”;class B:public A public:void f3()cout f4();void f4()cout f5();void f5()cout “B:f5n”;void main()B b;b.f1();对象对象bA类继承:类继承:f1()虚函数虚函数f2()f3()虚函数虚函数f4()虚函数虚函数f5()B类新增:类新增:f3()虚函数虚函数f4()虚函数虚函数f5()运行:运行:A:f1A:f2B:f3B:f4B:f5【例】(【例】(在成员函数中调用虚函数:在成员函数中调用虚函数:关键是分析在调用该成员函数时,

40、其关键是分析在调用该成员函数时,其 this 指针是指针是基类的对象指针还是派生类的对象指针!基类的对象指针还是派生类的对象指针!)#includeclass A public:void f1()cout f2();void f2()cout f3();virtual void f3()cout f4();virtual void f4()cout f5();void f5()cout “A:f5n”;class B:public A public:void f1()cout f2();void f3()cout f4();void f4()cout f5();void f5()cout f1

41、();A *pa=&b;pa-f1();对象对象bA类继承:类继承:f1()f2()f3()虚函数虚函数f4()虚函数虚函数f5()B类新增:类新增:f1()f3()虚函数虚函数f4()虚函数虚函数f5()运行:运行:B:f1A:f2B:f3B:f4B:f5A:f1A:f2B:f3B:f4B:f5【例】(切记!【例】(切记!在构造函数中调用虚函数在构造函数中调用虚函数与在成员函数中调用虚函数有区别!与在成员函数中调用虚函数有区别!)#includeclass A public:A()f();virtual void f()cout “A:fn”;class B:public A public:

42、B()f();void g1()cout “B:fn”;void g2()f();class C:public B public:C()f();void f()cout “C:fn”;class D:public C public:D()f();void g3()cout “D:fn”;void main()D d;d.g2();A类继承:类继承:f()虚函数虚函数对象对象dB类继承:类继承:g1()g2()C类继承:类继承:f()虚函数虚函数D类新增:类新增:g3()注意:注意:在构造函数中调用虚在构造函数中调用虚函数时,调用的是本类中定函数时,调用的是本类中定义的虚函数,若本类中没有义的虚

43、函数,若本类中没有定义该虚函数,则调用的是定义该虚函数,则调用的是上一层基类中定义的该函数。上一层基类中定义的该函数。运行:运行:A:fA:fC:fC:fC:f【例】(使用动态多态性,编写程序求球体、圆柱体的体积和表面积。)【例】(使用动态多态性,编写程序求球体、圆柱体的体积和表面积。)分析:分析:若球体半径为若球体半径为r,则:其体积,则:其体积 V=4r3/3;表面积;表面积 S=4r2 。若圆柱底圆半径为若圆柱底圆半径为r,高为,高为h,则:其体积,则:其体积 V=r2 h;表面积;表面积 S=2r(r+h)。由于球体、圆柱体均可从圆继承而来,所以先定义基类由于球体、圆柱体均可从圆继承而

44、来,所以先定义基类 Circle,在基类中定义两个,在基类中定义两个虚函数虚函数求体积虚函数求体积虚函数 volume()、求表面积虚函数、求表面积虚函数 area()。球体类。球体类 Sphere 和和圆柱体类圆柱体类 Cylinder 可由可由 圆类圆类 Circle 派生而来,在两个派生类中分别对两个虚函数派生而来,在两个派生类中分别对两个虚函数进行重新定义,用于计算球体、圆柱体的体积和表面积。进行重新定义,用于计算球体、圆柱体的体积和表面积。在主函数在主函数 main()中,定义一个基类中,定义一个基类 Circle 的对象指针的对象指针 p,先后用来指向球体类,先后用来指向球体类Sp

45、here、圆柱体类、圆柱体类 Cylinder 这同一类族中不同派生类的对象,当基类指针这同一类族中不同派生类的对象,当基类指针 p 指向指向包含虚函数的包含虚函数的 Sphere 类、类、Cylinder 类的对象时,系统会根据该指针所指向对象的类的对象时,系统会根据该指针所指向对象的类型,来决定调用的是哪一个类的虚函数。类型,来决定调用的是哪一个类的虚函数。#includeconst double PI=3.14159;class Circle protected:float radius;public:Circle(float a=0):radius(a)virtual double v

46、olume()return 0;/虚函数虚函数 virtual double area()return (PI*radius*radius);/虚函数虚函数;class Sphere:public Circle public:Sphere(float a=0):Circle(a)double volume()return (4*PI*radius*radius*radius/3);/虚函数虚函数 double area()return (4*PI*radius*radius);/虚函数虚函数;class Cylinder:public Circle float h;public:Cylinde

47、r(float a=0,float b=0):Circle(a)h=b;double volume()return (PI*radius*radius*h);/虚函数虚函数 double area()return (2*PI*radius*(h+radius);/虚函数虚函数;void main()Circle *p;Sphere s(10);p=&s;cout “球体体积球体体积=”volume()endl;cout “球体表面积球体表面积=”area()endl;Cylinder c(20,30);p=&c;cout “圆柱体积圆柱体积=”volume()endl;cout “圆柱表面积圆

48、柱表面积=”area()endl;运行:运行:球体体积球体体积=4188.79球体表面积球体表面积=1256.64 圆柱体积圆柱体积=37699.1圆柱表面积圆柱表面积=6283.18 虚析构函数虚析构函数l 虚析构函数的声明:虚析构函数的声明:若将基类的析构函数声明为虚函数,则由该基类派生的所有派若将基类的析构函数声明为虚函数,则由该基类派生的所有派生类的析构函数都自动成为虚函数,即使这些派生类的析构函数名与基类的析构函生类的析构函数都自动成为虚函数,即使这些派生类的析构函数名与基类的析构函数名并不相同!数名并不相同!l 必须声明虚析构函数的情况:必须声明虚析构函数的情况:在由一个基类派生出

49、的类体系中,若需要动态创建派在由一个基类派生出的类体系中,若需要动态创建派生类对象,就必须将析构函数声明为虚函数,以实现撤消对象时的多态性!这样,生类对象,就必须将析构函数声明为虚函数,以实现撤消对象时的多态性!这样,若程序中用若程序中用 delete 运算符去撤消动态分配的派生类对象,而运算符去撤消动态分配的派生类对象,而 delete 运算符后面跟运算符后面跟着的是指向派生类对象的基类指针,则系统调用的不是基类的析构函数,而是派生着的是指向派生类对象的基类指针,则系统调用的不是基类的析构函数,而是派生类的析构函数!类的析构函数!l 习惯的做法:习惯的做法:一般在基类中都将析构函数声明为虚析

50、构函数,即使基类并不需要自一般在基类中都将析构函数声明为虚析构函数,即使基类并不需要自定义析构函数时,也显式定义一个函数体为空的虚析构函数,以保证在撤消动态分定义析构函数时,也显式定义一个函数体为空的虚析构函数,以保证在撤消动态分配的派生类对象时能得到正确的处理。配的派生类对象时能得到正确的处理。【例【例】(必须声明虚析构函数的情况。)】(必须声明虚析构函数的情况。)#includeclass A public:A()cout “A类构造了类构造了!n”;A()cout “A类析构了类析构了!n”;virtual void f()cout “A:fn”;void g()f();class B:

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 办公、行业 > 各类PPT课件(模板)
版权提示 | 免责声明

1,本文(第8章2━━多态性与虚函数课件.ppt)为本站会员(晟晟文业)主动上传,163文库仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。
2,用户下载本文档,所消耗的文币(积分)将全额增加到上传者的账号。
3, 若此文所含内容侵犯了您的版权或隐私,请立即通知163文库(发送邮件至3464097650@qq.com或直接QQ联系客服),我们立即给予删除!


侵权处理QQ:3464097650--上传资料QQ:3464097650

【声明】本站为“文档C2C交易模式”,即用户上传的文档直接卖给(下载)用户,本站只是网络空间服务平台,本站所有原创文档下载所得归上传人所有,如您发现上传作品侵犯了您的版权,请立刻联系我们并提供证据,我们将在3个工作日内予以改正。


163文库-Www.163Wenku.Com |网站地图|