第7章 多态性及虚函数(成教)_第1页
第7章 多态性及虚函数(成教)_第2页
第7章 多态性及虚函数(成教)_第3页
第7章 多态性及虚函数(成教)_第4页
第7章 多态性及虚函数(成教)_第5页
已阅读5页,还剩49页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、第第7章章 多态性与虚函数多态性与虚函数 多态性是面向对象程序设计的重要特征多态性是面向对象程序设计的重要特征之一,多态性机制:之一,多态性机制:n 增加了面向对象软件系统的灵活性增加了面向对象软件系统的灵活性n 减少了冗余信息减少了冗余信息n 提高了软件的可重用性和可扩充性提高了软件的可重用性和可扩充性7.1 多态性概述多态性概述 指不同对象收到相同的消息时,产指不同对象收到相同的消息时,产生不同的动作。生不同的动作。 多态性是指用一个名字定义不同的多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操函数,这些函数执行不同但又类似的操作,从而可以作,从而可以使用相同的调用方式来调

2、使用相同的调用方式来调用这些具有不同功能的同名函数。用这些具有不同功能的同名函数。 C+中的多态性可以分为四类中的多态性可以分为四类:n参数多态:参数多态:由由类模板类模板实例化各个类都有的具有相同的实例化各个类都有的具有相同的操作,而操作对象的类型却各不相同操作,而操作对象的类型却各不相同n包含多态:包含多态:主要通过主要通过虚函数虚函数来实现。强调不同类中的来实现。强调不同类中的同名成员函数的多态行为同名成员函数的多态行为n强制多态:强制多态:即,即,将一个变元的类型加以变化将一个变元的类型加以变化,比如加,比如加法运算时候浮点数与整数的强制转换(编译程序通过法运算时候浮点数与整数的强制转

3、换(编译程序通过语义操作,把操作对象的类型强行加以变换,以符合语义操作,把操作对象的类型强行加以变换,以符合函数或操作符的要求)函数或操作符的要求)n重载多态:重载多态:普通函数、类的成员函数的重载、运算符普通函数、类的成员函数的重载、运算符重载重载属于重载多态。属于重载多态。 前面两种统称为通用多态,而后面两种统称为专前面两种统称为通用多态,而后面两种统称为专用多态。用多态。 是通过是通过静态联编静态联编来实现的。静态联编就是来实现的。静态联编就是在编译阶段完成的联编。编译时多态性主要是在编译阶段完成的联编。编译时多态性主要是通过通过和运算符重载和运算符重载实现的。实现的。 是用是用动态联编

4、动态联编实现的。动态联编是运行阶实现的。动态联编是运行阶段完成的联编。运行时多态性主要是通过段完成的联编。运行时多态性主要是通过虚函虚函数数来实现的。来实现的。 7.2 运算符重载运算符重载n重载,即重新赋予新的含义。重载,即重新赋予新的含义。n函数重载就是对一个已有的函数赋予新函数重载就是对一个已有的函数赋予新的含义,使之实现新功能。的含义,使之实现新功能。什么是运算符重载?什么是运算符重载?n运算符也可以重载,对运算符也可以重载,对C+已提供的运已提供的运算符进行重载,赋予它们新的含义,使算符进行重载,赋予它们新的含义,使之一名多用。譬如,用之一名多用。譬如,用“+”号如何进行号如何进行两

5、个复数的相加两个复数的相加? C+中不能直接用运中不能直接用运算符算符“+”对复数进行相加运算。对复数进行相加运算。n可以通过定义一个专门的函数来实现复数相加。可以通过定义一个专门的函数来实现复数相加。 n例例7.1 通过通过函数函数来实现复数相加。来实现复数相加。#include using namespace std;class Complex /定义定义Complex类类public:Complex( )real=0;imag=0; /定义构造函数定义构造函数Complex(double r,double i) /构造函数重载构造函数重载real=r; imag=i; Complex c

6、omplex_add(Complex &c2); /声明复数相加函数声明复数相加函数void display( ); /声明输出函数声明输出函数 private:double real; /实部实部double imag; /虚部虚部;Complex Complex complex_add(Complex &c2) Complex c; c.real=real+c2.real; c.imag=imag+c2.imag; return c; void Complex display( ) /定义输出函数定义输出函数 cout(real,imagi)endl; int main(

7、) Complex c1(3,4),c2(5,-10),c3; /定义定义3个复数对象个复数对象 c3=plex_add(c2); /调用复数相加函数调用复数相加函数 coutc1=; c1.display( ); /输出输出c1的值的值 coutc2=; c2.display( ); /输出输出c2的值的值 coutc1+c2=; c3.display( ); /输出输出c3的值的值 return 0;运行结果如下:运行结果如下: c1=(3,4i)c2=(5,-10i)c1+c2=(8,-6i)n结果是正确的,但调用方式不直观、太烦结果是正确的,但调用方式不直观、太烦琐,不方便。能否也和整

8、数的加法运算一样,琐,不方便。能否也和整数的加法运算一样,直接用加号直接用加号“+”来实现复数运算呢?来实现复数运算呢?如:如:c3=c1+c2;n如果能做到,就为对象的运算提供了很大如果能做到,就为对象的运算提供了很大的方便。的方便。n这就需要对运算符这就需要对运算符“+”进行重载。进行重载。7.2.1 运算符重载的方法运算符重载的方法定义一个重定义一个重载运算符的函数,在需要执行被重载的运算符时,载运算符的函数,在需要执行被重载的运算符时,系统就系统就自动自动调用该函数,以实现相应的运算。调用该函数,以实现相应的运算。函数类型函数类型 operator 运算符名称运算符名称 (形参表列形参

9、表列) 对运算符的重载处理对运算符的重载处理 n例如,想将例如,想将“+”用于用于Complex类类(复数复数)的加法运的加法运算,函数的原型:算,函数的原型: Complex operator+ (Complex& c1,Complex& c2); 函数函数operator+重载了运算符重载了运算符+。n 用函数的方法理解运算符用函数的方法理解运算符:在运算符重载后,执行表达式就是调用在运算符重载后,执行表达式就是调用函数的过程。函数的过程。可以把两个整数相加也可以把两个整数相加也调用函数:调用函数: int operator + (int a,int b) return (

10、a+b); 如果有表达式如果有表达式5+8,就调用此函数,将,就调用此函数,将5和和8作作为调用函数时的实参,函数的返回值为为调用函数时的实参,函数的返回值为13。(1) C+不允许用户自己定义新的运算符,不允许用户自己定义新的运算符,只能对已有的只能对已有的C+运算符进行重载。运算符进行重载。(2) C+中绝大部分的运算符允许重载。不中绝大部分的运算符允许重载。不能重载的运算符只有能重载的运算符只有5个:个: . (成员访问运算符成员访问运算符) * (成员指针访问运算符成员指针访问运算符) (域运算符域运算符) sizeof (长度运算符长度运算符) ? : (条件运算符条件运算符)前两个

11、运算符不能重载是为了保前两个运算符不能重载是为了保证访问成员的功能不能被改变,证访问成员的功能不能被改变,域运算符和域运算符和sizeof运算符的运算运算符的运算对象是类型而不是变量或一般表对象是类型而不是变量或一般表达式,不具重载的特征。达式,不具重载的特征。(3) 重载不能改变运算符运算对象重载不能改变运算符运算对象(即操作数即操作数)的个的个数。数。(4) 重载不能改变运算符的优先级别。重载不能改变运算符的优先级别。(5) 重载不能改变运算符的结合性。重载不能改变运算符的结合性。(6) 重载运算符的函数不能有默认的参数,否则就重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数

12、,与前面第改变了运算符参数的个数,与前面第(3)点矛盾。点矛盾。(7) 重载的运算符必须和用户定义的自定义类型的重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有一个是类对象对象一起使用,其参数至少应有一个是类对象(或或类对象的引用类对象的引用)。也就是说,参数不能全部是也就是说,参数不能全部是C+的标准类型,以防止用户修改用于标准类型数据的标准类型,以防止用户修改用于标准类型数据的运算符的性质。的运算符的性质。(8) 用于类对象的运算符一般必须重载,但有用于类对象的运算符一般必须重载,但有两个例外,运算符两个例外,运算符“=”和和“&”不必用户重载。不必用户重载。

13、赋值运算符赋值运算符(=)可以用于每一个类对象,可以利用可以用于每一个类对象,可以利用它在同类对象之间相互赋值。它在同类对象之间相互赋值。 地址运算符地址运算符&也不必重载,它能返回类对象在内也不必重载,它能返回类对象在内存中的起始地址。存中的起始地址。(9) 应当使重载运算符的功能类似于该运算符应当使重载运算符的功能类似于该运算符作用于标准类型数据时所实现的功能。作用于标准类型数据时所实现的功能。(10) 运算符重载函数可以是类的运算符重载函数可以是类的成员函数成员函数(如如例例7.2),也可以是类的,也可以是类的友元函数友元函数,还可以是既,还可以是既非类的成员函数也不是友元函数的

14、普通函数。非类的成员函数也不是友元函数的普通函数。(1)将运算符重载函数作为)将运算符重载函数作为Complex类中类中的成员函数的成员函数n 例例7.2 改写例改写例7.1,重载运算符,重载运算符“+”,使之,使之能用于两个复数相加。能用于两个复数相加。class Complexpublic:Complex( )real=0;imag=0;Complex(double r,double i)real=r;imag=i;Complex operator+(Complex &c2); /声明重载运算符的函数声明重载运算符的函数void display( ); private:double

15、 real;double imag;Complex Complex operator+(Complex &c2) /定义重载运算符的定义重载运算符的函数函数 Complex c; c.real=real+c2.real; c.imag=imag+c2.imag; return c; void Complex display( ) cout(real,imagi)endl;int main( ) Complex c1(3,4),c2(5,-10),c3; c3=c1+c2; /运算符运算符+用于复数运算用于复数运算 coutc1=; c1.display( ); coutc2=; c2.

16、display( ); coutc1+c2=; c3.display( ); return 0;运行结果与例运行结果与例7.1相同:相同: c1=(3,4i)c2=(5,-10i)c1+c2=(8,-6i)n在将运算符在将运算符+重载为类的成员函数后,重载为类的成员函数后,C+编译系统将程序中的表达式编译系统将程序中的表达式c1+c2解释为:解释为:c1.operator+(c2) /其中其中c1和和c2是是Complex类的对象类的对象即以即以c2为实参调用为实参调用c1的运算符重载函数的运算符重载函数operator+(Complex &c2),进行求值,得到两个,进行求值,得到两

17、个复数之和。复数之和。(2)将运算符重载函数作为)将运算符重载函数作为Complex类的类的友元函数友元函数n例例7.3 将运算符将运算符“+”重载为适用于复数加法,重载函数不重载为适用于复数加法,重载函数不作为成员函数,而放在类外,作为作为成员函数,而放在类外,作为Complex类的友元函数。类的友元函数。class Complexpublic:Complex( ) real=0;imag=0;Complex(double r,double i) real=r;imag=i;friend Complex operator + (Complex &c1,Complex &c2)

18、;/重载函数作为友元函数重载函数作为友元函数void display( ); private:double real;double imag;Complex operator + (Complex &c1,Complex &c2) /定义作为友元函数的重载函数定义作为友元函数的重载函数 return Complex(c1.real+c2.real, c1.imag+c2.imag);void Complex display( ) cout(real,imagi)endl;int main( ) Complex c1(3,4),c2(5,-10),c3; c3=c1+c2; co

19、utc1=; c1.display( ); coutc2=; c2.display( ); coutc1+c2 =; c3.display( );n将运算符将运算符“+”重载为非成员函数后,重载为非成员函数后,C+编译系统将程序中的表达式编译系统将程序中的表达式c1+c2解释为:解释为: operator+(c1,c2)n即执行即执行c1+c2相当于调用以下函数:相当于调用以下函数: Complex operator + (Complex &c1,Complex &c2) return Complex(c1.real+c2.real, c1.imag+c2.imag);n单目运

20、算符只有一个操作数,如单目运算符只有一个操作数,如!a,-b,&c,*p,还有最常用的,还有最常用的+i和和-i等。等。n单目运算符只有一个操作数,因此运算符单目运算符只有一个操作数,因此运算符重载函数只有一个参数,如果运算符重载函重载函数只有一个参数,如果运算符重载函数作为成员函数,则还可以省略此参数。数作为成员函数,则还可以省略此参数。n 以自增运算符以自增运算符“+”为例,介绍单目运算为例,介绍单目运算符的重载。符的重载。n例例7.5 有一个有一个Time类,包含数据成员类,包含数据成员minute(分分)和和sec(秒秒),模拟秒表,每次走一秒,满,模拟秒表,每次走一秒,满60

21、秒进一秒进一分钟,此时秒又从分钟,此时秒又从0开始算。要求输出分和秒的值。开始算。要求输出分和秒的值。#include using namespace std;class Timepublic:Time( )minute=0;sec=0; /默认构造函数默认构造函数Time(int m,int s):minute(m),sec(s) /构造函数重载构造函数重载Time operator+( ); /声明运算符重载函数声明运算符重载函数void display( )coutminute:sec=60) sec-=60; /满满60秒进秒进1分钟分钟 +minute; return *this;

22、/返回当前对象值返回当前对象值 int main( ) Time time1(34,0); for (int i=0;i61;i+) +time1; time1.display( ); return 0;运行情况如下:运行情况如下: 34:134:234:5935:035:1 (共输出共输出61行行)n例例7.6 在例在例7.5程序的基础上增加对后置自程序的基础上增加对后置自增运算符的重载。增运算符的重载。修改后的程序如下:修改后的程序如下:#include using namespace std;class Timepublic: Time( )minute=0;sec=0; Time(in

23、t m,int s):minute(m),sec(s) Time operator+( ); /声明前置自增运算符声明前置自增运算符“+”重载函数重载函数Time operator+(int); /声明后置自增运算符声明后置自增运算符“+”重载函数重载函数void display( )coutminute:sec=60) sec-=60; +minute; return *this; /返回自加后的当前对象返回自加后的当前对象Time Time operator+(int) /定义后置自增运算符定义后置自增运算符“+”重载函数重载函数 Time temp(*this); sec+; if (s

24、ec=60) sec-=60; +minute; return temp; /返回的是自加前的对象返回的是自加前的对象 Time是类名,是类名,temp是是Time类的一个实例,使用了类的一个实例,使用了Time类的拷贝构造函数来类的拷贝构造函数来创建了这个实例,拷贝源是创建了这个实例,拷贝源是当前对象。当前对象。int main( ) Time time1(34,59), time2;cout time1 : ;time1.display( );+time1;cout+time1: ;time1.display( );time2=time1+; /将自加前的对象的值赋给将自加前的对象的值赋给

25、time2couttime1+: ;time1.display( );cout=60) sec-=60; +minute; return *this; p既然是后置自加,返回时就要返回自加前既然是后置自加,返回时就要返回自加前的对象,等操作结束后才自加,所以,要的对象,等操作结束后才自加,所以,要通过通过Time temp(*this)在操作之前拷贝,等在操作之前拷贝,等到操作结束后,返回到操作结束后,返回temp,而不能是,而不能是*this,因为此时的因为此时的*this已经不是已经不是temp了,这和前了,这和前置自加是有区别的。置自加是有区别的。 n重载后置自增运算符时,多了一个重载后

26、置自增运算符时,多了一个int型的参型的参数,增加这个参数只是为了与前置自增运算符数,增加这个参数只是为了与前置自增运算符重载函数有所区别,此外没有任何作用。编译重载函数有所区别,此外没有任何作用。编译系统在遇到重载后置自增运算符时,会自动调系统在遇到重载后置自增运算符时,会自动调用此函数。用此函数。n通过本章前面几节的讨论,可以看到:通过本章前面几节的讨论,可以看到:在在 C+中,运算符重载是很重要的、很有实中,运算符重载是很重要的、很有实用意义的。它使类的设计更加丰富多彩,用意义的。它使类的设计更加丰富多彩,扩大了类的功能和使用范围,使程序易于扩大了类的功能和使用范围,使程序易于理解,易于

27、对对象进行操作,它体现了为理解,易于对对象进行操作,它体现了为用 户 着 想 、 方 便 用 户 使 用 的 思 想 。用 户 着 想 、 方 便 用 户 使 用 的 思 想 。n有了运算符重载,在声明了类之后,人们有了运算符重载,在声明了类之后,人们就可以像使用标准类型一样来使用自己声就可以像使用标准类型一样来使用自己声明的类。类的声明往往是一劳永逸的,有明的类。类的声明往往是一劳永逸的,有了好的类,用户在程序中就不必定义许多了好的类,用户在程序中就不必定义许多成员函数去完成某些运算和输入输出的功成员函数去完成某些运算和输入输出的功能,使主函数更加简单易读。能,使主函数更加简单易读。n好的运

28、算符重载能体现面向对象程序设计好的运算符重载能体现面向对象程序设计思想。思想。7.3 虚函数虚函数 n虚函数提供了一种更为灵活的多态性机虚函数提供了一种更为灵活的多态性机制。制。n虚函数允许函数调用与函数体之间的联虚函数允许函数调用与函数体之间的联系系在运行时在运行时才建立,也就是在才建立,也就是在运行时才运行时才决定如何动作,决定如何动作,即所谓的即所谓的动态联编动态联编。 面向对象技术中的面向对象技术中的 “后期绑后期绑定定”技术。技术。可以在基类中定义一个可以在基类中定义一个虚函数虚函数,然后在派生类中覆盖它,然后在派生类中覆盖它,当调用此方当调用此方法时,系统会根据对象的类型而决定法时

29、,系统会根据对象的类型而决定调用哪一个对象的方法(即:不同对调用哪一个对象的方法(即:不同对象收到相同消息时,产生不同的动象收到相同消息时,产生不同的动作作 )。)。如:如:class fruit virtual void eat()=0; class apple:public fruit void eat() printf(apple eat); class orange:public fruit void eat() printf(orange eat); void fruite_eat(fruit * f) f-eat();例例7.9 虚函数的引例虚函数的引例1。#includeclas

30、s Apublic: void show() coutA; ;class B:public A public: void show() coutshow(); pc=&b; pc-show(); return 0; :AA:ABl问题:问题:指向基类对象的指指向基类对象的指针可以指向它的公有派生针可以指向它的公有派生类对象。类对象。但这个对象指针但这个对象指针调用同名但不同级的成员调用同名但不同级的成员函数时会有问题。函数时会有问题。#includeclass base int a,b;public: base(int x,int y) a=x; b=y; void show( ) c

31、outbase-n; couta bendl;class derived:public baseprivate:int c;public:derived(int x,int y,int z):base(x,y) c=z; void show()coutderived-n;coutc= cshow ();pc=&mc;pc-show ();运行结果运行结果:base-60 60base-10 20?没有指向派生类没有指向派生类例例7.10 虚函数的引虚函数的引例例2错误的原因错误的原因:C+的的静态联静态联编机制编机制。静态联编首先将指。静态联编首先将指向基类对象的向基类对象的pc与基类

32、的成与基类的成员函数员函数show()连接在一起连接在一起,以后以后不管不管pc再指向哪个对象,再指向哪个对象,pc-show调用的总是基类调用的总是基类的成员函数的成员函数show()。void main()base mb(60,60),*pc;derived mc(10,20,30);pc=&mb;pc-show ();pc=&mc;pc-show ();void main()/pc=&mb;pc-show ();pc=&mc;pc-show ();mc.show();(derived *)pc)-show();更改获得预期更改获得预期结果的方法结果的方法b

33、ase-60 60base-10 20derived-c= 30derived-c= 30 n虚函数同派生类的结合可使虚函数同派生类的结合可使C+支持运支持运行时的多态性,实现了行时的多态性,实现了,而而,即常说的即常说的“同一接口,同一接口,多种方法多种方法”,它帮助程序员处理越来越,它帮助程序员处理越来越复杂的程序。复杂的程序。 例例7.11 虚函数的作用。虚函数的作用。#includeclass Base public: Base(int x,int y) a=x; b=y; virtual void show() /定义虚函数定义虚函数show() coutBase-n; couta

34、bendl; private: int a,b; ;class Derived : public Base public: Derived(int x,int y,int z):Base(x,y)c=z; void show() /重新定义虚函数重新定义虚函数show() cout Derived-n; coutcshow(); /调用基类调用基类Base的的show()版本版本 pc=&mc; pc-show(); /调用派生类调用派生类Derived的的show()版本版本 程序运行结果如下程序运行结果如下:Base-60 60Derived-30 结果正确结果正确关键字关键字vi

35、rtual指示指示c+编译器,函编译器,函数调用数调用pc-show()在运行时确定调在运行时确定调用的函数用的函数,即对调用进行,即对调用进行动态联编动态联编程序在运行时根据指针程序在运行时根据指针pc所指向的所指向的实际对象调用该对象的成员函数实际对象调用该对象的成员函数n定义虚函数的方法如下定义虚函数的方法如下: :virtual virtual 函数类型函数类型 函数名函数名( (形参表形参表) ) / / 函数体函数体 n对虚函数的定义的几点说明:对虚函数的定义的几点说明:通过定义虚函数使用通过定义虚函数使用C+提供的多态性机制提供的多态性机制时,派生类应从其时,派生类应从其必须首先

36、在基类中定义为虚函数;必须首先在基类中定义为虚函数;C+规定,规定,。因此,派生类中的因此,派生类中的virtual可写可不写,可写可不写,为避免混乱,最好在派生类的虚函数进行重为避免混乱,最好在派生类的虚函数进行重新定义时加上关键字新定义时加上关键字virtual。 一个虚函数无论被公有继承多少次,仍保持一个虚函数无论被公有继承多少次,仍保持其虚函数特性。其虚函数特性。虚函数必须是类的成员函数虚函数必须是类的成员函数,不能是友员函,不能是友员函数,也不能是静态成员函数。数,也不能是静态成员函数。内联函数不能是虚函数内联函数不能是虚函数,因为内联函数是在,因为内联函数是在编译时确定位置。虚函数

37、虽然定义在类内部,编译时确定位置。虚函数虽然定义在类内部,但编译时仍将其视为非内联。但编译时仍将其视为非内联。构造函数不能是虚函数构造函数不能是虚函数,因为虚函数作为运,因为虚函数作为运行过程中多态的基础,主要是针对对象的,行过程中多态的基础,主要是针对对象的,而构造函数是在对象产生之前运行的,因此而构造函数是在对象产生之前运行的,因此虚构造函数无意义。虚构造函数无意义。析构函数可以是虚函数。析构函数可以是虚函数。7.4 纯虚函数和抽象类纯虚函数和抽象类 n纯虚函数纯虚函数(pure virtual function)是一个在是一个在基类中说明的虚函数,它在该基类中说明的虚函数,它在该基类中没基类中没有定义有定义,但要求,但要求在它的派生类中必须定在它的派生类中必须定义自己的版本,或重新说明为纯虚函数。义自己的版本,或重新说明为纯虚函数。n纯虚函数的定义形式如下纯虚函数的定义形式如下: virtual 函数类型函数类型 函数名函数名(参数表参数表)=0; 注意:注意:n纯虚函数没有函数体;纯虚函数没有函数体;n最后面的最后面的“=0”并不表示函数返回值为并不表示函数返回

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论