收藏 分享(赏)

6 多态性与虚函数.ppt

上传人:11xg27ws 文档编号:7353457 上传时间:2019-05-15 格式:PPT 页数:56 大小:512KB
下载 相关 举报
6 多态性与虚函数.ppt_第1页
第1页 / 共56页
6 多态性与虚函数.ppt_第2页
第2页 / 共56页
6 多态性与虚函数.ppt_第3页
第3页 / 共56页
6 多态性与虚函数.ppt_第4页
第4页 / 共56页
6 多态性与虚函数.ppt_第5页
第5页 / 共56页
点击查看更多>>
资源描述

1、第6章 多态性与虚函数6.1 多态性概述6.2 虚函数6.3 纯虚函数和抽象类,6.1 多态性概述,多态性是面向对象程序设计的重要特征之一。 多态性是指发出同样的消息被不同类型的对象接收时导致完全不同的行为。,多态的实现: 1 函数重载 2 运算符重载 3 虚函数 4 类模板,在C+中,多态的实现和联编这一概念有关。所谓联编就是把函数名与函数体的程序代码连接(联系)在一起的过程。静态联编联编动态联编静态联编就是在编译阶段完成的联编。静态联编函数调用速度很快。效率高, 但缺乏灵活性;动态联编是运行阶段完成的联编。动态联编在运行时才能确定调用哪个函数,它降低了程序的运行效率,但增强了程序的灵活性。

2、C+实际上是采用了静态联编和动态联编相结合的联编方法。,编译时的多态性多态性 运行时的多态性编译时的多态是通过静态联编来实现的。运行时的多态是通过动态联编实现的。在C+中:编译时多态性主要是通过函数重载和运算符重载实现的。运行时多态性主要是通过虚函数来实现的。,6.2 虚 函 数 6.2.1 虚函数的引入6.2.2 虚函数的作用和定义6.2.3 虚析构函数6.2.4 虚函数与重载函数的关系6.2.5 多继承与虚函数,#include class base /例6.1-1 虚函数引例 int a,b;public:base(int x,int y) a=x; b=y; void show( )

3、coutshow( );mp= ,运行结果?,6.2.1 虚函数的引入,#include /例6.1-1 虚函数引例 class base int a,b;public:base(int x,int y) a=x; b=y; void show( ) coutshow( );mp= ,程序运行结果不是: 调用基类base的show函数 a=50 b=50 调用派生类dirive的show函数 c=30,程序运行结果如下: 调用基类base的show函数 a=50 b=50 调用基类base的show函数 a=10 b=20,为什么?,在C+中规定: 基类的对象指针可以指向它的公有派生的对象,但

4、是当其指向公有派生类对象时,它只能访问派生类中从基类继承来的成员,而不能访问公有派生类中定义的成员,例如: class A public:void print1(). . . ; class B:public A public:void print2(). . . ; void main() A *p1; /定义基类A的指针变量p1B op2; /定义派生类B的对象op2p1= ,正确, 基类指针变量p1可以访问派生类中从基类继承来的成员函数print1(),错误,基类指针变量p1不能访问派生类中定义的成员函数print2(),#include /例6.1-1 虚函数引例 class base

5、 int a,b;public:base(int x,int y) a=x; b=y; void show( ) coutshow( );mp= ,指向基类的指针变量,当其指向公有派生类对象时,可以直接访问派生类中从基类继承来的成员, 但不能直接访问公有派生类中定义的成员。,不管指向基类对象的指针mp当前指向哪个对象(基类对象或派生类对象),mp-show()调用的都是基类中定义的show()。,例如: void main( ) base mb(50,50), *mp;dirive mc(10,20,30);mp=,问题:能否用同一个语句“mp-show()”既能访问基类又能访问派生类的同名函

6、数呢?,希望分别调用基类base的show()函数和派生类dirive的 show()函数,C+中可以用虚函数来解决这个问题。,6.2.2 虚函数的作用和定义1虚函数的作用虚函数就是在基类中被关键字virtual说明,并在派生类中重新定义的函数。 class baseint a,b;public:. . .virtual void show( ). . . ; class dirive:public baseint c;public:. . .void show( ) . . . ;,在基类中,show()定义为虚函数,在派生类中,重新定义虚函数show(),虚函数定义后,就可以通过基类指针访

7、问基类和派生类中的虚函数。void main( ) base mb(50,50), *mp;dirive mc(10,20,30);mp= ,调用基类base的show()函数,调用派生类dirive的show()函数,#include /例5.2-1 class base int a,b;public:base(int x,int y) a=x; b=y; virtual void show( ) coutshow(); mp=,在基类中,show()定义为虚函数,在派生类中,重新定义虚函数show(),调用基类base的show()函数,调用派生类dirive的show()函数,#incl

8、ude /例6.2-1 class base int a,b;public:base(int x,int y) a=x; b=y; virtual void show( ) coutshow(); mp=,程序运行结果如下: 调用基类base的show函数 a=50 b=50 调用派生类dirive的show函数 c=30,调用基类base的show()函数,调用派生类dirive的show()函数,为什么把基类中的show()函数定义为虚函数时,程序的运行结果就正确了呢?这是因为,关键字virtual指示C+编译器,使用语句“mp-show()”调用虚函数时,采用动态联编的方式,即在运行时确

9、定调用哪个show()函数。我们把使用同一种调用形式“mp-show()”,调用同一类族中不同类的虚函数称为动态的多态性,即运行时的多态性。可见,虚函数与派生类的结合可使C+支持运行时的多态性。,2.虚函数的定义定义虚函数的格式如下:重新定义虚函数时,要求:该函数与基类的虚函数有相同的名称;该函数与基类的虚函数有相同的参数个数及相同的对应参数类型;该函数与基类的虚函数有相同的返回类型或者满足赋值兼容规则的指针、引用型的返回类型。,virtual 函数类型 函数名(形参表) 函数体 ,#include /例6.3 虚函数的定义举例。 using namespace std; class Gran

10、dam /声明基类Grandampublic:virtual void introduce_self() /定义虚函数introduce_self cout“I am grandam.“endl; ; class Mother:public Grandam /声明派生类Motherpublic:void introduce_self() /重新定义虚函数introduce_self cout“I am mother.“endl; ; class Daughter:public Mother /声明派生类Daughterpublic:void introduce_self() /重新定义虚函数i

11、ntroduce_self cout“I am daughter.“endl; ;,int main() Grandam *ptr; /定义指向基类的对象指针ptrGrandam g; /定义基类对象gMother m; /定义派生类对象mDaughter d; /定义派生类对象dptr= ,本程序运行的结果如下: I am grandam. I am mother. I am daughter.,说明:(1) 通过定义虚函数来使用多态性机制时,派生类必须从它的基类公有派生。(2)在基类中,声明虚函数原型(需加上virtual ),而在类外定义虚函数时,则不必再加virtual。例如:clas

12、s B0 public:virtual void print(char* p); ;void B0:print(char* p ) coutp“print()“endl; ,在类外,定义虚函数时,不要加virtual,声明虚函数原型,需加上virtual,(3) 如果在派生类中没有对基类的虚函数重新定义,则公有派生类继承其直接基类的虚函数。一个虚函数无论被公有继承多少次,它仍然保持其虚函数的特性。例如:class B0. . .public:virtual void show(). . .;class B1:public B0. ;,若在公有派生类B1中没有重新定义虚函数show,则函数sho

13、w在派生类中被继承,仍是虚函数。,在基类B0中,定义show为虚函数,(4) 在派生类中重新定义该虚函数时,关键字virtual可以写也可以不写。 但是,为了使程序更加清晰,最好在每一层派生类中定义该函数时都加上关键字virtual。class B0. . .public:virtual void show(). . .;class B1:public B0.virtual void show (). . . ;(5) 虽然使用对象名和点运算符的方式也可以调用虚函数,但是这种调用是在编译时进行的,是静态联编,它没有利用虚函数的特性。只有通过基类指针访问虚函数时才能获得运行时的多态性。,在基类B

14、0中,定义show为虚函数,在派生类中,重新定义虚函数show()时,最好也加上关键字virtual,#include /例 class base int a,b;public:base(int x,int y) a=x; b=y; virtual void show( ) cout“调用基类base的show函数n“;cout“a=“ a“b=“bendl; ; class dirive: public base int c;public: dirive(int x,int y,int z):base(x,y) c=z; void show( ) cout “调用派生类dirive的show

15、函数n“; cout“c=“cendl; ; void main( ) base mb(50,50) ;dirive mc(10,20,30);mb.show(); mc.show();,使用” mb.show();”的方式调用基类base的show函数,使用” mc.show();”的方式调用派生类dirive的show函数,程序运行结果如下: 调用基类base的show函数 a=50 b=50 调用派生类dirive的函数 c=30,这种调用是在编译时进行的,是静态联编,(6) 虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活

16、哪个函数。(7) 内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时仍将其看作是非内联的。(8) 构造函数不能是虚函数,但是析构函数可以是虚函数,而且通常说明为虚函数。,6.2.3 虚析构函数 #include /例6.4-1 虚析构函数的引例1。 using namespace std; class Basepublic:Base() cout“调用基类Base的析构函数n“; ; class Derived:public Basepublic:Derived () cout“调用派生类Derived的析构函数n“; ; int main()

17、Derived obj;return 0; ,程序运行的结果为: 调用派生类Derived的析构函数 调用基类Base的析构函数,#include /例6.5-1 虚析构函数的引例2。 using namespace std; class Bpublic:B() cout“调用基类B的析构函数n“; ; class D:public Bpublic:D() cout“调用派生类D的析构函数n“; ; int main() B *p; /定义指向基类B的指针变量p p= new D; delete p; return 0; ,用运算符new为派生类的无名对象动态地 分配了一个存储空间,并将地址赋

18、给对象指针p,用delete撤销无名对象,释放动态存储空间,程序运行的结果为: 调用基类B的析构函数,本程序只执行了基类B的析构函数,而没有执行派生类D的析构函数。原因是当撤销指针P所指的派生类的无名对象, 调用析构函数时,采用了静态联编方式,只调用了基类B的析构函数。,#include /例6.6-1 虚析构函数的使用。 using namespace std; class Bpublic:virtual B() cout“调用基类B的析构函数n“; ; class D:public Bpublic:D() cout“调用派生类D的析构函数n“; ; int main() B *p; /定义

19、指向基类B的指针变量p p= new D; delete p; return 0; ,用运算符new为派生类的无名对象动态地 分配了一个存储空间,并将地址赋给对象指针p,用delete撤销无名对象,释放动态存储空间,程序运行的结果为: 调用派生类D的析构函数 调用基类B的析构函数,将 中基类的析构函数声明为虚析构函数,使用了虚析构函数, 程序执行了动态联编,实现了运行的多态性。,声明虚析构函数的一般格式为:说明:(1) 虚析构函数没有类型,也没有参数。(2) 如果将基类的析构函数定义为虚函数,由该基类所派生的所有派生类的析构函数也都自动成为虚函数。(3)在C+中,不能声明虚构造函数,但是可以声

20、明虚析构函数。,virtual 类名(); 函数体 ,6.2.4 虚函数与重载函数的关系 (1) 在一个派生类中重新定义虚函数是函数重载的另一种形式,但它不同于一般的函数重载。(2) 当普通的函数重载时,其函数的参数个数或参数类型必须有所不同,函数的返回类型也可以不同。但是,当重载一个虚函数时,要求函数名、返回类型、参数个数、参数的类型和顺序与基类中的虚函数原型完全相同。,(3) 当重载一个虚函数时, 如果仅仅返回类型不同,其余均相同,系统会给出错误信息;例如:class basepublic:virtual void f3( ); ;class derived:public base pub

21、lic:char f3( ); ;,两个函数f3() 仅仅返回类型不同,将给出错误信息。,(4) 若仅仅函数名相同,而参数的个数、类型或顺序不同,系统将它作为普通的函数重载,这时将失去虚函数的特性。例如:class basepublic:virtual void f2( );class derived:public basepublic:void f2(int x);,两个函数f2()函数名相同,但参数的个数和类型不同,作为普通的函数重载,这时将失去虚函数的特性。,#include /例6.7-1 class basepublic:virtual void func1( );virtual v

22、oid func2( ); virtual void func3( ); void func4( ); ; class derived:public base public:void func1( ); /是虚函数,这里可不写virtualvoid func2(int x); /作为普通函数重载,虚特性消失char func3( ); void func4( ); ; /是普通函数重载,不是虚函数 void base:func1( )cout“-base func1-n“; void base:func2( )cout“-base func2-n“; void base:func3( ) co

23、ut“-base func3-n“; / void base:func4( ) cout“-base func4-n“; void derived:func1( )cout“-derived func1-n“; void derived:func2(int x)cout“derived func2n“; void derived:func4( )cout“-derived func4-n“;,错误,因为与基类中的func3只有返回类型不同,#include class basepublic:virtual void func1( ); virtual void func2( ); void f

24、unc4( ); ; class derived:public basepublic:void func1( ); /是虚函数,这里可不写virtualvoid func2(int x); /作为普通函数重载,虚特性消失void func4( ); ; /是普通函数重载,不是虚函数void base:func1( )cout“-base func1-n“;void base:func2( )cout“-base func2-n“;void base:func4( ) cout“-base func4-n“;void derived:func1( )cout“-derived func1-n“;

25、void derived:func2(int x)cout“derived func2n“;void derived:func4( )cout“-derived func4-n“;,将错误语句去掉后,运行结果?,void main( ) base d1,*bp;derived d2;bp= ,程序执行结果如下: -derived func1- -base func2 -base func4,6.2.5 多继承与虚函数多重继承可以视为多个单继承的组合。因此,多重继承情况下的虚函数的调用与单继承情况下的虚函数的调用有相似之处。,#include /例6.8-1 class A1public: vi

26、rtual void f( ) coutf( ); /调用基类A1的f() ptr2= ,此处调用的f()为非虚函数,因此调用基类B的f(),此处调用的f()为虚函数,因此调用派生类B的f(),虚函数,非虚函数,普通的成员函数,对虚函数进行重定义,运行结果: -A1- -A2- -B- -A2-,6.3 纯虚函数和抽象类6.3.1 纯虚函数的引例1.纯虚函数的引例例6.9-1 应用C+的虚函数,计算三角形、矩形和圆的面积。,圆类 Circle 虚函数area(),三角形类Triangle 虚函数area(),矩形类Square 虚函数area(),基类Figure虚函数 area( ),cla

27、ss Figure protected:double x,y;public:Figure(double a,double b) x=a;y=b; virtual void area( ) cout“在基类中定义的虚函数n“;,定义一个公共基类Figure,定义虚函数area(),为派生类提供一个公共接口,以便派生类根据需要重新定义虚函数。,class Triangle:public Figure public:Triangle(double a,double ) :Figure(a,b) void area( ) cout“三角形的高是“x“,底是 “y;cout“,面积是“0.5*x*yen

28、dl; ;,虚函数area()重新定义,用作求三角形的面积,定义三角形派生Triangle,class Square:public Figure public:Square(double a,doubleb):Figure(a,b) void area( ) cout“矩形的长是“x“,宽是 “y;cout“,面积是“x*yendl; ;,虚函数area()重新定义,用作求矩形的面积,定义矩形派生类Square,class Circle:public Figure public:Circle(double a):Figure(a,a) void area( ) cout“圆的半径是“x;cou

29、t“,面积是“3.1416*x*xendl; ;,虚函数area()重新定义,用作求圆的面积,定义圆派生类Circle,int main( ) Figure *p; /定义基类指针pTriangle t(10.0,6.0); /定义三角形类对象tSquare s(10.0,6.0); /定义矩形类对象sCircle c(10.0); /定义圆类对象cp= ,计算三角形的面积,计算矩形的面积,计算圆的面积,2.纯虚函数 class Figure protected:double x,y;public:Figure(double a,double b) x=a; y=b; virtual void

30、 area( ) cout“在基类中定义的虚函数n“; ;,基类本身并不需要这个虚函数,而是为派生类提供一个公共接口,以便派生类根据需要重新定义虚函数。,class Figure protected:double x,y;public:Figure(double a,double b) x=a; y=b;virtual void area( )=0; ;,将函数area()简化表示成这种形式,纯虚函数,纯虚函数是在声明虚函数时被“初始化”为0的虚函数。,纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行重新定义。 纯虚函数的特点: 纯虚函数没有函数体; 最后面的“

31、=0”并不表示函数的返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”; 纯虚函数不具备函数的功能,不能被调用。,纯虚函数的一般形式如下: virtual 函数类型 函数名(参数表)=0;,例6.9-2 使用纯虚函数的例子。 #include class Circle protected:int r;public:void setr(int x) r=x; virtual void show( )=0; ; class Area:public Circlepublic:void show( ) cout“这个圆的面积是:“3.14*r*rendl; ; class Perimeter

32、:public Circlepublic:void show( ) cout“这个圆的周长是: “2*3.14*rendl; ;,重定义虚函数show,定义虚函数show,重定义虚函数show,void main( ) Circle *ptr;Area ob1;Perimeter ob2;ob1.setr(10);ob2.setr(10);ptr= ,程序运行结果如下: 这个圆的面积是: 314 这个圆的周长是: 62.8,6.3.2.抽象类什么是抽象类?如果一个类至少有一个纯虚函数,那么就称该类为抽象类。class Figureprotected:double x,y;public:Figu

33、re(double a,double b) x=a; y=b;virtual void area( )=0;,纯虚函数,抽象类,抽象类的作用抽象类的作用是作为一个类族的共同基类,相关的派生类是从这个基类派生出来的。使用抽象类的几点规定(1) 由于抽象类中至少包含有一个没有定义功能的纯虚函数,因此抽象类只能用作其他类的基类,不能建立抽象类对象。 (2) 不允许从具体类派生出抽象类。所谓具体类,就是不包含纯虚函数的普通类;(3) 抽象类不能用作函数的参数类型、函数的返回类型或显式转换的类型;,(4) 可以声明指向抽象类的指针或引用,此指针可以指向它的派生类,进而实现多态性。(5) 在派生类中,如果

34、对基类的纯虚函数没有重新定义,则该函数在派生类中仍是纯虚函数,该派生类仍为抽象类。 例如: class B0 public:virtual void show()=0;. . .; class B1:public B0 . . . ;,在基类定义show()为纯虚函数,基类B0为抽象类。,在派生类中show()函数没有重新定义,则函数show()在派生类中仍是纯虚函数。派生类B1仍是抽象类。,6.4 程序举例例6.11-1 应用抽象类,求圆、圆内接正方形和圆外切正方形的面积和周长。,例6.11-1 应用抽象类,求圆、圆内接正方形和圆外切正方形的面积和周长。,定义一个抽象类Figureclass

35、 Figure protected:double r; public:Figure(double x) r=x; virtual void area()=0; /纯虚函数virtual void perimeter()=0; /纯虚函数 ;,class Circle:public Figure public:Circle(double x):Figure(x) void area() cout“圆的面积是“3.14*r*rendl; void perimeter( ) cout“圆的周长是“; cout2*3.14*rendl; ;,定义一个圆派生类,在类Circle中重定义虚函数perime

36、ter( ),在类Circle中重定义虚函数area(),class In_square:public Figure public: In_square(double x):Figure(x) void area() cout“ 圆内接正方形的面积是“; cout2*r*rendl;void perimeter( ) cout“圆内接正方形的周长是“;cout4*1.414*rendl; ;,定义一个圆内接正方形类,在类In_square中重定义虚函数area(),在类In_square中重定义虚函数perimeter( ),class Ex_square:public Figure publ

37、ic: Ex_square(double x):Figure(x) void area() cout“ 圆外切正方形的面积是“; cout4*r*rendl;void perimeter( ) cout“圆外切正方形的周长是“;cout8*r endl; ;,定义一个圆外切正方形类,在类Ex_square中重定义虚函数area(),在类Ex_square中重定义虚函数perimeter(),int main() Figure *ptr; /定义抽象类Figure的指针ptrCircle ob1(5); /定义类Circle的对象ob1In_square ob2(5); /定义类In_squar

38、e的对象ob2Ex_square ob3(5); /定义类Ex_square的对象ob3ptr= ,抽象类和虚函数使程序的扩充非常容易。例如,在上述程序中,增加一个计算圆外切三角形面积和周长的功能: class Trianglepublic Figurepublic:Triangle(double x)Figure(x) void area() cout圆外切三角形的面积是;cout3*1.732*r*rendl;void perimeter() cout圆外切三角形的周长是;cout6*1.732*rendl; ;,在类Triangle中重定义虚函数perimeter(),在类Triangle中重定义虚函数area(),在main()函数中增加下述几条语句:Triangle ob4(5);ptr=程序运行后,即可打印出相应圆外切三角形面积和周长:圆外切三角形的面积是129.9 圆外切三角形的周长是51.96,

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

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报