C++程序设计课件多态性.ppt_第1页
C++程序设计课件多态性.ppt_第2页
C++程序设计课件多态性.ppt_第3页
C++程序设计课件多态性.ppt_第4页
C++程序设计课件多态性.ppt_第5页
已阅读5页,还剩58页未读 继续免费阅读

下载本文档

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

文档简介

1、-1-,C+ Programming Language,Dr. Zheng Xiaojuan Professor Software College of Northeast Normal University November. 2010,-2-,多态性,-3-,本章内容,1 多态性概述 2 运算符重载 3 虚函数 4 抽象类,-4-,1 多态性概述,1.基本概念:同一名称,不同的功能实现方式。多态性是面向对象的精髓,也是难点。在C+中,多态性是通过虚函数来实现的。 2.作用:设计出可扩充性好的程序(覆盖、重载基类的成员);可以实现从抽象到具体,从一般到特殊(在泛泛的问题解决方法基础上解决某一

2、特殊的问题)。 3.赋值兼容原则:一个派生类的对象在使用上可以被当作基类对象;反之则禁止,具体表现为: 派生类对象可以被赋值给基类对象; Base b; Derive d; b=d;,-5-,派生类的对象可以初始化基类对象的引用; Derive d; Base b=d; 派生类对象的地址可以被赋值给基类的指针: Derive d; Base *ptr= 主要原因:派生类是基类的超集,并包含有基类的成员(继承来的);当将派生类的对象赋值给基类的对象时,系统将继承来的成员赋值给基类的对象;而当将基类的对象赋值给派生类的对象将导致派生类的对象有未被复制成员(成员不够)。,-6-,指向派生类及基类对象

3、的指针: 在public继承方式时,指向基类对象的指针也可以指向派生类对象,反之则禁止。具体表现为: Base *Pb,ObjB; Derived *pd,ObjD; Pb=/将基类对象的指针复制给派生类对象的指针时,必须强制类型转换。 用途:基类对象的指针是实现动态多态性的一个手段,基类对象的指针可以指向基类对象和派生类对象,派生类对象只能指向派生类对象,-7-,多态性为用户提供单一接口示意图,-8-,4.面向对象的多态性种类: 静态多态性:在编译过程中确定同名操作的具体操作对象。 动态多态性:在程序运行过程中动态地确定操作所针对的 具体对象。这种确定操作具体对象的过程就是 联编(bindi

4、ng),也称为绑定。 联编:是指计算机程序自身彼此关联的过程。也就是把一个标识符名和一个存储地址联系在一起的过程。用面向对象的术语讲,就是把一条消息和一个对象的方法相结合的过程。,-9-,联编方法: 静态联编:(静态束定,早期联编),联编工作在编译连接阶 段完成。在编译、连接过程中,系统根据类型匹配 等特征确定程序中操作调用与执行该操作的代码的 关系,即确定某一个同名标识到底是要调用哪一段 程序代码。 如:函数重载、运算符重载。 动态联编: (动态束定,晚期联编)联编工作在程序运行阶段 完成。在编译、连接过程中无法解决的联编问题, 要等到程序开始运行之后再来确定。 如:虚函数,-10-,2 运

5、算符重载,class point private: int x,y; public: point(int xx=0,int yy=0)x=xx;y=yy; int get_x(); int get_y(); /. ; point p1(1,1),p2(3,3) “p1+p2”-需要编写程序来说明“+”在作用于point类对象时,该实现什么样的功能,这就是运算符重载。,-11-,2.1运算符重载概述 1.需要理解的一个思想: (1)运算符是一些预定义的函数名字。 (2)表达式与其等价的函数调用表示:,This指针,有,无,实现过程中首先将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运

6、算函数的参数。,-12-,2.运算符重载的几个问题: (1)定义:运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时,导致不同类型的行为。C+允许同一运算符具有多种不同运算功能的机制。 (2)为什么需要运算符重载:C+中预定义了许多运算符,能够满足用户的一般应用要求,但是其运算的对象应是基本数据类型,而不适用于用户自定义数据类型(如类),这可以使用运算符重载。 (3)含义:同一运算符具有多种不同运算功能,如将“+(加)”变化为“*(乘)”;扩充运算符的运算对象数据类型(如将+也适用对象类型)。 (4)实质:函数重载。,-13-,(5)C+中的运算符除了类属关系运算符“

7、.”、作用域分辨符“:”、成员指针运算符“.*”、sizeof运算符和三目运算符“?:”之外,全部可以重载,而且只能重载C+中已有的运算符,不能臆造新的运算符。 (6)重载之后运算符的优先级和结合性都不能改变,也不能改变运算符的语法结构,即单目运算符只能重载为单目运算符,双目运算符只能重载为双目运算符。 (7)运算符重载后的功能应当与原有功能相类似。 (8)重载运算符含义必须清楚,不能有二义性。,-14-,3.运算符种类: 一元运算符,二元运算符. 4.实现手段:借助与类运算符函数和友元运算符函数来实现. 5.运算符函数(语法):由关键字operator后跟一运算符. 如operator+()

8、 6.实现机制: (1)将指定的运算表达式转化为对运算符函数的调用,通过函数调用从而在函数体中达到特定的运算功能。 (2)运算对象转化为运算符函数的实参。 (3)根据实参的类型来确定需要调用的函数。,-15-,2.2 运算符重载为成员函数(类运算符函数):因为它是成员函数,从而允许它访问私有成员数据。 1.定义形式:类同于一般的成员函数并且也允许为内联函数,只是函数名有要求。 operator(形参表) 函数体; ,-16-,2.如何设计: (1)函数返回值类型-与运算结果的数据类型一致; (2)函数名-以“operator运算符”命名; (3)函数形参-类型与运算对象的数据类型一致(一般采用

9、引用),如果使用类运算符函数,则个数为运算对象的个数减一;如果使用友元运算符函数,则个数与运算对象的个数相同; (4)函数体-按照运算符的自然运算语义来编程。 3.应用要点:被重载的运算符如用于基本类型的数据运算,系统仍旧使用原始的运算功能;只有被应用于指定的类的类型对象运算时才转化为对运算符函数的调用。,-17-,a.双目运算:oprdl B oprd2 oprdl.operator B (oprd2) b.单目运算 1)前置单目运算:U oprd oprd.operator U( ) 2)后置单目运算:oprd V 为区别前置单目运算函数要带有一个整型(int)形参。 oprd.opera

10、tor V(int),-18-,【例1】双目运算符重载为成员函数。 #include class point private: float x,y; public: point(float xx=0,float yy=0)x=xx;y=yy; float get_x()return x; float get_y()return y; point operator+(point p1);/重载运算符“+” point operator-(point p1);/和“-”为成员函数 ;,-19-,point point:operator+(point q) return point(x+q.x,y+

11、q.y); point point:operator-(point q) return point(x-q.x,y-q.y); void main() point p1(3,3),p2(2,2),p3,p4;/声明point类的对象 p3=p1+p2;/两点相加 p4=p1-p2;/两点相减 coutp1+p2:x=p3.get_x(),y=p3.get_y()endl; coutp1-p2:x=p4.get_x(),y=p4.get_y()endl; ,创建一个临时的无名对象作为返回值,效率高。,-20-,point point:operator+(point q) point p; p.x

12、=x+q.x; p.y=y+q.y; return p; point point:operator-(point q) point p; p.x=x-q.x; p.y=y-q.y; return p; ,创建一个局部对象p(这时会调用构造函数),执行return语句时,会调用拷贝构造函数,将p的值拷贝到主调函数中的一个无名临时对象中。当函数operator+结束时,会调用析构函数析构对象p,然后p消亡。,-21-,【例2】单目运算符重载为成员函数。 #include class point private: float x,y; public: point(float xx=0,float y

13、y=0)x=xx;y=yy; float get_x()return x; float get_y()return y; point operator+();/重载前置运算符“+” point operator-();/重载前置运算符“-” ;,-22-,point point:operator+() if(x0)-x; if(y0)-y; return *this; ,-23-,void main() point p1(10,10),p2(200,200); for(int i=0;i5;i+) coutp1:x=p1.get_x(),y=p1.get_y()endl; +p1; for(i

14、=0;i5;i+) coutp2:x=p2.get_x(),y=p2.get_y()endl; -p2; ,-24-,程序运行结果为 p1:x=10,y=10 p1:x=11,y=11 p1:x=12,y=12 p1:x=13,y=13 p1:x=14,y=14 p2:x=200,y=200 p2:x=199,y=199 p2:x=198,y=198 p2:x=197,y=197 p2:x=196,y=196,-25-,前置单目运算符和后置单目运算符重载的区别: 函数的形参。 语法规定,前置单目运算符重载为成员函数时没有形参,而后置单目运算符重载为成员函数时,需要有一个int型形参。这个int

15、型参数在函数体中并不使用,纯粹是用来区别前置与后置。,-26-,2.3 运算符重载为友元函数:将运算符函数设计为非成员函数,同时为能访问private成员数据,将它设计为友元函数(其定义和使用规则与一般的友元函数相同)。 1.语法: friend operator(形参表) 函数体; ,-27-,2.应用要点: (1)注意形参的定义与类运算符函数的不同;友元运算符函数无this指针,因而在函数体内必须通过对象名来使用private成员数据;一般将一元运算符函数重载为类运算符函数(“=”也必须),而将二元运算符重载为友元运算符函数,以免出现使用错误。 (2)运算所需要的操作数都需要通过函数的形参

16、表来传递,在参数表中形参从左到右的顺序就是运算符操作数的顺序。 (3)有些运算符不能重载为友元,如“=”、“()”、“”和“-”。,-28-,a.双目运算:oprdl B oprd2 operator B (oprdl,oprd2) b.单目运算: 1)前置单目运算:U oprd operator U(oprd) 2)后置单目运算:oprd V 函数的形参有两个,一个是A类的对象oprd, 另一个是整型(int)形参。 operator V(oprd,int),-29-,【例3】双目运算符重载为友元重载。 #include class point private: float x,y; pub

17、lic: point(float xx=0,float yy=0)x=xx;y=yy; float get_x()return x; float get_y()return y; friend point operator+(point p1,point p2); friend point operator-(point p1,point p2); ;,-30-,point operator+(point p1,point p2) return point(p1.x+p2.x,p1.y+p2.y); point operator-(point p1,point p2) return point

18、(p1.x-p2.x,p1.y-p2.y); void main() point p1(3,3),p2(2,2),p3,p4;/声明point类的对象 p3=p1+p2;/两点相加 p4=p1-p2;/两点相减 coutp1+p2:x=p3.get_x(),y=p3.get_y()endl; coutp1-p2:x=p4.get_x(),y=p4.get_y()endl; ,-31-,2.4 其它运算符重载 比较运算符重载(如,=,=,!=)。 赋值运算符重载(如=,+=,-=,*=,/=)。 下标运算符“”重载。 运算符new和delete重载。 逗号运算符“,”重载。,-32-,#incl

19、ude class counter public: counter()v=0; counter operator +(); counter operator +(int); void print()coutvendl; private: unsigned v; ;,counter counter:operator +() v+; return *this; counter counter:operator +(int) counter t; t.v=v+; return t; ,void main() counter c; for(int i=0;i8;i+) c+; c.print();,f

20、or(i=0;i8;i+) +c; c.print(); ,-33-,3 虚函数,3.1为什么要引入虚函数 #include class base public: void who() coutthis is the class of base!endl; ; class derive1:public base public: void who() coutthis is the class of derive1!endl; ;,-34-,class derive2:public base public: void who() coutwho();,ptr=,通过指针引起的普通成员函数调用,仅

21、仅与指针的类型有关,而与指针正指向什么对象无关。,此程序的意图是用ptr指针分别指向不同的对象,以便执行不同对象所对应的类的成员函数。,-35-,this is the class of base! this is the class of base! this is the class of base! this is the class of derive1! this is the class of derive2! 为达到目的采取的方法: (1)采用显式的方式调用派生类的函数成员 例如: obj1.who()或obj2.who() (2)采用对指针的强制类型转换的方法, 例如:(der

22、ive1*)ptr)-who()或(derive2*)ptr)-who(),-36-,关于类型转换,char ch;,int i;,1Byte,ch=(char)i; i=(int)ch;,1Byte,1Byte,1Byte,1Byte,-37-,Fish对象内存布局,Animal对象内存,Fish继承部分,this指针,Fish对象的内存,图 Fish对象内存布局,-38-,本来使用对象指针是为了表达一种动态的性质,即当指针指向不同对象时执行不同的操作,但并没有起到这种作用。 要实现这种功能-引入虚函数的概念。只需将基类的who()函数声明为虚函数即可。,-39-,3.2 虚函数的定义及使用

23、 1.虚函数的定义:在基类中以virtual加以修饰的非静态成员函数并且在一级或多级派生类中被重新定义的成员函数。 2.为什么需要虚函数:当派生类覆盖基类中同名成员函数时,为避免调用时的二义性可以采用静态或动态联编。 (1)静态联编:采用对象名(借助同名覆盖原则:派生类覆盖基类中同名成员;未强行指明则为派生类同名成员;如访问被同名覆盖的同名基类成员,应使用基类名限定。)或类名限定(根据定义时的类型编译调用代码) (2)动态联编:采用基类对象的指针或引用(因为有些问题在设计时不能预知或为提高程序灵活性,必须使用动态联编),此时根据指针指向的目标对象的类型编译调用代码。,-40-,3.如何实现动态

24、联编:在基类中将同名成员函数定义为虚函数;调用这些同名函数时应采用基类对象的指针或引用。 4.虚函数的特点: (1)具有继承性-在基类中一旦定义为虚函数,在以后的各级派生类中同名成员函数自动也为虚函数而不管是否有virtual修饰; (2)虚函数的本质:是替换(或称覆盖)而非重载定义。 (3)派生类中如没有重新定义虚函数,则将继承基类中的虚函数,此时系统仍旧采用静态联编;析构函数可以为虚函数但构造函数不能为虚函数,并且虚函数不适用于有大量for,do-while,while语句的场合以免频繁调用,降低速度。,-41-,5.如何调用虚函数:采用基类对象的指针或引用并根据该指针当前的目标对象的类型

25、,动态地调用此对象所属的类中的虚函数。 6.应用要点: 在调用虚函数时是根据基类的指针当前所指向的目标对象而非该指针在定义时的类类型; 7.必须使用虚函数的场合:设计时无法预知所需要使用的函数。,-42-,8.定义语法:virtual只修饰成员函数的说明而非函数体的定义 virtual(形参表) 函数体 注意: (1)虚函数的声明只能出现在类声明中的函数原型声明中,而不能出现在成员的函数体实现的时候。 (2)当基类中的某个成员函数被声明为虚函数后,此虚函数就可以在一个或多个派生类中被重新定义,在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数、参数类型以及参数的顺序都必须与基类中

26、的原型完全相同。,virtual告诉编译器:这个成员函数在派生类中可能会有不同实现,当用这个成员函数操作指针或引用所标识的对象时,对这个成员函数调用的束定应延迟到程序运行时再进行。,-43-,(3)运行过程中的多态要满足3个条件: a.类型兼容原则 b.声明虚函数 c.由成员函数来调用或者是通过指针、引用来访问虚函数。,-44-,关于函数的覆盖与隐藏,#include class Animal public: void eat() coutanimal eatendl; void sleep() coutanimal sleependl; void breathe() coutaniaml b

27、reatheendl; ; class Fish : public Animal public: breathe() coutfish bubbleendl; ; void main() Fish fh; fh. breathe();,breathe() Animal: breathe(); coutfish bubbleendl; :作用域标识符,用于指明一个函数属于那个类或一个数据成员属于那个类。,所谓隐藏是指派生类中具有与基类同名的函数(不考虑参数列表是否相同),从而在派生类中隐藏了基类的同名函数。 构成函数覆盖的条件: 派生类函数与基类函数完全相同,基类没有使用virtual。 派生类

28、的函数与基类函数同名,但参数列表不同,不管基类是否使用virtual 。 当隐藏发生时可使用基类名:函数名 (参数)调用基类的被隐藏函数。,-45-,函数的覆盖,Derived:xfn(int i) Derived:xfn(int i) Base:yfn(float i) Derived:yfn(float i) Base:zfn() Derived:zfn(),#include class Base public: virtual void xfn(int i) coutxfn(5); pD-xfn(5); pB-yfn(3.14f); pD-yfn(3.14f); pB-zfn(); pD

29、-zfn(); ,函数的覆盖(override)是发生在父类与子类之间的。 重载发生在同一类。 构成函数覆盖的条件: 基类函数必须是虚函数。 发生覆盖的两个函数要分别位于派生类和基类中。 函数名称和参数列表必须完全相同。,-46-,虚函数与多态性,#include class Animal public: void eat() coutbreathe(); void main() Fish fh; Animal *pAn; pAn= ,C+编译器进行了类型转换, pAn保存的就是Animal对象地址。,C+编译器在编译时就确定了哪个函数被调用,这叫做早期绑定(early binding) 。,

30、-47-,#include class Animal public: void eat() coutbreathe(); void main() Fish fh; Animal *pAn; pAn= ,虚函数与多态性,当C+编译器在编译的时候,发现Animal类的breathe()函数是虚函数,这个时候C+就会采用迟绑定(late binding)的技术,在运行时,依据对象的类型(在程序中,我们传递的Fish类对象的地址)来确认调用的哪一个函数,这种能力就叫做C+的多态性。,-48-,虚函数与多态性,在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调

31、用相应的函数,如果对象类型是派生类,就调用派生类的函数,如果对象类型是基类,就调用基类的函数。,-49-,【例5】使用虚函数。 #include class base public: virtual void who()/虚函数声明 coutthis is the class of base!endl; ; class derive1:public base public: void who()/重新定义虚函数 coutthis is the class of derive1!endl; ;,-50-,class derive2:public base public: void who()/重

32、新定义虚函数 coutwho();,ptr= ,程序运行结果 this is the class of base! this is the class of derive1! this is the class of derive2!,ptr指针指向哪个对象是在执行过程中确定的,所以体现的是一种动态的多态性。,-51-,3多继承中的虚函数【例6】多继承中使用虚函数。 #include class base1 public: virtual void who()/函数who()为虚函数 coutthis is the class of base1!endl; ; class base2 publ

33、ic: void who()/此函数who()为一般的函数 coutthis is the class of base2!endl; ;,-52-,class derive:public base1,public base2 public: void who() coutwho(); ptr2=,ptr2-who(); ptr1= ,程序执行的结果为 this is the class of base1! this is the class of base2! this is the class of derive! this is the class of base2!,who()在不同的场

34、合呈现不同的性质,-53-,3.3 虚函数的限制 只有成员函数才能声明为虚函数。因为虚函数仅适用于有继承关系的类对象,所以普通函数不能声明为虚函数。 虚函数必须是非静态成员函数。这是因为静态成员函数不受限于某个对象。 内联函数不能声明为虚函数。因为内联函数不能在运行中动态确定其位置。即使虚函数在类的内部定义,编译时,仍将其看作非内联的。 构造函数不能声明为虚函数。多态是指不同的对象对同一消息有不同的行为特性。虚函数作为运行过程中多态的基础,主要是针对对象的,而构造函数是在对象产生之前运行的,因此,虚构造函数是没有意义的。 析构函数可以声明为虚函数。析构函数的功能是在该类对象消亡之前进行一些必要

35、的清理工作。,-54-,3.4 虚析构函数 1.定义:将析构函数设计为虚函数。 2.原因:构造函数必须在创建对象这一时刻被调用而不能迟后调用,故不能为虚函数;析构函数是在对象被创建以后调用,因而可以迟后调用,即可为虚函数。 3.应用场合:当利用基类的指针来定义产生派生类的对象时,为删除派生类的对象应设计出虚析构函数(将基类的析构函数设计为虚函数)。 4.只要基类的析构函数被声明为虚函数,则派生类的析构函数无论是否使用virtual进行说明,都自动地成为虚函数。 4.声明语法: virtual类名,-55-,【例8】虚析构函数的使用 #include class A public: virtua

36、lA()cout“A:A() called.n”; ; class B:public A public: B(int i)(buf=new chari; virtualB() delete buf; cout“B:B() called.n”; ,private: char *buf; ; void fun(A *a) delete a; void main() A *a=new B(15); fun(a); ,运行结果: B:B() called. A:A() called.,如果类A中的析构函数不用虚函数,结果如何? A:A() called.,基类的析构函数由派生类的析构函数调用,-56-

37、,4 抽象类,1.纯虚函数定义:纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的实现内容。 2.纯虚函数的语法声明形式: virtual(参数表)=0 纯虚函数与一般虚函数在书写形式上的不同在于其后面加了“=0”,表明在基类中不用定义该函数,它的实现部分函数体留给派生类去做。 3.抽象类定义:包含一个或多个纯虚函数的基类。,-57-,纯虚函数,#include class Animal public: void eat() coutbreathe(); void main() Fish fh; Animal *pAn; pAn= ,纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体给出定义。这样的类叫抽象类。在派生类中必须完全实现基类的纯虚函数。 纯虚函数多用在一些方法行为的设计上,在设计基类时不太好确定或将来的行为多种多样,而此行无为有时必须的。 C+的多态性是由虚函数实现的,而不是纯虚函数。在子类中如果有对基类虚函数的覆盖定义,无论覆盖定义是否有virtual关键字,都是虚函数。,-58-

温馨提示

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

评论

0/150

提交评论