《第-8-章(2)━━多态性与虚函数课件.ppt》由会员分享,可在线阅读,更多相关《第-8-章(2)━━多态性与虚函数课件.ppt(36页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、C+程序设计程序设计第第8章章(2)多态性与虚函数多态性与虚函数1主要内容主要内容lC+的多态性的多态性l动态多态性的实现条件动态多态性的实现条件l虚函数的声明虚函数的声明l虚函数的特性与调用虚函数的特性与调用l静态关联、动态关联静态关联、动态关联l虚析构函数虚析构函数l纯虚函数纯虚函数l抽象类抽象类l综合实例综合实例2C+的多态性的多态性l多态性:多态性:指对不同类型的对象发送同样的消息(即调用同名的函数),不同类型的指对不同类型的对象发送同样的消息(即调用同名的函数),不同类型的对象在接收时会产生不同的行为(即执行各自同名的函数)。对象在接收时会产生不同的行为(即执行各自同名的函数)。l编
2、译时多态性(静态多态性):编译时多态性(静态多态性):指在编译阶段,系统就可根据所操作的对象,确定指在编译阶段,系统就可根据所操作的对象,确定其具体的操作。其具体的操作。编译时多态性是通过函数重载、运算符重载来实现的。编译时多态性是通过函数重载、运算符重载来实现的。函数重载是函数重载是根据函数调用式中所给出的实参的类型或实参的个数,在编译阶段系统就可确定调根据函数调用式中所给出的实参的类型或实参的个数,在编译阶段系统就可确定调用的是同名函数中的哪一个。运算符重载是根据运算式中所给出的运算对象的类型,用的是同名函数中的哪一个。运算符重载是根据运算式中所给出的运算对象的类型,在编译阶段系统就可确定
3、执行的是同种运算中的哪一个。在编译阶段系统就可确定执行的是同种运算中的哪一个。l运行时多态性(动态多态性):运行时多态性(动态多态性):指在编译阶段,系统仅根据函数调用式是无法确定指在编译阶段,系统仅根据函数调用式是无法确定调用的是同名函数中的哪一个;必须在程序运行过程中,动态确定所要调用函数的调用的是同名函数中的哪一个;必须在程序运行过程中,动态确定所要调用函数的当前对象,并根据当前对象的类型来确定调用的是同名函数中的哪一个。当前对象,并根据当前对象的类型来确定调用的是同名函数中的哪一个。运行时多运行时多态性是通过态性是通过“类的继承关系类的继承关系”加上加上“虚函数虚函数”联合起来实现的。
4、联合起来实现的。3动态多态性的实现条件动态多态性的实现条件l要有类的继承层次结构:要有类的继承层次结构:一个基类可以派生出不同的派生类,各派生类中可以新增一个基类可以派生出不同的派生类,各派生类中可以新增与基类中的函数名字相同、参数个数及类型也相同的成员,这些同名的成员函数在与基类中的函数名字相同、参数个数及类型也相同的成员,这些同名的成员函数在不同的派生类中就有不同的含义!这样,不同的派生类中就有不同的含义!这样,在类的继承结构中,不同的层次上出现了在类的继承结构中,不同的层次上出现了名字相同、参数个数及类型也相同、但功能不同的函数!名字相同、参数个数及类型也相同、但功能不同的函数!l引入虚
5、函数:引入虚函数:作用是在由一个基类派生出的类体系中实现作用是在由一个基类派生出的类体系中实现“一个接口,多种方法一个接口,多种方法”,主要用于建立通用程序。对于同一类体系中的各层次派生类,使用虚函数可实现,主要用于建立通用程序。对于同一类体系中的各层次派生类,使用虚函数可实现统一的类接口,以便用相同的方式对各层次派生类的对象进行操作。统一的类接口,以便用相同的方式对各层次派生类的对象进行操作。虚函数是基类虚函数是基类的成员函数,是为了实现某一种功能而假设的虚拟函数,在该基类的各层次派生类的成员函数,是为了实现某一种功能而假设的虚拟函数,在该基类的各层次派生类中对该虚函数可有各自不同的定义中对
6、该虚函数可有各自不同的定义!l要能体现虚函数的特性:要能体现虚函数的特性:必须通过基类的对象指针、基类的对象引用来调用各层次必须通过基类的对象指针、基类的对象引用来调用各层次派生类对象的同名虚函数,才能体现虚函数的特性!派生类对象的同名虚函数,才能体现虚函数的特性!因为只有这样才能用相同的调因为只有这样才能用相同的调用方式去调用不同层次派生类对象的同名虚函数,从而实现动态多态性。用方式去调用不同层次派生类对象的同名虚函数,从而实现动态多态性。4虚函数的声明虚函数的声明l虚函数的声明:虚函数的声明:class 基类基类 virtual 返回值类型返回值类型 成员函数名成员函数名 (形参表形参表)
7、函数体函数体 ;当一个基类的某成员函数声明为虚函数,则在该基类的所有派生类中,与虚函数同名、当一个基类的某成员函数声明为虚函数,则在该基类的所有派生类中,与虚函数同名、参数个数及类型相同、且返回值类型也相同的,不论是否有关键字参数个数及类型相同、且返回值类型也相同的,不论是否有关键字 virtual 修饰,都修饰,都是虚函数,反之不然。但要注意:若在派生类中只是与虚函数同名,而参数个数或是虚函数,反之不然。但要注意:若在派生类中只是与虚函数同名,而参数个数或类型有不同时,属于函数重载,不是虚函数!类型有不同时,属于函数重载,不是虚函数!virtual 只是用在类中声明虚函数,若在类外定义虚函数
8、前面不要加只是用在类中声明虚函数,若在类外定义虚函数前面不要加 virtual。构造函数、静态成员函数不能声明为虚函数!析构函数可以声明为虚函数。构造函数、静态成员函数不能声明为虚函数!析构函数可以声明为虚函数。5虚函数的特性与调用虚函数的特性与调用l如何体现虚函数的特性?如何体现虚函数的特性?只有通过基类的对象指针、基类的对象引用来调用派生类只有通过基类的对象指针、基类的对象引用来调用派生类对象的虚函数时,才能体现虚函数的特性!而通过派生类对象的对象名、对象指针、对象的虚函数时,才能体现虚函数的特性!而通过派生类对象的对象名、对象指针、对象引用来调用虚函数时,无法体现虚函数的特性!对象引用来
9、调用虚函数时,无法体现虚函数的特性!l派生类对象中派生类对象中一般成员函数一般成员函数的调用的调用方法:方法:可通过派生类对象的对象名、对象指针、对象引用来调用!调用过程:若派生类新可通过派生类对象的对象名、对象指针、对象引用来调用!调用过程:若派生类新增成员函数中存在该函数,则被调用;若不存在,则调用上一层基类中的该函数;增成员函数中存在该函数,则被调用;若不存在,则调用上一层基类中的该函数;若若这这一一层层基基类类中也不存在,就中也不存在,就继续继续往上一往上一层寻层寻找找 ,直至找到,直至找到该该函数并被函数并被调调用。用。l派生类对象中派生类对象中一般成员函数一般成员函数的调用的调用方
10、法:方法:可通过基类的对象指针、基类的对象引用来调用派生类对象中的一般成员函数!但可通过基类的对象指针、基类的对象引用来调用派生类对象中的一般成员函数!但只能调用派生类中从该基类继承过来的那部分成员函数!只能调用派生类中从该基类继承过来的那部分成员函数!6虚函数的特性与调用虚函数的特性与调用l派生类对象中派生类对象中虚函数虚函数的调用的调用方法:方法:派生类对象的虚函数也是成员函数,可按一般成员函数的方式调用!即:可通过派派生类对象的虚函数也是成员函数,可按一般成员函数的方式调用!即:可通过派生类对象的对象名、对象指针、对象引用来调用!调用过程与一般成员函数的调用生类对象的对象名、对象指针、对
11、象引用来调用!调用过程与一般成员函数的调用过程相同!由此可见,这种调用方式无法体现虚函数的特性!过程相同!由此可见,这种调用方式无法体现虚函数的特性!l派生类对象中派生类对象中虚函数虚函数的调用的调用方法:方法:可通过基类的对象指针、基类的对象引用来调用派生类对象的虚函数!调用过程:可通过基类的对象指针、基类的对象引用来调用派生类对象的虚函数!调用过程:调调用的是派生用的是派生类类中的虚函数!若派生中的虚函数!若派生类类中没有重新定中没有重新定义该义该虚函数,虚函数,则调则调用的是上一用的是上一层层基基类类中的中的该该虚函数;若在虚函数;若在这这一一层层基基类类中也没有重新定中也没有重新定义该
12、义该虚函数,就虚函数,就继续继续往上一往上一层寻层寻找找 ,直至,直至基类的对象指针、基类的对象引用它们本身所属的那一层基类!基类的对象指针、基类的对象引用它们本身所属的那一层基类!l动态多态性的实现:动态多态性的实现:可以让基类的对象指针(或基类的对象引用)先后指向(或先可以让基类的对象指针(或基类的对象引用)先后指向(或先后引用)同一类族中不同派生类的对象,以便用相同的调用方式去调用不同派生类后引用)同一类族中不同派生类的对象,以便用相同的调用方式去调用不同派生类对象中的同名虚函数,从而实现动态多态性。对象中的同名虚函数,从而实现动态多态性。7【例例】(派生类对象中(派生类对象中一般成员函
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,int b,int c):B(a,b)m=c;void g
14、()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类继承的成员:类继承的成员:int m;g();不是虚函数不是虚函数D类新增的成
15、员:类新增的成员:int n;f();不是虚函数不是虚函数 pdd1对象对象 dd18【例例】(派生类对象中(派生类对象中虚函数虚函数的调用的调用方法:可通过派生类对象的对象名、对象指方法:可通过派生类对象的对象名、对象指针、对象引用来调用!注意调用过程与一般成员函数相同!由此可见,这种调用方针、对象引用来调用!注意调用过程与一般成员函数相同!由此可见,这种调用方式无法体现虚函数的特性!)式无法体现虚函数的特性!)#includeclass A int x;public:A(int a)x=a;virtual void g()cout “A:x=”x endl;class B:public A
16、 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.f();dd1.g();运行:运行:D:n=4C:m=3
17、D: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对象对象 dd19【例例】(派生类对象中(派生类对象中一般成员函数一般成员函数的调用的调用方法:可通过基类的对象指针、基类方法:可通过基类的对象指针、基类的对象引用来调用!但只能调用派生类中从该基类继承来的那部分成员函数!)的对象引用来调用!但只能调用派生类中从该基类继承来的那部分成员函
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-f();为为什么不行?什么不行?B *pb=&c1,&bb
19、1=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!10【例例】(派生类对象中(派生类对象中虚函数虚函数的调用的调用方法:可通过基类的
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;void g()cout “B:y=”y endl;class
21、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类继承的成员:类继承的成员:int y;g();虚函数虚函数C
22、类类新增的成员:新增的成员:int z;g();虚函数虚函数f();虚函数虚函数 pb pa pcC1对象对象运行:运行:C:z=3C:z=3C:z=3C:z=3C:z=3C:z=3 Bye!11【例例】(请注意调用过程。)(请注意调用过程。)#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 endl;class C:public B int z;p
23、ublic: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类类新增的成员:新增的成员:int z;f();虚函数虚函数 pb pa pcC1对象对
24、象运行:运行:B:y=2B:y=2B:y=2B:y=2B:y=2B:y=2Bye!12【例例】(请注意调用过程。)(请注意调用过程。)#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):B(a,b)z=c;void g()cout “C:z=
25、”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 pa pcC1对象对象运行:运行:A:x=1A:x=1C:
26、z=3C:z=3 C:z=3C:z=3 Bye!13【例例】(注意,若派生类中的某函数只是与虚函数同名,但参数个数或类型有不同时,(注意,若派生类中的某函数只是与虚函数同名,但参数个数或类型有不同时,则属于函数的重载,而不是虚函数!)则属于函数的重载,而不是虚函数!)#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 endl;class C:publ
27、ic 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 x;g();虚函数虚函数从从B类继承的成员:类继承的成员
28、: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!14【例例】(动态多态性的实现:动态多态性的实现:可以让基类的对象指针,先后指向同一类族中不同派生类可以让基类的对象指针,先后指向同一类族中不同派生类的对象,这样就可以用相同的调用方式去调用不同派生类对象中的同名虚函数,从的对象,这样就可以用相同的调用方式去调用不同派生类对象中的同名虚函数,从而实现动态多态性。)而实现动态多态性。)#include
29、class 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)z=c;cout “调用调用C类构造了类构造了!n”;vo
30、id show()cout “C类类 z=”z show();p=&b1;p-show();p=&c1;p-show();注意:可以让基类的对象指针,先后指向同一类族注意:可以让基类的对象指针,先后指向同一类族中不同派生类的对象,这样就可以用相同的调用方中不同派生类的对象,这样就可以用相同的调用方式去调用不同派生类对象中的同名虚函数,从而实式去调用不同派生类对象中的同名虚函数,从而实现动态多态性。现动态多态性。运行:运行:调运调运A类构造了!类构造了!调运调运A类构造了!类构造了!调运调运B类构造了!类构造了!调运调运A类构造了!类构造了!调运调运B类构造了!类构造了!调运调运C类构造了!类构
31、造了!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()虚函数虚函数对象对象a116静态关联、动态关联静态关联、动态关联l关联(绑定、关联(绑定、联编联编):):指程序自身彼此关联的过程,即程序中不同部分互相绑定的指程序自身彼此关联的过程,即程序中不同部分互相绑定的过程,以确定程序中的各个函数调用式与所执行的相应函数代码的关系,简单的说,过程,以确定程
32、序中的各个函数调用式与所执行的相应函数代码的关系,简单的说,关联就是把某个标识符与某个存储地址联系起来。关联就是把某个标识符与某个存储地址联系起来。l静态关联(静态绑定、静态联编):静态关联(静态绑定、静态联编):指指关联(绑定、联编)工作出现在编译阶段。关联(绑定、联编)工作出现在编译阶段。例如,程序中使用对象名来调用成员函数时,在编译阶段系统就能根据对象的类型例如,程序中使用对象名来调用成员函数时,在编译阶段系统就能根据对象的类型确定所要调用的是哪一个类的成员函数并进行关联。再如,使用对象名来调用某个确定所要调用的是哪一个类的成员函数并进行关联。再如,使用对象名来调用某个类族中的虚函数时,
33、在编译阶段系统也能确定要调用的虚函数属于哪一个类并进行类族中的虚函数时,在编译阶段系统也能确定要调用的虚函数属于哪一个类并进行关联。关联。此外,函数重载和运算符重载也是在编译阶段进行关联的。此外,函数重载和运算符重载也是在编译阶段进行关联的。l动态关联(动态关联(动态绑定动态绑定、动态联编):、动态联编):指指绑定(联编、关联)工作出现绑定(联编、关联)工作出现在运行在运行阶段阶段。例如,程序中使用基类的对象指针(或例如,程序中使用基类的对象指针(或基类的对象引用)来基类的对象引用)来调用某个类族中的虚函调用某个类族中的虚函数时,数时,只有在程序运行过程中,才能根据其具体指向(或具体引用)该只
34、有在程序运行过程中,才能根据其具体指向(或具体引用)该类族中类族中的哪的哪一个类的对象,确定出调用的是哪一个类的虚函数。一个类的对象,确定出调用的是哪一个类的虚函数。17静态关联、动态关联静态关联、动态关联l虚函数的动态关联:虚函数的动态关联:只有通过基类的对象指针(或基类的对象引用)来调用同一类只有通过基类的对象指针(或基类的对象引用)来调用同一类族中派生类的虚函数时,才属于动态关联!原因是:只有在程序运行过程中,系统族中派生类的虚函数时,才属于动态关联!原因是:只有在程序运行过程中,系统才能根据该指针(或该引用)具体指向(或具体引用)的是同一类族中的才能根据该指针(或该引用)具体指向(或具
35、体引用)的是同一类族中的哪一个派哪一个派生类的对象,确定出调用的是哪一个派生类的虚函数。生类的对象,确定出调用的是哪一个派生类的虚函数。l注意:注意:虚函数与一般成员函数比较,调用时执行速度要慢一些。使用虚函数,系统要有一定虚函数与一般成员函数比较,调用时执行速度要慢一些。使用虚函数,系统要有一定的空间和时间开销。的空间和时间开销。当一个类中有虚函数时,编译系统会为该类构造一个虚函数表,它是一个指针数组,当一个类中有虚函数时,编译系统会为该类构造一个虚函数表,它是一个指针数组,存放该类的每个虚函数的入口地址。存放该类的每个虚函数的入口地址。由于虚函数的调用机制是间接实现的,且动态关联是在程序运
36、行阶段,相对会降低程由于虚函数的调用机制是间接实现的,且动态关联是在程序运行阶段,相对会降低程序的运行效率。序的运行效率。但通用性也是程序追求的主要目标之一。但通用性也是程序追求的主要目标之一。l虚函数必须是非静态的成员函数:虚函数必须是非静态的成员函数:因为静态成员函数不受限于某个对象。因为静态成员函数不受限于某个对象。18【例例】(分别通过对象名、基类的对象指针去调用派生类的虚函数。(分别通过对象名、基类的对象指针去调用派生类的虚函数。)#includeclass A int x;public:A()x=10;virtual void show()cout “x=”x endl;class
37、 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;show()虚函数虚函数对象对象bint x;show()虚函数虚函数对象
38、对象aint x;show()虚函数虚函数int z;show()虚函数虚函数对象对象c19【例例】(在成员函数中调用虚函数:在成员函数中调用虚函数:关键是分析在调用该成员函数时,其关键是分析在调用该成员函数时,其 this 指针是指针是基类的对象指针还是派生类的对象指针!基类的对象指针还是派生类的对象指针!)#includeclass A public:virtual void f1()cout f2();void f2()cout f3();virtual void f3()cout f4();virtual void f4()cout f5();void f5()cout “A:f5n”
39、;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:f520【例例】(在成员函数中调用虚函数:在成员函数中调用虚函数:关键是分析在调用该成员函数时,其关键是分析在调用该成员函数时,其 this 指针是指针是基类的对象指针
40、还是派生类的对象指针!基类的对象指针还是派生类的对象指针!)#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();A *pa=&b;pa-f1();对象对象bA类继承:类继承:f1
41、()f2()f3()虚函数虚函数f4()虚函数虚函数f5()B类新增:类新增:f1()f3()虚函数虚函数f4()虚函数虚函数f5()运行:运行:B:f1A:f2B:f3B:f4B:f5A:f1A:f2B:f3B:f4B:f521【例例】(切记!(切记!在构造函数中调用虚函数在构造函数中调用虚函数与在成员函数中调用虚函数有区别!与在成员函数中调用虚函数有区别!)#includeclass A public:A()f();virtual void f()cout “A:fn”;class B:public A public:B()f();void g1()cout “B:fn”;void g2(
42、)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:f22【例例】(使用动态多态性,编写程序求球体、圆柱体的体积和表面积。)(使用动态多态性,编写程序求球体、圆柱体的体积和表面积。)分析:分析:若球体半径为若球体半径为r,则:其体积则:其体积 V=4r3/3;表面积表面积 S=4r2 。若圆柱底圆半径为若圆柱底圆半径为r,高为高为h,则:其体积则:其体积 V=r2 h;表面积表面积 S=2r(r+h)。由于球体、圆柱体均可从圆继承而来,所以先定义基类由于球体、圆柱体均可从圆继承而来,所以先定义基类 Circle,在基类中定义两个在基类中定义两个虚函数虚函数求体
44、积虚函数求体积虚函数 volume()、求表面积虚函数求表面积虚函数 area()。球体类球体类 Sphere 和圆和圆柱体类柱体类 Cylinder 可由可由 圆类圆类 Circle 派生而来,在两个派生类中分别对两个虚函数进派生而来,在两个派生类中分别对两个虚函数进行重新定义,用于计算球体、圆柱体的体积和表面积。行重新定义,用于计算球体、圆柱体的体积和表面积。在主函数在主函数 main()中,定义一个基类中,定义一个基类 Circle 的对象指针的对象指针 p,先后用来指向球体类先后用来指向球体类Sphere、圆柱体类圆柱体类 Cylinder 这同一类族中不同派生类的对象,当基类指针这同
45、一类族中不同派生类的对象,当基类指针 p 指向指向包含虚函数的包含虚函数的 Sphere 类、类、Cylinder 类的对象时,系统会根据该指针所指向对象的类的对象时,系统会根据该指针所指向对象的类型,来决定调用的是哪一个类的虚函数。类型,来决定调用的是哪一个类的虚函数。23#includeconst double PI=3.14159;class Circle protected:float radius;public:Circle(float a=0):radius(a)virtual double volume()return 0;/虚函数虚函数 virtual double area(
46、)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);/虚函数虚函数;24class Cylinder:public Circle float h;public:Cylinder(float a=0,float b=0):Circle(a)h=b;double
47、 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 “圆柱表面积圆柱表面积=”area()endl;运行:运行:球体体积球体体积=4188.79球体表
48、面积球体表面积=1256.64 圆柱体积圆柱体积=37699.1圆柱表面积圆柱表面积=6283.18 25虚析构函数虚析构函数l虚析构函数的声明:虚析构函数的声明:若将基类的析构函数声明为虚函数,则由该基类派生的所有派若将基类的析构函数声明为虚函数,则由该基类派生的所有派生类的析构函数都自动成为虚函数,即使这些派生类的析构函数名与基类的析构函生类的析构函数都自动成为虚函数,即使这些派生类的析构函数名与基类的析构函数名并不相同!数名并不相同!l必须声明虚析构函数的情况:必须声明虚析构函数的情况:在由一个基类派生出的类体系中,若需要动态创建派在由一个基类派生出的类体系中,若需要动态创建派生类对象,
49、就必须将析构函数声明为虚函数,以实现撤消对象时的多态性!这样,生类对象,就必须将析构函数声明为虚函数,以实现撤消对象时的多态性!这样,若程序中用若程序中用 delete 运算符去撤消动态分配的派生类对象,而运算符去撤消动态分配的派生类对象,而 delete 运算符后面跟着运算符后面跟着的是指向派生类对象的基类指针,则系统调用的不是基类的析构函数,而是派生类的是指向派生类对象的基类指针,则系统调用的不是基类的析构函数,而是派生类的析构函数!的析构函数!l习惯的做法:习惯的做法:一般在基类中都将析构函数声明为虚析构函数,即使基类并不需要自一般在基类中都将析构函数声明为虚析构函数,即使基类并不需要自
50、定义析构函数时,也显式定义一个函数体为空的虚析构函数,以保证在撤消动态分定义析构函数时,也显式定义一个函数体为空的虚析构函数,以保证在撤消动态分配的派生类对象时能得到正确的处理。配的派生类对象时能得到正确的处理。26【例例】(必须声明虚析构函数的情况。)(必须声明虚析构函数的情况。)#includeclass A public:A()cout “A类构造了类构造了!n”;A()cout “A类析构了类析构了!n”;virtual void f()cout “A:fn”;void g()f();class B:public A public:B()f();cout “B类构造了类构造了!n”;B