《C++程序设计教程第二版课件C++程序设计教程12章节》由会员分享,可在线阅读,更多相关《C++程序设计教程第二版课件C++程序设计教程12章节(20页珍藏版)》请在金锄头文库上搜索。
1、17:02:04,1,C+程序设计教程(第二版),第十二章 多态 Chapter 12 Polymorphism,清华大学出版社 钱 能,17:02:04,2,第十章内容,继承召唤多态 (Inheritance Summon up Polymorphism) 抽象编程的困惑(Abstract Programming Perplexing) 虚函数(Virtual Function) 避免虚函数误用(Avoiding Misusing Virtual Function) 精简共性的类 (Simplify Class with Generality) 多态编程(Polymorphic Progra
2、mming) 类型转换(Type Conversions ),17:02:04,3,1. 继承召唤多态 (Inheritance Summon up Polymorphism),祖孙互易的说明: 晚辈是前辈的后继,晚辈是前辈的一种 反之不确,前辈不是晚辈,也不是晚辈的一种 这一概念也影响到类的继承操作,甚至其指针操作: Student是GraduateStudent的祖先则: Student s, *pS; GraduateStudent gs, *pGS; s = gs; / ok gs = s; / error pS = static_cast( / error,17:02:04,4,同化
3、效应,class Student public: / . . . void display() cout“UnderGraduaten”; ; class GraduateStudent :public Student public: / . . . void display() cout“Graduaten“; ; Student ds; GraduateStudent gs; ds.display(); gs.display();,对于右边的类和对象操作,则有下列结果: UnderGraduate Graduate 但是,若执行 s = gs; 则执行s.display()的结果为: Un
4、derGraduate 说明,gs虽然是研究生对象,但在赋值给大学生对象操作之后,被大学生同化了,丧失了研究生的本性,17:02:04,5,同化效应蔓延到指针操作,Student s, *ps; GraduateStudent gs; ps = 结果: UnderGraduate Graduate,这意味着子类对象就是父类对象的性质是一句空话因为操作起来原来是将子类对象粗暴的同化,根本扼杀了子类对象的个性 于是,子类对象与父类对象共存的容器便失去了意义,因为容器中没有子类对象,全部同化成一种父类对象了,世界失去了丰富多彩性!,17:02:04,6,关键技术,void fn(Student /
5、显示研究生信息 结果: UnderGraduate Graduate,子类对象赋值给父类对象,使得子类对象不复存在,这没有异议 Student s = gs; 否则,让父类对象行使子类职能就太离谱了 当子类对象的地址赋给父类对象指针(或引用)时,应让子类对象维持其状态的完整性,并且还要显露其子类的特征即多态性:,17:02:04,7,2. 抽象编程的困惑 ( Abstract Programming Perplexing ),类型域方案可以做到,即实现fn函数如下: void fn(Student 但不敢恭维这种方法,因为它导致类编程与应用编程互相依赖,因而破坏了只关注局部细节的抽象编程,17
6、:02:04,8,破坏抽象编程的后果是: 可维护性,可扩展性受到伤害 若增加一个博士类,则类代码与应用程序代码都得改,而这本来不是应用程序的份内事 因而呼吁从语言内部来支持这种多态性,17:02:04,9,3. 虚函数 ( Virtual Function ),于是应用程序便有多态,而且fn函数实现非常简捷: void fn(Student / 显示研究生信息 结果: UnderGraduate Graduate,类中采用虚函数: class Student public: virtual void display() cout“UnderGraduaten”; ; class Graduat
7、eStudent :public Student public: virtual void display() cout“Graduaten”; ; 注:子类同名函数上的virtual可省,17:02:04,10,多态性使得应用程序使用类体系中的祖孙对象共存的复杂局面达到了一种编程自在境界 程序员从使用孤立的类(抽象数据类型),到使用分层的类,并且让各种对象“同场竞技”,充分展现其个性.尝到了对象化编程的真正乐趣 +类机制的虚函数就是冲着让类编程实质性地支持应用编程中对家族化对象操作依赖的目的,从而面向对象来分析、设计和解决问题,17:02:04,11,4. 避免误用虚函数 ( Avoidin
8、g Misusing Virtual Function ),class Base public: virtual void fn(int x) cout“Basen“; ; class Sub : public Base public: virtual void fn(double x) cout“Subn“; ; void test(Base ,子类重载父类成员函数不能传播“虚”性 右边程序的运行结果说明了这一点: Base Base,17:02:04,12,函数重载对于编译识别来说,返回类型是不起作用的 void fn(int); int fn(int); 对于fn(2);无法判断应该调用
9、哪个函数而使调用遭遇编译失败 对于虚函数来说,声明: virtual Base* fn(); virtual Sub* fn(); 应看作不能分辨其多态调用的虚函数,但却引编译认为是同一个虚函数而获得通过 这是语言技术上的一种处理,事实上,如果一个多态函数正在处理Sub类的对象,则它仍可以通过返回的Sub对象指针,继续处理Sub对象,似乎更自然,17:02:04,13,注意事项: 多态性是通过成员函数捆绑不同类型的对象来体现的,所以,虚函数一定是成员函数,而且,静态成员函数都不行,因为它不捆绑对象,同样,构造函数也不行,因为它只产生对象,也不捆绑对象可是,析构函数却可以是虚函数,事实上,鼓励类
10、继承体系中的每个类最好其析构函数都是虚函数 一旦设置了虚函数,就与编译器达成了滞后联编的协议,函数必定分离于当前运行的模块,因而就不可能是内联函数了,17:02:04,14,5. 精简共性的类 ( Simplify Class with Generality ),孤立的类之间有一些共性,希望减少一些冗余代码 通常不能因此而构成上下级关系的类层次,因为会产生不良反应:基类如果扩展功能,势必连累子类,17:02:04,15,6. 多态编程 ( Polymorphic Programming ),将共性的类通过继承由共性构成的基类来实现或许会比较明智因为两者的扩展彼此之间不会带来干扰而且还可任意派生
11、子类 类的继承体系决定之后,其基类的多态成员函数也可确定通常可以在子类之间进行比较,同名不同功能的函数往往可以置成虚函数 class Account protected: / . . . public: virtual void display ( ) const; virtual void withdrawal ( double amount ) ;,17:02:04,16,多态编程中,会遇到各种子类混在一个集合中的情形,这正是多态大展身手的时候针对左边的类体系,有多态的应用代码: vector a; / . . . for(int i=0; iarea();,class Shape pub
12、lic: / . . . virtual void area() ; class Circle:public Shape public: void area() / . . . ; class Triangle:public Shape public: void area() / . . . ; / . . .,17:02:04,17,7. 类型转换 ( Type Conversions ),调用虚函数是一种多态的方法,它是将运行中无法知道的对象交给系统去自动辨认类型从而作出准确的操作 使用动态转型策略: 另一种实现是在运行中,判断对象的类型,从而可以准确的捆绑调用确定的成员函数,这是一种主动
13、辨认对象类型的编程策略,17:02:04,18,辨认对象的类型,首先应该知道其属于哪个类系然后确定要判断的子类名称再行编码例如,针对每个对象进行操作: Savings类对象,余额增加以1%计算的利息; Checking类对象,余额增加以0.05%计算的利息 vector a; / . Checking* pC; Savings* pS; for(int i=0; i(ai) pC-deposit(pC-getBalan()*0.05); else if(pS = dynamic_cast( ,动态转型,17:02:04,19,动态转型只限于多态类,而静态转型适合更一般的类对象 例如,没有多态性
14、的学生与研究生之间的转换: Student* ps=static_cast( /. ,静态转型,17:02:04,20,许多标准类库函数的声明为了适应大多数编程场合,做了完美的设计例如,max: const char* max(const char* s1, const char* s2) return strcmp(s1, s2)0 ? s1 : s2; 但是,编程总是调用标准库函数和自处理交替进行的,当返回的对象要进行写操作时,由于类型的限制,便不能再正确编码下去了然而,事实上,从一开始,对象完全是可修改的,只是由于函数调用的结果,而使对象性质被迫发生了变化为了将这种本质上可以修改的对象回复可修改的状态,使用常量转型 int fn() char* p = max(“hello“,“world“); / error char* p = const_cast(max(“hello”,”world”);/ok / ,常量转型,