接口实现和继承实现

接口实现和继承实现

ID:13555992

大小:28.05 KB

页数:13页

时间:2018-07-23

上传者:U-2462
接口实现和继承实现_第1页
接口实现和继承实现_第2页
接口实现和继承实现_第3页
接口实现和继承实现_第4页
接口实现和继承实现_第5页
资源描述:

《接口实现和继承实现》由会员上传分享,免费在线阅读,更多相关内容在行业资料-天天文库

[C++]接口继承与实现继承 接口继承与实现继承所谓接口继承,就是派生类只继承函数的接口,也就是声明;而实现继承,就是派生类同时继承函数的接口和实现。我们都很清楚C++中有几个基本的概念,虚函数、纯虚函数、非虚函数。虚函数:虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。——MSDN虚函数用来表现基类和派生类的成员函数之间的一种关系. 虚函数的定义在基类中进行,在需要定义为虚函数的成员函数的声明前冠以关键字 virtual. 基类中的某个成员函数被声明为虚函数后,此虚函数就可以在一个或多个派生类中被重新定义. 在派生类中重新定义时,其函数原型,包括返回类型,函数名,参数个数,参数类型及参数的先后顺序,都必须与基类中的原型完全相同. 虚函数是重载的一种表现形式,是一种动态的重载方式.纯虚函数:纯虚函数在基类中没有定义,它们被初始化为0。 任何用纯虚函数派生的类,都要自己提供该函数的具体实现。 定义纯虚函数 virtualvoidfun(void)=0;非虚函数:一般成员函数,无virtual关键字修饰。 至于为什么要定义这些函数,我们可以将虚函数、纯虚函数和非虚函数的功能与接口继承与实现继承联系起来:声明一个纯虚函数(purevirtual)的目的是为了让派生类只继承函数接口,也就是上面说的接口继承。纯虚函数一般是在不方便具体实现此函数的情况下使用。也就是说基类无法为继承类规定一个统一的缺省操作,但继承类又必须含有这个函数接口,并对其分别实现。但是,在C++中,我们是可以为纯虚函数提供定义的,只不过这种定义对继承类来说没有特定的意义。因为继承类仍然要根据各自需要实现函数。 通俗说,纯虚函数就是要求其继承类必须含有该函数接口,并对其进行实现。是对继承类的一种接口实现要求,但并不提供缺省操作,各个继承类必须分别实现自己的操作。声明非纯虚函数(impurevirtual)的目的是让继承类继承该函数的接口和缺省实现。与纯虚函数唯一的不同就是其为继承类提供了缺省操作,继承类可以不实现自己的操作而采用基类提供的默认操作。声明非虚函数(non-virtual)的目的是为了令继承类继承函数接口及一份强制性实现。相对于虚函数来说,非虚函数对继承类要求的更为严格,继承类不仅要继承函数接口,而且也要继承函数实现。也就是为继承类定义了一种行为。 总结:纯虚函数:要求继承类必须含有某个接口,并对接口函数实现。虚函数:继承类必须含有某个接口,可以自己实现,也可以不实现,而采用基类定义的缺省实现。非虚函数:继承类必须含有某个接口,必须使用基类的实现。 C++箴言:接口继承和实现继承 (public)inheritance这个表面上简单易懂的观念,一旦被近距离审视,就会被证明是由两个相互独立的部分组成的:inheritanceoffunctioninterfaces(函数接口的继承)和inheritanceoffunctionimplementations(函数实现的继承)。这两种inheritance之间的差异正好符合本书Introduction中论述的functiondeclarations(函数声明)和functiondefinitions(函数定义)之间的差异。   作为一个class的设计者,有的时候你想要derivedclasses只继承一个memberfunction的interface(declaration)。有的时候你想要derivedclasses既继承interface(接口)也继承implementation(实现),但你要允许它们替换他们继承到的implementation。还有的时候你想要derivedclasses继承一个函数的interface(接口)和implementation(实现),而不允许它们替换任何东西。   为了更好地感觉这些选择之间的不同之处,考虑一个在图形应用程序中表示几何图形的classhierarchy(类继承体系):   classShape{   public:   virtualvoiddraw()const=0;   virtualvoiderror(conststd::string&msg);   intobjectID()const;   ...   };   classRectangle:publicShape{...};   classEllipse:publicShape{...};   Shape是一个abstractclass(抽象类),它的purevirtualfunction(纯虚拟函数)表明了这一点。作为结果,客户不能创建Shapeclass的实例,只能创建从它继承的classes的实例。但是,Shape对所有从它(公有)继承的类施加了非常强大的影响,因为    成员函数interfacesarealwaysinherited。就像Item32解释的,publicinheritance意味着is-a,所以对一个baseclass来说成立的任何东西,对于它的derivedclasses也必须成立。因此,如果一个函数适用于一个class,它也一定适用于它的derivedclasses。   Shapeclass中声明了三个函数。第一个,draw,在一个明确的显示设备上画出当前对象。第二个,error,如果memberfunctions需要报告一个错误,就调用它。第三个,objectID,返回当前对象的唯一整型标识符。每一个函数都用不同的方式声明:draw是一个purevirtualfunction(纯虚拟函数);error是一个simple(impure?)virtualfunction(简单虚拟函数);而objectID是一个non-virtualfunction(非虚拟函数)。这些不同的声明暗示了什么呢?   考虑第一个purevirtualfunction(纯虚拟函数)draw:   classShape{   public:   virtualvoiddraw()const=0;   ...   };   purevirtualfunctions(纯虚拟函数)的两个最显著的特性是它们必须被任何继承它们的具体类重新声明,和抽象类中一般没有它们的定义。把这两个特性加在一起,你应该认识到。   声明一个purevirtualfunction(纯虚拟函数)的目的是使derivedclasses继承一个函数interfaceonly。   这就使Shape::drawfunction具有了完整的意义,因为它要求所有的Shape对象必须能够画出来是合情合理的,但是Shapeclass本身不能为这个函数提供一个合乎情理的缺省的实现。例如,画一个椭圆的算法和画一个矩形的算法是非常不同的,Shape::draw的声明告诉具体derivedclasses的设计者:“你必须提供一个drawfunction,但是我对于你如何实现它不发表意见。”   顺便提一句,为一个purevirtualfunction(纯虚拟函数)提供一个定义是有可能的。也就是说,你可以为Shape::draw提供一个实现,而C++也不会抱怨什么,但是调用它 的唯一方法是用classname限定修饰这个调用:   Shape*ps=newShape;//error!Shapeisabstract   Shape*ps1=newRectangle;//fine   ps1->draw();//callsRectangle::draw   Shape*ps2=newEllipse;//fine   ps2->draw();//callsEllipse::draw   ps1->Shape::draw();//callsShape::draw   ps2->Shape::draw();//callsShape::draw   除了帮助你在鸡尾酒会上给同行程序员留下印象外,这个特性通常没什么用处,然而,就像下面你将看到的,它能用来作为一个“为simple(impure)virtualfunctions提供一个safer-than-usual的实现”的机制。   simplevirtualfunctions背后的故事和purevirtuals有一点不同。derivedclasses照常还是继承函数的interface,但是simplevirtualfunctions提供了一个可以被derivedclasses替换的实现。如果你为此考虑一阵儿,你就会认识到   声明一个simplevirtualfunction的目的是让derivedclasses继承一个函数interfaceaswellasadefaultimplementation。   考虑Shape::error的情况:   classShape{   public:   virtualvoiderror(conststd::string&msg);   ...   };   interface要求每一个class必须支持一个在遭遇到错误时被调用的函数,但是每一个class可以自由地用它觉得合适的任何方法处理错误。如果一个class不需要做什么特别 的事情,它可以仅仅求助于Shapeclass中提供的错误处理的缺省版本。也就是说,Shape::error的声明告诉derivedclasses的设计者:“你应该支持一个errorfunction,但如果你不想自己写,你可以求助Shapeclass中的缺省版本。”   结果是:允许simplevirtualfunctions既指定一个函数接口又指定一个缺省实现是危险的。来看一下为什么,考虑一个XYZ航空公司的飞机的hierarchy(继承体系)。XYZ只有两种飞机,ModelA和ModelB,它们都严格地按照同样的方法飞行。于是,XYZ设计如下hierarchy(继承体系):   classAirport{...};//representsairports   classAirplane{   public:   virtualvoidfly(constAirport&destination);   ...   };   voidAirplane::fly(constAirport&destination)   {   defaultcodeforflyinganairplanetothegivendestination   }   classModelA:publicAirplane{...};   classModelB:publicAirplane{...};   为了表述所有的飞机必须支持一个fly函数,并为了“不同机型可能(在理论上)需要不同的对fly的实现”的事实,Airplane::fly被声明为virtual。然而,为了避免在ModelA和ModelBclasses中些重复的代码,缺省的飞行行为由Airplane::fly的函数体提供,供ModelA和ModelB继承。   这是一个经典的object-oriented设计。因为两个classes共享一个通用特性(它们实现fly的方法),所以这个通用特性就被转移到一个baseclass之中,并由两个classes 来继承这个特性。这个设计使得通用特性变得清楚明白,避免了代码重复,提升了未来的可扩展性,简化了长期的维护——因为object-oriented技术,所有这些东西都受到很高的追捧。XYZ航空公司应该引以为荣。   现在,假设XYZ公司的财富增长了,决定引进一种新机型,ModelC。ModelC在某些方面与ModelA和ModelB不同。特别是,它的飞行不同。   XYZ公司的程序员在hierarchy(继承体系)中增加了ModelC的class,但是由于他们匆匆忙忙地让新的机型投入服务,他们忘记了重定义flyfunction:   classModelC:publicAirplane{   ...//noflyfunctionisdeclared   };   于是,在他们的代码中,就出现了类似这样的东西:   AirportPDX(...);//PDXistheairportnearmyhome   Airplane*pa=newModelC;   ...   pa->fly(PDX);//callsAirplane::fly!   这是一个灾难:企图让一个ModelCobject像一个ModelA或ModelB一样飞行。这在旅行人群中可不是一种鼓舞人心的行为。  这里的问题并不在于Airplane::fly有缺省的行为,而是在于ModelC被允许不必明确说出它要做什么就可以继承这一行为。幸运的是,“为derivedclasses(派生类)提供缺省的行为,但是除非它们提出明确的要求,否则就不交给他们”是很容易做到的。这个诀窍就是切断virtualfunction(虚拟函数)的interface(接口)和它的defaultimplementation(缺省实现)之间的联系。以下用的就是这个方法:   classAirplane{    public:   virtualvoidfly(constAirport&destination)=0;   ...   protected:   voiddefaultFly(constAirport&destination);   };   voidAirplane::defaultFly(constAirport&destination)   {   defaultcodeforflyinganairplanetothegivendestination   }   注意Airplane::fly是被如何变成一个purevirtualfunction(纯虚拟函数)的。它为飞行提供了interface(接口)。那个缺省的实现也会出现在Airplaneclass中,但是现在它是一个独立的函数,defaultFly。像ModelA和ModelB这样需要使用缺省行为的Classes只是需要在他们的fly的函数体中做一下对defaultFly的inline调用(但是请参见Item30提供的关于inline化和virtualfunctions(虚拟函数)的交互作用的信息):   classModelA:publicAirplane{   public:   virtualvoidfly(constAirport&destination)   {defaultFly(destination);}   ...   };   classModelB:publicAirplane{   public:   virtualvoidfly(constAirport&destination)   {defaultFly(destination);}   ...   };    对于ModelCclass,不可能在无意中继承到不正确的fly的实现,因为Airplane中的purevirtual(纯虚拟)强制要求ModelC提供的它自己的fly版本。   classModelC:publicAirplane{   public:   virtualvoidfly(constAirport&destination);   ...   };   voidModelC::fly(constAirport&destination)   {   codeforflyingaModelCairplanetothegivendestination   }   这一方案并非十分安全(程序员还是能通过copy-and-paste使他们自己陷入麻烦),但是它比最初的设计更加可靠。至于Airplane::defaultFly,它是protected(保护的)是因为它完全是Airplane和它的derivedclasses(派生类)的实现细节。使用飞机的客户应该只在意它能飞,而不必管飞行是如何实现的。   Airplane::defaultFly是一个non-virtualfunction(非虚拟函数)这一点也很重要。这是因为derivedclass(派生类)不应该重定义这个函数,这是一个在Item36中专门介绍的原则。如果defaultFly是virtual(虚拟的),你就会遇到一个循环的问题:如果某些derivedclass(派生类)应该重定义defaultFly却忘记了的时候会如何呢?   一些人反对为interface(接口)和defaultimplementation(缺省实现)分别提供函数,就像上面的fly和defaultFly那样。首先,他们注意到,这样做会导致类似的相关函数名污染classnamespace(类名字空间)的问题。然而他们仍然同意interface(接口)和defaultimplementation(缺省实现)应该被分开。他们是怎样解决这个表面上的矛盾呢?通过利用以下事实:purevirtualfunctions(纯虚拟函数)必须在concretederivedclasses(具体派生类)中被redeclared(重声明),但是它们也可以有它们自己的实现。以下就是Airplanehierarchy(继承体系)如何利用这一能力定义一个purevirtualfunction(纯虚拟函数):    classAirplane{   public:   virtualvoidfly(constAirport&destination)=0;   ...   };   voidAirplane::fly(constAirport&destination)//animplementationof   {//apurevirtualfunction   defaultcodeforflyinganairplaneto   thegivendestination   }   classModelA:publicAirplane{   public:   virtualvoidfly(constAirport&destination)   {Airplane::fly(destination);}   ...   };   classModelB:publicAirplane{   public:   virtualvoidfly(constAirport&destination)   {Airplane::fly(destination);}   ...   };   classModelC:publicAirplane{   public:   virtualvoidfly(constAirport&destination);    ...   };   voidModelC::fly(constAirport&destination)   {   codeforflyingaModelCairplanetothegivendestination   }   除了用purevirtualfunction(纯虚拟函数)Airplane::fly的函数体代替了独立函数Airplane::defaultFly之外,这是一个和前面的几乎完全相同的设计。本质上,fly可以被拆成两个基本组件。它的declaration(声明)指定了它的interface(接口)(这是derivedclasses(派生类)必须使用的),而它的definition(定义)指定它的缺省行为(这是derivedclasses(派生类)可以使用的,但只是在他们明确要求这一点时)。将fly和defaultFly合并,无论如何,你失去了给予这两个函数不同的保护层次的能力:原来是protected的代码(通过位于defaultFly中实现)现在成为public(因为它位于fly中)。   最后,我们看看Shape的non-virtualfunction(非虚拟函数),objectID:   classShape{   public:   intobjectID()const;   ...   };   当一个memberfunction(成员函数)是non-virtual(非虚拟的)时,不应该指望它在derivedclasses(派生类)中的行为会有所不同。实际上,一个non-virtualmemberfunction(非虚拟成员函数)指定了一个invariantoverspecialization(超越特殊化的不变量),因为不论一个derivedclass(派生类)变得多么特殊,它都把它看作是不允许变化的行为。如下所指除的,   声明一个non-virtualfunction(非虚拟函数)的目的是tohavederivedclassesinheritafunctioninterfaceaswellasamandatoryimplementation(使派生类既继承一个函数的接口,又继承一个强制的实现)。    你可以这样考虑Shape::objectID的声明,“每一个Shapeobject有一个产生objectidentifier(对象标识码),而且这个objectidentifier(对象标识码)总是用同样的方法计算出来的,这个方法是由Shape::objectID的定义决定的,而且derivedclass(派生类)不应该试图改变它的做法。”因为一个non-virtualfunction(非虚拟函数)被看作一个invariantoverspecialization(超越特殊化的不变量),在derivedclass(派生类)中他绝不应该被重定义,细节的讨论参见Item36。   对purevirtual,simplevirtual,和non-virtualfunctions的声明的不同允许你精确指定你需要derivedclasses(派生类)继承什么东西。分别是interfaceonly(仅有接口),interfaceandadefaultimplementation(接口和一个缺省的实现),和interfaceandamandatoryimplementation(接口和一个强制的实现)。因为这些不同的声明类型意味着根本不同的意义,当你声明你的memberfunctions(成员函数)时你必须在它们之间仔细地选择。如果你这样做了,你应该可以避免由缺乏经验的类设计者造成的两个最常见的错误。   第一个错误是声明所有的函数为non-virtual(非虚拟)。这没有给derivedclasses(派生类)的特殊化留出空间;non-virtualdestructors(非虚拟析构函数)尤其有问题(参见Item7)。当然,完全有理由设计一个不作为baseclass(基类)使用的类。在这种情况下,一套独享的non-virtualmemberfunctions(非虚拟成员函数)是完全合理的。然而,更通常的情况下,这样的类既可能出于对virtual(虚拟)和non-virtualfunctions(非虚拟函数)之间区别的无知,也可能是对virtualfunctions(虚拟函数)的性能成本毫无根据的担心的结果。事实是,几乎任何作为baseclass(基类)使用的类都会有virtualfunctions(虚拟函数)(还是参见Item7)。   如果你关心virtualfunctions(虚拟函数)的成本,请允许我提起基于经验的80-20规则(参见Item30),在一个典型的程序中的情况是,80%的运行时间花费在执行其中的20%的代码上。这个规则是很重要的,因为它意味着,平均下来,你的函数调用中的80%可以被虚拟化而不会对你的程序的整体性能产生哪怕是最轻微的可察觉的影响。在你走进对“你是否能负担得起一个virtualfunction(虚拟函数)的成本”忧虑的阴影之前,应该使用一些简单的预防措施,以确保你关注的是你的程序中能产生决定性不同的那20%。   另一个常见的错误声明所有的memberfunctions(成员函数)为virtual(虚拟)。有时候这样做是正确的——Item31的Interfaceclasses(接口类)可以作为证据。然而,它也可能是缺乏表明态度的决心的类设计者的标志。某些函数在derivedclasses(派生类)中不应该被重定义,而且只要在这种情况下,你都应该通过将那些函数声明为non-virtual (非虚拟)而明确地表达这一点。它不是为那些人服务的,他们假设如果他们只需花一些时间重定义你的所有函数,你的类就会被所有的人用来做所有的事情,如果你有一个invariantoverspecialization(超越特殊化的不变量),请直说,不必害怕!   ThingstoRemember   ·Inheritanceofinterface(接口继承)与inheritanceofimplementation(实现继承)不同。在publicinheritance(公开继承)下,derivedclasses(派生类)总是继承baseclassinterfaces(基类接口)。   ·Purevirtualfunctions(纯虚拟函数)指定inheritanceofinterfaceonly(仅有接口被继承)。   ·Simple(impure)virtualfunctions(简单虚拟函数)指定inheritanceofinterface(接口继承)加上inheritanceofadefaultimplementation(缺省实现继承)。   ·Non-virtualfunctions(非虚拟函数)指定inheritanceofinterface(接口继承)加上inheritanceofamandatoryimplementation(强制实现继承)。  

当前文档最多预览五页,下载文档查看全文

此文档下载收益归作者所有

当前文档最多预览五页,下载文档查看全文
温馨提示:
1. 部分包含数学公式或PPT动画的文件,查看预览时可能会显示错乱或异常,文件下载后无此问题,请放心下载。
2. 本文档由用户上传,版权归属用户,天天文库负责整理代发布。如果您对本文档版权有争议请及时联系客服。
3. 下载前请仔细阅读文档内容,确认文档内容符合您的需求后进行下载,若出现内容与标题不符可向本站投诉处理。
4. 下载文档时可能由于网络波动等原因无法下载或下载错误,付费完成后未能成功下载的用户请联系客服处理。
关闭