荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Second (石开), 信区: Program
标  题: C++语言常见问题解答(2-12)继承ABC
发信站: 荔园晨风BBS站 (Sun Sep 23 10:50:57 2001), 转信

==========================
● 12A:继承--虚拟函数
==========================

Q56:什麽是「虚拟成员函数」?

虚拟函数可让衍生的类别「取代」原基底类别所提供的运作。只要某物件是衍生出来

的,就算我们是透过基底物件的指标,而不是以衍生物件的指标来存取该物件,编译

器仍会确保「取代後」的成员函数被呼叫。这可让基底类别的演算法被衍生者所替换

,即使我们不知道衍生类别长什麽样子。

注意:衍生的类别亦可“部份”取代(覆盖,override)掉基底的运作行为(如有必

要,衍生类别的运作行为亦可呼叫它的基底类别版本)。

========================================

Q57:C++ 怎样同时做到动态系结和静态型别?

底下的讨论中,"ptr" 指的是「指标」或「参考」。

一个 ptr 有两种型态:静态的 ptr 型态,与动态的「被指向的物件」的型态(该

件可能实际上是个由其他类别衍生出来的类别的 ptr)。

「静态型别」("static typing") 是指:该呼叫的「合法性」,是以 ptr 的静态型

别为侦测之依据,如果 ptr 的型别能处理成员函数,则「指向的物件」自然也能。


「动态系结」("dynamic binding") 是指:「程式码」呼叫是以「被指向的物件」之

型态为依据。被称为「动态系结」,是因为真正会被呼叫的程式码是动态地(於执行

时期)决定的。

========================================

Q58:衍生类别能否将基底类别的非虚拟函数覆盖(override)过去?

可以,但不好。

C++ 的老手有时会重新定义非虚拟的函数,以提升效率(换一种可能会运用到衍生类

别才有的资源的作法),或是用以避开遮蔽效应(hiding rule,底下会提,或是看

看 ARM ["Annotated Reference Manual"] sect.13.1),但是用户的可见性效果

须完全相同,因为非虚拟的函数是以指标/参考的静态型别为分派(dispatch)的依

据,而非以指到的/被参考到的物件之动态型别来决定。

========================================

Q59:"Warning: Derived::f(int) hides Base::f(float)" 是什麽意思?

这是指:你死不了的。

你出的问题是:如果 Derived 宣告了个叫做 "f" 的成员函数,Base 却早已宣告

个不同型态签名型式(譬如:参数型态或是 const 不同)的 "f",这样子 Base
"f"
就会被「遮蔽 hide」住,而不是被「多载 overload」或「覆盖 override」(即使

Base "f" 已经是虚拟的了)。

解决法:Derived 要替 Base 被遮蔽的成员函数重新定义(就算它不是虚拟的)。通

常重定义的函数,仅仅是去呼叫合适的 Base 成员函数,譬如:

        class Base {
        public:
          void f(int);
        };

        class Derived : public Base {
        public:
          void f(double);
          void f(int i) { Base::f(i); }
        };             // ^^^^^^^^^^--- 重定义的函数只
是去呼叫 Base::f(int)

========================
● 12B:继承--一致性
========================

Q60:我该遮蔽住由基底类别继承来的公共成员函数吗?

绝对绝对绝对绝对不要这样做!

想去遮蔽(删去、撤消)掉继承下来的公共成员函数,是个很常见的错误。这通常是

脑袋塞满了浆糊的人才会做的傻事。

========================================

Q61:圆形 "Circle" 是一种椭圆 "Ellipse" 吗?

若椭圆能够不对称地改变其两轴的大小,则答案就是否定的。

比方说,椭圆有个 "setSize(x,y)" 的运作行为,且它保证说「椭圆的 width() 为

x,height() 为 y」。这种情况之下,正圆形就不能算是一种椭圆。因为只要把某个

椭圆能做而正圆形不能的东西放进去,圆形就不再是个椭圆了。

这样一来,圆和椭圆之间可能有两种的(合法)关系:
 * 将圆与椭圆完全分开来谈。
 * 让圆及椭圆都同时自一个基底衍生出来,该基底为「不能做不对称的 setSize

   运作的特殊椭圆形」。

以第一个方案而言,椭圆可继承自「非对称图形」(伴随著一个 setSize(x,y) ),

圆形则继承自「对称图形」,带有一个 setSize(size) 成员函数。

第二个方案中,可让卵形 "Oval" 类别有个 "setSize(size)":将 "width()" 和

"height()" 都设成 "size",然後让椭圆和圆形都自卵形中衍生出来。椭圆(而不是

正圆形)会加入一个 "setSize(x,y)" 运算(如果这个 "setSize()" 运作行为的名

称重复了,就得注意前面提过的「遮蔽效应」)。

========================================

Q62:对「圆形是/不是一种椭圆」这两难问题,有没有其他说法?

如果你说:椭圆都可以不对称地挤压,又说:圆形是一种椭圆,又说:圆形不能不对

称地挤压下去,那麽很明显的,你说过的某句话要做修正(老实说,该取消掉)。所

以你不是得去掉 "Ellipse::setSize(x,y)",去掉圆形和椭圆间的继承关系,就是得

承认你的「圆形」不一定是正圆。

这儿有两个 OO/C++ 新手最易落入的陷阱。他们想用程式小技巧来弥补差劲的事前设

计(他们重新定义 Circle::setSize(x,y),让它丢出一个例外,呼叫 "abort()" ,

或是选用两参数的平均数,或是不做任何事情),不幸的,这些技俩都会让使用者感

到吃惊:他们原本都预期 "width() == x" 和 "height() == y" 这两个事实会
成立。

唯一合理的做法似乎是:降低椭圆形 "setSize(x,y)" 的保证事项(譬如,你可以改

成:「这运作行为“可能”会把 width() 设成 x、height() 设成 y,也可能“不

任何事”」)。不幸的,这样会把界限冲淡,因为使用者没有任何有意义的物件行为

足以依靠,整个类别阶层也就无毫价值可言了(很难说服别人去用一个:问你说它是

做什麽的,你却只会耸耸肩膀说不知道的物件)。

==========================
● 12C:继承--存取规则
==========================

Q63:为什麽衍生的类别无法存取基底的 "private" 东西?

让你不被基底类别将来的改变所影响。

衍生类别不能存取到基底的私有(private)成员,它有效地把衍生类别「封住」,
基底类别内的私有成员如有改变,也不会影响到衍生的类别。

========================================

Q64:"public:"、"private:"、"protected:" 的差别是?

"Private:" 在前几节中讨论过了;"public:" 是指:「任何人都能存取之」;第三

个 "protected:" 是让某成员(资料成员或是成员函数)只能由衍生类别存取之。


【译注】"protected:" 是让「衍生类别」,而非让「衍生类别的物件案例」能存取

        得到 protected 的部份。

========================================

Q65:当我改变了内部的东西,怎样避免子类别被破坏?

物件类别有两个不同的介面,提供给不同种类的用户:
 * "public:" 介面用以服务不相关的类别。
 * "protected:" 介面用以服务衍生的类别。

除非你预期所有的子类别都会由你们的工作小组建出来,否则你应该将基底类别的资

料位元内容放在 "private:" 处,用 "protected:" 行内存取函数来存取那些资料

这样的话,即使基底类别的私有资料改变了,衍生类别的程式也不会报废,除非你改

变了基底类别的 protected 处的存取函数。

--
                            既然热爱生命
                            那么,
                            一切都在意料之中。

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.52.239]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店