您好,欢迎访问三七文档
当前位置:首页 > 电子/通信 > 综合/其它 > 钱能C++程序设计教程12
16:15:031C++程序设计教程(第二版)第十二章多态Chapter12Polymorphism清华大学出版社钱能16:15:032第十章内容1.继承召唤多态(InheritanceSummonupPolymorphism)2.抽象编程的困惑(AbstractProgrammingPerplexing)3.虚函数(VirtualFunction)4.避免虚函数误用(AvoidingMisusingVirtualFunction)5.精简共性的类(SimplifyClasswithGenerality)6.多态编程(PolymorphicProgramming)7.类型转换(TypeConversions)16:15:0331.继承召唤多态(InheritanceSummonupPolymorphism)祖孙互易的说明:晚辈是前辈的后继,晚辈是前辈的一种.反之不确,前辈不是晚辈,也不是晚辈的一种.这一概念也影响到类的继承操作,甚至其指针操作:Student是GraduateStudent的祖先.则:Students,*pS;GraduateStudentgs,*pGS;s=gs;//okgs=s;//errorpS=static_castStudent*(&gs);//okpGS=static_castGraduateStudent*(&s);//error16:15:034同化效应classStudent{public://...voiddisplay(){cout“UnderGraduate\n”;}};classGraduateStudent:publicStudent{public://...voiddisplay(){cout“Graduate\n;}};Studentds;GraduateStudentgs;ds.display();gs.display();对于右边的类和对象操作,则有下列结果:UnderGraduateGraduate但是,若执行s=gs;则执行s.display()的结果为:UnderGraduate说明,gs虽然是研究生对象,但在赋值给大学生对象操作之后,被大学生同化了,丧失了研究生的本性.16:15:035同化效应蔓延到指针操作Students,*ps;GraduateStudentgs;ps=&s;ps-display();ps=static_castStudent*(&gs);ps-display();结果:UnderGraduateGraduate这意味着子类对象就是父类对象的性质是一句空话.因为操作起来原来是将子类对象粗暴的同化,根本扼杀了子类对象的个性.于是,子类对象与父类对象共存的容器便失去了意义,因为容器中没有子类对象,全部同化成一种父类对象了,世界失去了丰富多彩性!16:15:036关键技术voidfn(Student&x){//...}intmain(){Students;GraduateStudentgs;fn(s);//显示大学生信息fn(gs);//显示研究生信息}结果:UnderGraduateGraduate子类对象赋值给父类对象,使得子类对象不复存在,这没有异议.Students=gs;否则,让父类对象行使子类职能就太离谱了.当子类对象的地址赋给父类对象指针(或引用)时,应让子类对象维持其状态的完整性,并且还要显露其子类的特征.即多态性:16:15:0372.抽象编程的困惑(AbstractProgrammingPerplexing)类型域方案可以做到,即实现fn函数如下:voidfn(Student&x){switch(x.type){caseStudent::STUDENT:x.calcTuition();break;caseStudent::GRADUATESTUDENT:GraduateStudent&rx=static_castGraduateStudent&(x);rx.calcTuition();break;}}但不敢恭维这种方法,因为它导致类编程与应用编程互相依赖,因而破坏了只关注局部细节的抽象编程.16:15:038破坏抽象编程的后果是:可维护性,可扩展性受到伤害.若增加一个博士类,则类代码与应用程序代码都得改,而这本来不是应用程序的份内事.因而呼吁从语言内部来支持这种多态性.16:15:0393.虚函数(VirtualFunction)于是应用程序便有多态,而且fn函数实现非常简捷:voidfn(Student&x){x.display()}intmain(){Students;GraduateStudentgs;fn(s);//显示大学生信息fn(gs);//显示研究生信息}结果:UnderGraduateGraduate类中采用虚函数:classStudent{public:virtualvoiddisplay(){cout“UnderGraduate\n”;}};classGraduateStudent:publicStudent{public:virtualvoiddisplay(){cout“Graduate\n”;}};注:子类同名函数上的virtual可省16:15:0310多态性使得应用程序使用类体系中的祖孙对象共存的复杂局面达到了一种编程自在境界.程序员从使用孤立的类(抽象数据类型),到使用分层的类,并且让各种对象“同场竞技”,充分展现其个性.尝到了对象化编程的真正乐趣.C++类机制的虚函数就是冲着让类编程实质性地支持应用编程中对家族化对象操作依赖的目的,从而面向对象来分析、设计和解决问题.16:15:03114.避免误用虚函数(AvoidingMisusingVirtualFunction)classBase{public:virtualvoidfn(intx){coutBase\n;}};classSub:publicBase{public:virtualvoidfn(doublex){coutSub\n;}};voidtest(Base&b){b.fn(3.5);}intmain(){test(Base());test(Sub());}子类重载父类成员函数不能传播“虚”性右边程序的运行结果说明了这一点:BaseBase16:15:0312函数重载对于编译识别来说,返回类型是不起作用的.voidfn(int);intfn(int);对于fn(2);无法判断应该调用哪个函数.而使调用遭遇编译失败.对于虚函数来说,声明:virtualBase*fn();virtualSub*fn();应看作不能分辨其多态调用的虚函数,但却引编译认为是同一个虚函数而获得通过.这是语言技术上的一种处理,事实上,如果一个多态函数正在处理Sub类的对象,则它仍可以通过返回的Sub对象指针,继续处理Sub对象,似乎更自然16:15:0313注意事项:多态性是通过成员函数捆绑不同类型的对象来体现的,所以,虚函数一定是成员函数,而且,静态成员函数都不行,因为它不捆绑对象,同样,构造函数也不行,因为它只产生对象,也不捆绑对象.可是,析构函数却可以是虚函数,事实上,鼓励类继承体系中的每个类最好其析构函数都是虚函数.一旦设置了虚函数,就与编译器达成了滞后联编的协议,函数必定分离于当前运行的模块,因而就不可能是内联函数了.16:15:03145.精简共性的类(SimplifyClasswithGenerality)孤立的类之间有一些共性,希望减少一些冗余代码.通常不能因此而构成上下级关系的类层次,因为会产生不良反应:基类如果扩展功能,势必连累子类.16:15:03156.多态编程(PolymorphicProgramming)将共性的类通过继承由共性构成的基类来实现或许会比较明智.因为两者的扩展彼此之间不会带来干扰.而且还可任意派生子类类的继承体系决定之后,其基类的多态成员函数也可确定.通常可以在子类之间进行比较,同名不同功能的函数往往可以置成虚函数.classAccount{protected://...public:virtualvoiddisplay()const;virtualvoidwithdrawal(doubleamount){}};16:15:0316多态编程中,会遇到各种子类混在一个集合中的情形,这正是多态大展身手的时候.针对左边的类体系,有多态的应用代码:vectorshape*a;//...for(inti=0;ia.size();++i)a[i]-area();classShape{public://...virtualvoidarea(){}};classCircle:publicShape{public:voidarea(){…}//...};classTriangle:publicShape{public:voidarea(){…}//...};//...16:15:03177.类型转换(TypeConversions)调用虚函数是一种多态的方法,它是将运行中无法知道的对象交给系统去自动辨认类型从而作出准确的操作使用动态转型策略:另一种实现是在运行中,判断对象的类型,从而可以准确的捆绑调用确定的成员函数,这是一种主动辨认对象类型的编程策略16:15:0318辨认对象的类型,首先应该知道其属于哪个类系.然后确定要判断的子类名称.再行编码.例如,针对每个对象进行操作:Savings类对象,余额增加以1%计算的利息;Checking类对象,余额增加以0.05%计算的利息vectorAccount*a;//...Checking*pC;Savings*pS;for(inti=0;ia.size();++i){if(pC=dynamic_castChecking*(a[i]))pC-deposit(pC-getBalan()*0.05);elseif(pS=dynamic_castSavings*(&a[i]))pS-deposit(pS-getBalan()*0.1);a[i]-display();}动态转型16:15:0319动态转型只限于多态类,而静态转型适合更一般的类对象.例如,没有多态性的学生与研究生之间的转换:Student*ps=static_castStudent*(&gs);例如:无类型指针到某类型指针的转换:voidfn(void*dd){Student*pp=static_castStudent*(&dd);//...}静态转型16:15:0320许多标准类库函数的声明为了适应大多数编程场合,做了完美的设计.例如,max:constchar*max(constchar*s1,constchar*s2){returnstrcmp(s1,s2)0?s1:s2;}但是,编程总是调用标准库函数和自处理交替进行的,当返回的对象要进行写操作时,由于类型的限制,便不能再正确编码下去了.然而,事实上,从一开始,对象完全是可修改的,只是由于函数调用的结果,而使对象性质被迫发生了变化.为了将这种本质上可以修改的对象回复可修改的状态,使用常量转型.intfn(){char*p=max(hello,world);//errorchar*p=const_castchar*(max(“hello”,”world”));//ok//…}常量转型
本文标题:钱能C++程序设计教程12
链接地址:https://www.777doc.com/doc-4036621 .html