⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 [21] inheritance proper inheritance and substitutability, c++ faq lite.htm

📁 c++faq。里面有很多关于c++的问题的解答。
💻 HTM
📖 第 1 页 / 共 2 页
字号:
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/virtual-functions.html">Previous&nbsp;section</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/abcs.html">Next&nbsp;section</A> 
]</SMALL> 
<HR>

<P><A name=[21.5]></A>
<DIV class=FaqTitle>
<H3>[21.5] 
派生类数组(array-of-<TT>Derived)</TT>“不是一种”基类数组(array-of-<TT>Base)</TT>是否意味着数组不好?<IMG 
alt=UPDATED! 
src="[21] Inheritance proper inheritance and substitutability, C++ FAQ Lite.files/updated.gif"></H3></DIV><SMALL><EM>[Recently 
changed so it uses new-style headers and the <TT>std::</TT> syntax and reworded 
references to STL (on 7/00). <A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#[21.9]">Click 
here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[21.9]:rawtext--></A>.]</EM></SMALL> 
<P>是的,<A 
href="http://www.sunistudio.com/cppfaq/containers-and-templates.html#[31.1]">数组很差劲</A>。(开个玩笑)。
<P>真诚的来说,数组和指针非常接近,并且指针很难处理。但是如果我们完全掌握了为什么从设计角度来看,以上FAQ所说的会是一个问题(例如,如果你真的知道为什么事物的容器不是一种任何事物的容器),并且你认为将维护你的代码的其他人都完全掌握这些OO的设计事实的话,那么你可以自由使用数组。但是如果你象大多数人一样的话,你应该使用诸如<A 
href="http://www.sunistudio.com/cppfaq/class-libraries.html#[32.1]">标准库</A>的<TT>std::vector&lt;T&gt;</TT>这样的模板容器类而不是原始的数组。 

<P>(注意:&nbsp;本&nbsp;FAQ&nbsp;的论述仅与公有继承(<TT>public</TT> inheritance)有关;&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/private-inheritance.html">私有和保护继承</A>并不相同) 

<P><SMALL>[&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#top">Top</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#bottom">Bottom</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/virtual-functions.html">Previous&nbsp;section</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/abcs.html">Next&nbsp;section</A> 
]</SMALL> 
<HR>

<P><A name=[21.6]></A>
<DIV class=FaqTitle>
<H3>[21.6] <TT>Circle</TT>(圆)是一种 <TT>Ellipse</TT>(椭圆)吗?</H3></DIV>
<P>如果椭圆允许改变圆率,则不是。
<P>例如,假设椭圆有一个<TT>setSize(x,y)</TT>成员函数,并且这个成员函数允许椭圆的&nbsp;<TT>width()</TT>是<TT>x</TT>,<TT>height()</TT>&nbsp;是<TT>y</TT>。在这种情况下,圆无法是一种椭圆。很简单,如果椭圆能做某些圆不能做的事,则圆不是一种椭圆。 

<P>据此推出圆和椭圆的两种(合法的)关系: 
<UL>
  <LI>使圆类和椭圆类完全无关 
  <LI>使圆和椭圆都从一个基类派生,该基类是“不能执行不对称<TT>setSize()</TT>运算的椭圆” </LI></UL>
<P>在第一种情况下,椭圆可以从<TT>AsymmetricShape</TT>(不对称图形)类派生,<TT>setSize(x,y)</TT>可以在<TT>AsymmetricShape</TT>类中声明。而圆可以从有<TT>setSize(size)</TT>成员函数的<TT>SymmetricShape</TT>(对称图形)类派生。
<P>在第二种情况下,<TT>Oval</TT>(卵形)类可以只有<TT>setSize(size)</TT>来同时设置 
<TT>width()</TT>和<TT>height()</TT>的大小。椭圆和圆都继承自<TT>Oval</TT>。椭圆(但不是圆)可以增加<TT>setSize(x,y)</TT>运算(但如果<TT>setSize()</TT>成员函数名称重复,当心<A 
href="http://www.sunistudio.com/cppfaq/strange-inheritance.html#[23.5]">隐藏规则</A>)<BR>
<P>(注意:&nbsp;本&nbsp;FAQ&nbsp;的论述仅与公有继承(<TT>public</TT> inheritance)有关;&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/private-inheritance.html">私有和保护继承</A>并不相同)
<P>(注意:&nbsp;<TT>setSize(x,y)</TT>并不是神圣的。依赖于你的目标,防止用户改变椭圆的尺寸也是可以的。在某些情况下,椭圆没有<TT>setSize(x,y)</TT>方法是有效的设计选择。然而这个系列的讨论是当你想为一个已存在的类建立一个派生类并且基类含有一个“无法接受”的方法时,该如何做。当然理想情形是在基类不存在时就发现这个问题。但生活并不总是理想的……) 

<P><SMALL>[&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#top">Top</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#bottom">Bottom</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/virtual-functions.html">Previous&nbsp;section</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/abcs.html">Next&nbsp;section</A> 
]</SMALL> 
<HR>

<P><A name=[21.7]></A>
<DIV class=FaqTitle>
<H3>[21.7] 对于“圆是/不是一种椭圆”这个两难问题,有其它说法吗?</H3></DIV>
<P>如果你主张所有椭圆是可以被压成不对称的,并且你主张圆是一种椭圆,并且你主张圆不能被压成不对称的。无疑你必须调整(实际上是撤回)你的主张之一。由此,你要么去掉<TT>Ellipse::setSize(x,y)</TT>,去掉圆和椭圆的继承关系,要么承认你的 
<TT>Circle</TT>s(圆)不必是正圆。
<P>这里有两个OO/C++编程新手通常会陷入的陷阱。他们会试图用代码的技巧来弥补设计的缺陷(他们会重定义<TT>Circle::setSize(x,y)</TT>来抛出异常,调用<TT>abort()</TT>,取两个参数的平均数,或者什么都不做)。不幸的是,由于用户期望&nbsp;<TT>width()&nbsp;==&nbsp;x</TT>并且&nbsp;<TT>height()&nbsp;==&nbsp;y</TT>,所以这些技巧会使用户惊讶。而让用户惊讶是不允许的。
<P>如果保持“圆是一种椭圆”的继承关系对你来说非常重要,那么你只能削弱椭圆的<TT>setSize(x,y)</TT>所做的承诺。例如,你可以改变承诺为,“该城圆函数可以把&nbsp;<TT>width()</TT>设置为<TT>x</TT><TT> 
</TT>并且/或把 
<TT>height()</TT>设置为<TT>y</TT>,或不做什么事情”。不幸的是由于用户没有任何意义的行为可以倚靠,这样会冲淡契约。因此整个层次都变得没有价值(如果某人问你到对象能做什么,而你只能耸耸肩膀的话,你很难说服他取使用这个对象) 

<P>(注意:&nbsp;本&nbsp;FAQ&nbsp;的论述仅与公有继承(<TT>public</TT> inheritance)有关;&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/private-inheritance.html">私有和保护继承</A>并不相同)
<P>(注意:&nbsp;<TT>setSize(x,y)</TT>并不是神圣的。依赖于你的目标,防止用户改变椭圆的尺寸也是可以的。在某些情况下,椭圆没有<TT>setSize(x,y)</TT>方法是有效的设计选择。然而这个系列的讨论是当你想为一个已存在的类建立一个派生类并且基类含有一个“无法接受”的方法时,该如何做。当然理想情形是在基类不存在时就发现这个问题。但生活并不总是理想的……) 

<P><SMALL>[&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#top">Top</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#bottom">Bottom</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/virtual-functions.html">Previous&nbsp;section</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/abcs.html">Next&nbsp;section</A> 
]</SMALL> 
<HR>

<P><A name=[21.8]></A>
<DIV class=FaqTitle>
<H3>[21.8] 
但我是数学博士,我相信圆是一种椭圆!这是否意味着Marshall&nbsp;Cline是傻瓜?或者C++是傻瓜?或者OO是傻瓜?</H3></DIV>
<P>事实上,这并不意味着这些。而是意味着你的直觉是错误的。
<P>看,我收到并回复了大量的关于这个主题的热情的e-mail。我已经给各地上千个软件专家讲授了数百次。我知道它违背了你的直觉。但相信我,你的直觉是错误的。<BR><BR>真正的问题是你的直觉中的“是一种(kind&nbsp;of)”的概念不符合OO中的彻底继承(学术上称为“子类型(subtyping)”)概念。派生类对象最起码必须是可以取代基类对象的。在圆/椭圆的情况下,<TT>setSize(x,y)</TT>成员函数违背了这个可置换性。<BR><BR>你有三个选择:[1]从<TT>Ellipse</TT>(椭圆)类中删除&nbsp;<TT>setSize(x,y)</TT>成员函数(从而废弃调用<TT>setSize(x,y)</TT>成员函数的已存在代码),[2]允许<TT>Circle</TT>(圆)的高和宽不同(一个不对称的圆),或者[3]去掉继承关系。抱歉,但没有其他选择。有人提过另一个选项,让圆和椭圆都从第三个通用基类派生,但这只不过是以上选项[3]的变种罢了。<BR><BR>换一种说法就是,你要么使基类弱一些(在这里就是说你不能为椭圆的高和宽设置不同的值),要么使派生类强一些(在这里就是使圆同时具有对称的和不对称的能力)。当这些都无法令人满意(就如圆/椭圆例子),通常就简单的消除继承关系。如果继承关系必须存在,你只能从基类中删除变形成员函数(<TT>setHeight(y)</TT>,<TT>setWidth(x),</TT>&nbsp;和<TT>setSize(x,y)</TT>)<BR>
<P>(注意:&nbsp;本&nbsp;FAQ&nbsp;的论述仅与公有继承(<TT>public</TT> inheritance)有关;&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/private-inheritance.html">私有和保护继承</A>并不相同)
<P>(注意:&nbsp;<TT>setSize(x,y)</TT>并不是神圣的。依赖于你的目标,防止用户改变椭圆的尺寸也是可以的。在某些情况下,椭圆没有<TT>setSize(x,y)</TT>方法是有效的设计选择。然而这个系列的讨论是当你想为一个已存在的类建立一个派生类并且基类含有一个“无法接受”的方法时,该如何做。当然理想情形是在基类不存在时就发现这个问题。但生活并不总是理想的……) 

<P><SMALL>[&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#top">Top</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#bottom">Bottom</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/virtual-functions.html">Previous&nbsp;section</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/abcs.html">Next&nbsp;section</A> 
]</SMALL> 
<HR>

<P><A name=[21.9]></A>
<DIV class=FaqTitle>
<H3>[21.9] 也许椭圆应该从圆继承?<IMG alt=NEW! 
src="[21] Inheritance proper inheritance and substitutability, C++ FAQ Lite.files/new.gif"></H3></DIV><SMALL><EM>[Recently 
created (on 7/00). <A 
href="http://www.sunistudio.com/cppfaq/abcs.html#[22.3]">Click here to go to the 
next FAQ in the "chain" of recent 
changes<!--rawtext:[22.3]:rawtext--></A>.]</EM></SMALL> 
<P>如果圆是基类,椭圆是派生类的话,那么你会面临许多新的问题。例如,假设圆有<TT>radius()</TT>方法(译注:设置半径的成员函数)。那么椭圆也会有<TT>radius()</TT>方法,但那没有意义:一个椭圆(可能不对称)的半径是什么意思?
<P>如果你克服这个障碍(也就是使得<TT>Ellipse::radius()</TT>返回主轴和辅轴的平均值或其它办法),那么<TT>radius()</TT>和 
<TT>area()</TT>(译注:得到面积的成员函数)之间的关联就会有问题。比如,假设圆有<TT>area()</TT>方法返回的是3.14159乘以<TT>radius()</TT>返回值的平方。而<TT>Ellipse::area()</TT>将不会返回椭圆的真实面积,否则你必须记住让<TT>radius()</TT>返回符合上述公式的某个值。
<P>即使你克服了这个问题(也就是使得<TT>Ellipse::radius()</TT>返回了椭圆的面积除以pi的平方根),你还要应付<TT>circumference()</TT>方法(译注:计算周长的成员函数)。比如,假设圆有<TT>circumference()</TT>方法返回2乘以pi乘以<TT>radius()</TT>的返回值。现在你的麻烦是:对于椭圆没有办法两碗水端平了:椭圆类不得不在面积,或者周长,或者两者的计算上撒谎。(译注:对于椭圆,面积和周长的计算无法同时得到正确答案,因为它们都使用了<TT>radius()</TT>的返回值,而它们对于<TT>radius()</TT>的返回值的要求却不相同,<TT>radius()</TT>无法同时满足它们的需要) 

<P>底线:只要派生类遵守基类的承诺,你就可以使用继承。而不能仅仅因为你感觉上象继承或仅仅因为你想使得代码被重用就使用继承。只有在(a)派生类的方法能遵守基类所做的所有承诺,并且(b)用户不会被你搞糊涂,并且(c)使用继承能明显获得实在的时间上的,金钱上的或风险上的改进时,才应该使用继承。 

<P><SMALL>[&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#top">Top</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#bottom">Bottom</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/virtual-functions.html">Previous&nbsp;section</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/abcs.html">Next&nbsp;section</A> 
]</SMALL> 
<HR>

<P><A name=[21.10]></A>
<DIV class=FaqTitle>
<H3>[21.10] 但我的问题与圆和椭圆无关,这种无聊的例子对我有什么好处?</H3></DIV>
<P>啊,有点小误会。你认为圆/椭圆例子是无聊的,但实际上,你的问题和它是同性质的。 
<P>我不在意你的继承问题是什么,但所有(是的,所有)不良的继承都可以归结为“圆不是一种椭圆”的例子。<BR><BR>这就是为什么:不良的继承总有一个有额外能力(经常是一个或两个额外的成员函数;有时是一个或多个成员函数给出的承诺)的基类,而派生类却无法满足它。你要么使基类弱一些,派生类强一些,要么消除继承关系。我见过很多很多很多不良的继承方案,相信我,它们都可以归结为圆/椭圆的例子。<BR><BR>因此,如果你真的理解了圆/椭圆的例子,你就能找出所有的不良继承。如果你没有理解圆/椭圆问题,那么你很可能犯一些严重的并且昂贵的继承错误。<BR><BR>令人忧伤,但是真的。<BR>
<P>(注意:&nbsp;本&nbsp;FAQ&nbsp;的论述仅与公有继承(<TT>public</TT> inheritance)有关;&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/private-inheritance.html">私有和保护继承</A>并不相同) 

<P><SMALL>[&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#top">Top</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#bottom">Bottom</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/virtual-functions.html">Previous&nbsp;section</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/abcs.html">Next&nbsp;section</A> 
]</SMALL> 
<HR>

<P><A name=bottom></A><A href="mailto:cline@parashift.com"><IMG height=26 
alt=E-Mail 
src="[21] Inheritance proper inheritance and substitutability, C++ FAQ Lite.files/mbox.gif" 
width=89>&nbsp;E-mail the author</A><BR>[&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/index.html"><EM>C++ FAQ Lite</EM></A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/index.html#table-of-contents">Table&nbsp;of&nbsp;contents</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/subject-index.html">Subject&nbsp;index</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/copy-permissions.html#[1.1]">About&nbsp;the&nbsp;author</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/copy-permissions.html#[1.2]">&copy;</A> 
|&nbsp;<A 
href="http://www.sunistudio.com/cppfaq/on-line-availability.html#[2.2]">Download&nbsp;your&nbsp;own&nbsp;copy</A>&nbsp;]<BR><SMALL>Revised 
Apr 8, 2001</SMALL> </P></BODY></HTML>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -