C程序设计(4-12).ppt_第1页
C程序设计(4-12).ppt_第2页
C程序设计(4-12).ppt_第3页
C程序设计(4-12).ppt_第4页
C程序设计(4-12).ppt_第5页
已阅读5页,还剩91页未读 继续免费阅读

下载本文档

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

文档简介

1、C+程序设计,理学院信息与计算科学系 杨 波 cookie_,第四部分 高级编程,第12章 多态(Polymorphism,第12章 多态,1. 继承召唤多态,2. 抽象编程的困惑,3. 多态性的概念,4. 虚函数,5. 避免虚函数误用,6. 精简共性的类,7. 多态编程,8. 类型转换,1.继承召唤多态,子类是父类的一种 父类不是子类的一种 常见父类与子类的操作 指针 父类指针操作父类对象(自然) 子类指针操作子类对象(自然) 父类指针操作子类对象(将子类看成父类的一种,安全) 子类指针操作父类对象(将父类看成子类的一种,不合理,危险) 引用 父类引用作为父类对象别名(自然) 子类引用作为子

2、类对象别名(自然) 父类引用作为子类对象别名(将子类看成父类的一种,安全) 子类引用作为父类对象别名(将父类看成子类的一种,不合理,危险,例12-1 源代码e1201.cpp,Deri dd1; Base * pbtr,pbtr,派生类对象作为基类对象合理 裁去专属于派生类部分 强制类型转换,Base bb1; Deri * pdtr= (Deri*,pdtr,基类对象作为派生类对象不合理,覆盖父类操作,如果父类中有一个操作,在子类中没有重新定义,则子类可以沿用该操作,但都是父类操作。 子类可以进行同名覆盖,进行子类操作,例12-2 源代码e1202.cpp,同化效应,例12-3 源代码e12

3、03.cpp,渴望多态,设立一个学生缴费办公室,处理全校学生的缴费工作,class Student public: double calcTuition ( ) ; class GraduateStudent : public Student public: double calcTuition ( ) ; class UnderGraduateStudent : public Student Public: double calcTuition ( ),void main UnderGraduateStudent us1, us2; GraduateStudent gs1, gs2; ? ?

4、 ? ? ? ,渴望多态,设立一个学生缴费办公室,处理全校学生的缴费工作,class Student public: double calcTuition ( ) ; class GraduateStudent : public Student public: double calcTuition ( ) ; class UnderGraduateStudent : public Student Public: double calcTuition ( ),void main UnderGraduateStudent us1, us2; GraduateStudent gs1, gs2; st

5、d:vector v; v.push_back(,void fn (Student* x) x-calcTuition( );,一个操作随着所传递或捆绑的对象类型的不同能够做出不同的反应,其行为模式称为多态,渴望多态,设立一个学生缴费办公室,处理全校学生的缴费工作,class Student public: double calcTuition ( ) ; class GraduateStudent : public Student public: double calcTuition ( ) ; class UnderGraduateStudent : public Student Publ

6、ic: double calcTuition ( ),void main UnderGraduateStudent us1, us2; GraduateStudent gs1, gs2; std:vector v; v.push_back(us1); v.push_back(us2); v.push_back(gs1); v.push_back(gs2); for(int i=0; iv.size(); +i) fn(vi);,void fn (Student,一个操作随着所传递或捆绑的对象类型的不同能够做出不同的反应,其行为模式称为多态,2.抽象编程的困惑,类型域方案可以做到,即实现fn函数

7、如下: void fn(Student 但不敢恭维这种方法,因为它导致类编程与应用编程互相依赖,因而破坏了只关注局部细节的抽象编程,例12-4 源代码e1204.cpp,破坏抽象编程的后果是: 可维护性,可扩展性受到伤害 若增加一个博士类,则类代码与应用程序代码都得改,而这本来不是应用程序的份内事 因而呼吁从语言内部来支持这种多态性,多态性和虚函数,例12-5 源代码e1205.cpp,3.多态性的概念(1/5,多态性:具有继承关系的类,其对象对同一个函数调用可以作出不同的响应 同一个函数调用同一条函数调用语句 不同的响应执行不同的函数,是面向对象设计的一个重要特征,向不同的对象发送同一个消息

8、,不同的对象在接收时会产生不同的行为(即方法,多态性 是“一个接口,多种方法,3.多态性的概念(2/5,静态绑定和动态绑定 静态绑定编译时就能确定一条函数调用语句要调用的函数 编译时的多态性 函数的重载来实现的,静态绑定,普通成员函数重载可表达为两种形式,1. 在一个类说明中重载,例如:Show ( int , char ) ; Show ( char * , float ),普通成员函数重载可表达为两种形式,1. 在一个类说明中重载,例如:Show ( int , char ); 与 Show ( char * , float ); 不是同一函数,编译能够区分,2. 基类的成员函数在派生类重

9、载。有 3 种编译区分方法,1)根据参数的特征加以区分,静态绑定,普通成员函数重载可表达为两种形式,1. 在一个类说明中重载,2. 基类的成员函数在派生类重载。有 3 种编译区分方法,1)根据参数的特征加以区分,例如:A : Show ( ); 有别于B : Show (,2)使用“ : ”加以区分,静态绑定,静态绑定,普通成员函数重载可表达为两种形式,1. 在一个类说明中重载,2. 基类的成员函数在派生类重载。有 3 种编译区分方法,1)根据参数的特征加以区分,2)使用“ : ”加以区分,例如:Aobj . Show ( )调用A : Show ( ) Bobj . Show ( )调用B

10、: Show (,3)根据类对象加以区分,根据this指针类型区分,3.多态性的概念(3/5,静态绑定和动态绑定 动态绑定:运行时才能确定函数调用语句调用的函数 调用对象的成员函数时(通过引用或指针),编译器暂不确定要调用的函数 编译器检查被调用函数是否存在(函数名、参数列表、返回值) 程序运行时,系统根据接收消息的对象类型来确定要调用的函数 运行时的多态性 通过虚函数来实现,3.多态性的概念(4/5,多态性的实现 编译时多态性 函数的重载来实现的 静态绑定 运行时多态性 通过基类指针或引用调用虚函数 动态绑定 多态性有助于更好地对程序进行抽象 控制模块能专注于一般性问题的处理 具体的操作交给

11、具体的对象去做,3.多态性的概念(5/5,多态性有助于提高程序的可扩展性 可以把控制模块与被操作的对象分开 可以添加已定义类的新对象,并能管理该对象 可以添加新类(已有类的派生类)的新对象,并能管理该对象 常用的程序设计方法 为具有继承关系的类定义接口(虚函数) 用链表、数组或容器组织所有对象 用基类指针或引用操作这些对象,问题,能否用同一个调用形式,既能调用派生类又能调用基类的同名函数,虚函数 动态绑定 运行时多态,4 虚函数,虚函数的定义 在函数原型前加上关键字virtual 如果一个函数在基类中被声明为虚函数,则他在所有派生类中都是虚函数(包括重定义函数) virtual void di

12、splay( ); 实现运行时多态的关键首先是要说明虚函数,另外,必须用基类指针(引用)调用虚函数在派生类的不同实现版本,例:基类与派生类中有同名函数,include class Base public : Base(char xx) x = xx; void who() cout who() ; p =,include class Base public : Base(char xx) x = xx; void who() cout who() ; p =,include class Base public : Base(char xx) x = xx; void who() cout wh

13、o() ; p =,p,定义基类指针,include class Base public : Base(char xx) x = xx; void who() cout who() ; p =,p,include class Base public : Base(char xx) x = xx; void who() cout who() ; p =,include class Base public : Base(char xx) x = xx; void who() cout who() ; p =,include class Base public : Base(char xx) x =

14、 xx; void who() cout who() ; p =,include class Base public : Base(char xx) x = xx; void who() cout who() ; p =,include class Base public : Base(char xx) x = xx; void who() cout who() ; p =,include class Base public : Base(char xx) x = xx; void who() cout who() ; p =,include class Base public : Base(

15、char xx) x = xx; void who() cout who() ; p =,void who() cout First derived class: x , y n ;,include class Base public : Base(char xx) x = xx; void who() cout who() ; p =,void who() cout First derived class: x , y n ;,通过对象调用成员函数,include class Base public : Base(char xx) x = xx; void who() cout who()

16、; p =,void who() cout Second derived class: x , y , z n ;,include class Base public : Base(char xx) x = xx; void who() cout who() ; p =,void who() cout Second derived class: x , y , z n ;,基类指针做类型转换,include class Base public : Base(char xx) x = xx; void who() cout who() ; p =,通过基类指针 只能访问从基类继承的成员,incl

17、ude class Base public : Base(char xx) x = xx; void who() cout who() ; p =,修改程序 定义虚函数,include class Base public : Base(char xx) x = xx; virtual void who() cout who() ; p =,基类定义虚函数,include class Base public : Base(char xx) x = xx; virtual void who() cout who() ; p =,派生类的重定义版本 默认为虚函数,include class Base

18、 public : Base(char xx) x = xx; virtual void who() cout who() ; p =,include class Base public : Base(char xx) x = xx; virtual void who() cout who() ; p =,p,include class Base public : Base(char xx) x = xx; virtual void who() cout who() ; p =,p,include class Base public : Base(char xx) x = xx; virtua

19、l void who() cout who() ; p =,include class Base public : Base(char xx) x = xx; virtual void who() cout who() ; p =,void who() cout First derived class: x , y n ;,include class Base public : Base(char xx) x = xx; virtual void who() cout who() ; p =,void who() cout First derived class: x , y n ;,incl

20、ude class Base public : Base(char xx) x = xx; virtual void who() cout who() ; p =,include class Base public : Base(char xx) x = xx; virtual void who() cout who() ; p =,void who() coutSecond derived class: x, y, zn ;,include class Base public : Base(char xx) x = xx; virtual void who() cout who() ; p

21、=,void who() coutSecond derived class: x, y, zn ;,include class Base public : Base(char xx) x = xx; virtual void who() cout who() ; p =,由于who()的虚特性 随着p指向不同对象,this指针作类型转换 执行不同实现版本,include class Base public : Base(char xx) x = xx; virtual void who() cout who(); void main() Base B_obj( A ) ; First_d F_

22、obj( T, O ) ; Second_d S_obj( E, N, D ) ; fun(,虚函数 的传播,include class Base public : Base(char xx) x = xx; virtual void who() cout who(); void main() Base B_obj( A ) ; First_d F_obj( T, O ) ; Second_d S_obj( E, N, D ) ; fun(,主控函数控制了继承体系中的所有成员 好处: 只用关心自身的控制逻辑,不用关心对象的不同,include class Base public : Base(

23、char xx) x = xx; virtual void who() cout who(); void main() Base B_obj( A ) ; First_d F_obj( T, O ) ; Second_d S_obj( E, N, D ) ; fun(,仅仅对于对象的指针和引用传递才会引发多态,例,class Base public: virtual void fn() cout In Base classn; ; class Sub : public Base public: virtual void fn() cout In Sub classn; ; void test(

24、Base,仅仅对于对象的指针和引用传递才会引发多态,虚函数和基类指针(引用,一个虚函数,在派生类层界面相同的重载函数都保持虚特性(虚函数的传播) 虚函数必须是类的成员函数,而且,静态成员函数都不行,因为它不捆绑对象,同样,构造函数也不行,因为它只产生对象,也不捆绑对象。 析构函数可以是虚函数,但构造函数不能是虚函数,事实上,鼓励类继承体系中的每个类最好其析构函数都是虚函数。 一旦设置了虚函数,就与编译器达成了滞后联编的协议,函数必定分离于当前运行的模块,因而就不可能是内联函数了,虚析构函数,析构函数可以声明为虚函数 delete 基类指针; 程序会根据基类指针指向的对象的类型确定要调用的析构函

25、数 基类的析构函数为虚函数,所有派生类的析构函数都是虚函数 如果要操作具有继承关系的类的动态对象,最好把基类的析构函数声明为虚函数,程序设计技巧 专业人员一般都习惯声明虚析构函数, 即使基类并不需要析构函数, 也显式地定义一个函数体为空的虚析构函数, 以保证在撤销动态分配空间时能得到正确的处理,include class A public: A() cout A:A() is called.n ; ; class B : public A public: B() cout B:B() is called.n ; ; void main() A *Ap = new B ; B *Bp2 = ne

26、w B ; cout delete first object:n ; delete Ap; cout delete second object:n ; delete Bp2 ;,普通析构函数在删除动态派生类对象的调用情况,include class A public: A() cout A:A() is called.n ; ; class B : public A public: B() cout B:B() is called.n ; ; void main() A *Ap = new B ; B *Bp2 = new B ; cout delete first object:n ; de

27、lete Ap; cout delete second object:n ; delete Bp2 ;,用基类指针 指向派生类的动态对象,普通析构函数在删除动态派生类对象的调用情况,include class A public: A() cout A:A() is called.n ; ; class B : public A public: B() cout B:B() is called.n ; ; void main() A *Ap = new B ; B *Bp2 = new B ; cout delete first object:n ; delete Ap; cout delete

28、 second object:n ; delete Bp2 ;,用派生类指针 指向派生类的动态对象,普通析构函数在删除动态派生类对象的调用情况,include class A public: A() cout A:A() is called.n ; ; class B : public A public: B() cout B:B() is called.n ; ; void main() A *Ap = new B ; B *Bp2 = new B ; cout delete first object:n ; delete Ap; cout delete second object:n ;

29、delete Bp2 ;,析构由基类指针指向的派生类对象 没有调用派生类析构函数,普通析构函数在删除动态派生类对象的调用情况,include class A public: A() cout A:A() is called.n ; ; class B : public A public: B() cout B:B() is called.n ; ; void main() A *Ap = new B ; B *Bp2 = new B ; cout delete first object:n ; delete Ap; cout delete second object:n ; delete Bp

30、2 ;,析构由派生类指针指向的派生类对象 正确调用派生类析构函数,普通析构函数在删除动态派生类对象的调用情况,include class A public: A() cout A:A() is called.n ; ; class B : public A public: B() cout B:B() is called.n ; ; void main() A *Ap = new B ; B *Bp2 = new B ; cout delete first object:n ; delete Ap; cout delete second object:n ; delete Bp2 ;,虚析构函

31、数在删除动态派生类对象的调用情况,virtual A() cout A:A() is called.n ;,include class A public: A() cout A:A() is called.n ; ; class B : public A public: B() cout B:B() is called.n ; ; void main() A *Ap = new B ; B *Bp2 = new B ; cout delete first object:n ; delete Ap; cout delete second object:n ; delete Bp2 ;,虚析构函数

32、在删除动态派生类对象的调用情况,virtual A() cout A:A() is called.n ;,正确调用派生类析构函数 释放所有资源,include class A public: A() cout A:A() is called.n ; ; class B : public A public: B() cout B:B() is called.n ; ; void main() A *Ap = new B ; B *Bp2 = new B ; cout delete first object:n ; delete Ap; cout delete second object:n ;

33、delete Bp2 ;,虚析构函数在删除动态派生类对象的调用情况,virtual A() cout A:A() is called.n ;,定义了基类虚析构函数,基类指针指向的 派生类动态对象也可以正确地用delete析构 设计类层次结构时,提供一个虚析构函数, 能够使派生类对象在不同状态下正确调用 析构函数,虚函数的作用,允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数,虚函数的使用方法,在基类用virtual声明成员函数为虚函数。 在派生类中重新定义此函数 定义一个指向基类对象的指针(引用)变量,并使它指向同一类族中需要调用该函数的对象。

34、通过该指针(引用)变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数,在类外定义虚函数时,不必再加virtual,函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,根据派生类的需要重新定义函数体,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰,如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数,虚函数机理,当编译器看见虚函数标志的时候,便暗暗记在心中,等到遇到虚函数的实际调用时,便将该捆绑操作

35、滞后到运行中,以实际的对象类型来实际捆绑其对应的成员函数操作,编译器不可能跟到运行的程序中去?编译器怎么做到,虚函数机理,当编译器看见虚函数标志的时候,便暗暗记在心中,等到遇到虚函数的实际调用时,便将该捆绑操作滞后到运行中,以实际的对象类型来实际捆绑其对应的成员函数操作。 编译器在虚函数调用(捆绑调用)处避开函数调用,只做一个指向实际对象的成员函数的间接访问(调用),实际对象若是基类则调用基类的成员函数,若是子类则调用子类的成员函数,虚函数机理,静态关联与动态关联,确定调用的具体对象的过程称为关联(binding)。 指把一个函数名与一个类对象捆绑在一起,建立关联。 一般地说,关联指把一个标识

36、符和一个存储地址联系起来。 静态关联 在运行前进行关联的,故又称为早期关联(early binding)。 重载属静态关联 动态关联 运行阶段的多态性 由于动态关联是在编译以后的运行阶段进行的,因此也称为滞后关联(late binding,面向对象的真意,类机制本身若不能支持多态编程,那只能获得一定程度的抽象编程,只能是基于对象的编程,不是完全面向对象的编程。 编译器看见虚函数调用就要做滞后处理。由于间接访问比直接访问绕了一个弯,于是付出了时间代价和保存若干指针地址的空间代价。 为了在使用类的编程中随时随地体现多态性,只要是继承结构,应尽量将成员函数设计成虚函数。(Java是这么处理的,程序员

37、没有选择的余地) C+可以选择设计一些独立的类,处理这些类对象不存在多态的问题,所以免去虚函数设计而赢得一点性能,多态性使得应用程序使用类体系中的祖孙对象共存的复杂局面达到了一种编程自在境界。 程序员从使用孤立的类(抽象数据类型),到使用分层的类,并且让各种对象“同场竞技”,充分展现其个性,尝到了对象化编程的真正乐趣。 +类机制的虚函数就是冲着让类编程实质性地支持应用编程中对家族化对象操作依赖的目的,从而面向对象来分析、设计和解决问题,5. 避免虚函数误用,搞清重载与覆盖,虚函数的重载特性,在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同 如果仅仅返回类型不同,

38、C+认为是错误重载 如果函数原型不同,仅函数名相同,丢失虚特性(覆盖,例: class base public : virtual void vf1 ( ) ; virtual void vf2 ( ) ; virtual void vf3 ( ) ; void f ( ) ;,void g ( ) derived d ; base * bp =,class derived : public base public : void vf1 ( ) ;/ 虚函数 void vf2 ( int ) ;/ 重载,参数不同,虚特性丢失 char vf3 ( ) ;/ error,仅返回类型不同 void

39、 f ( ) ;/ 非虚函数覆盖,在什么情况下应当声明虚函数,根据什么考虑是否把一个成员函数声明为虚函数 首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。 如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。不要仅仅考虑到要作为基类而把类中的所有成员函数都声明为虚函数。 应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问,如果是通过基类指针或引用去访问的,则应当声明为虚函数。 有时,在定义虚函数时,并不定义其函数体,即函数体是空的。它的作用只是定义了一个虚函数名,具体

40、功能留给派生类去添加,6. 精简共性的类,孤立的类,存款账户类 账号,余额 存款,对象创建,获得余额,显示和取款操作,结算账户类 账号,余额,汇款方式 对象创建,显示,存款,获得余额,取款操作和设置汇款方式,例12-6 源代码f1209.cpp,减少冗余代码,例12-7 源代码f1210.cpp,改变基类殃及子类,结算账户是储蓄账户的一种。? 银行改变了储蓄账户的政策,允许储蓄账户透支 在Savings类中增加表示透支范围的新的数据成员minBalance。 修改withdrawal操作,实施政策 Checking类通过继承拥有minBalance成员,且Checking类的withdrawal通过调用Savings类的withdrawal,也获得了这种透支能力? 这种继承合理吗,7. 多态编程,共同基类方案,例12-8 源代码f1211.cpp,自定义链表类(异质链表,程序中,用基类类型指针,可以生成一个连接不同派生类对象的动态链表,即每个结点指针可以指向类层次中不同的派生类对象。这种结点类型不相同链表称为异质链表,例12-9 源代码f1212.cpp,8. 类型转换,C风格的强制转换 (T)expression:将表达式转化成类型T

温馨提示

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

评论

0/150

提交评论