| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 6325 人关注过本帖
标题:推荐:C++语言常见问题解答
只看楼主 加入收藏
yuyunliuhen
Rank: 6Rank: 6
等 级:贵宾
威 望:20
帖 子:1435
专家分:0
注 册:2005-12-12
收藏
得分:0 
把衍生类别的指标转型成指向它的基底,可以吗? 
 
可以。 
 
衍生类别是该基底类别的特异化版本(衍生者「是一种」("a-kind-of") 基底)。这 
种向上的转换是绝对安全的,而且常常会发生(如果我指向一个汽车 Car,实际上我 
是指向一个车子 Vehicle): 
 
         void f(Vehicle* v); 
         void g(Car* c) { f(c); }        //绝对很安全;不需要转型 
 
注意:在这里我们假设的是 "public" 的继承;後面会再提到「另一种」"private/ 
protected" 的继承。

Go confidently in the  directions of your dreams,live the life you have imagined!Just do it!
It is no use learning without thinking!
2006-12-23 22:36
yuyunliuhen
Rank: 6Rank: 6
等 级:贵宾
威 望:20
帖 子:1435
专家分:0
注 册:2005-12-12
收藏
得分:0 
Derived* --> Base* 是正常的;那为什麽 Derived** --> Base** 则否? 
 
C++ 让 Derived* 能转型到 Base*,是因为衍生的物件「是一种」基底的物件。然而 
想由 Derived** 转型到 Base** 则是错误的!要是能够的话,Base** 就可能会被解 
参用(产生一个 Base*),该 Base* 就可能指向另一个“不一样的”衍生类别,这 
是不对的。 
 
照此看来,衍生类别的阵列就「不是一种」基底类别的阵列。在 Paradigm Shift 公 
司的 C++ 训练课程里,我们用底下的例子来比喻: 
 
                "一袋苹果「不是」一袋水果". 
                "A bag of apples is NOT a bag of fruit". 
 
如果一袋苹果可以当成一袋水果来传递,别人就可能把香蕉放到苹果袋里头去! 

Go confidently in the  directions of your dreams,live the life you have imagined!Just do it!
It is no use learning without thinking!
2006-12-23 22:36
yuyunliuhen
Rank: 6Rank: 6
等 级:贵宾
威 望:20
帖 子:1435
专家分:0
注 册:2005-12-12
收藏
得分:0 
衍生类别的阵列「不是」基底的阵列,是否表示阵列不好? 
 
没错,「阵列很烂」(开玩笑的 :-) 。 
 
C++ 内建的阵列有一个不易察觉的问题。想一想: 
 
         void f(Base* arrayOfBase) 
         { 
           arrayOfBase[3].memberfn(); 
         } 
 
         main() 
         { 
           Derived arrayOfDerived[10]; 
           f(arrayOfDerived); 
         } 
 
编译器认为这完全是型别安全的,因为由 Derived* 转换到 Base* 是正常的。但事 
实上这很差劲:因为 Derived 可能会比 Base 还要大,f() 里头的阵列索引不光是 
没有型别安全,甚至还可能没指到真正的物件呢!通常它会指到某个倒楣的 
Derived 物件的中间去。 
 
根本的问题在於:C++ 不能分辨出「指向一个东西」和「指向一个阵列」。很自然的 
,这是 C++“继承”自 C 语言的特徵。 
 
注意:如果我们用的是一个像阵列的「类别」而非最原始的阵列(譬如:"Array" 
而非 "T[]"),这问题就可以在编译期被挑出来,而非在执行的时候。

Go confidently in the  directions of your dreams,live the life you have imagined!Just do it!
It is no use learning without thinking!
2006-12-23 22:36
yuyunliuhen
Rank: 6Rank: 6
等 级:贵宾
威 望:20
帖 子:1435
专家分:0
注 册:2005-12-12
收藏
得分:0 
什麽是「虚拟成员函数」? 
 
虚拟函数可让衍生的类别「取代」原基底类别所提供的运作。只要某物件是衍生出来 
的,就算我们是透过基底物件的指标,而不是以衍生物件的指标来存取该物件,编译 
器仍会确保「取代後」的成员函数被呼叫。这可让基底类别的演算法被衍生者所替换 
,即使我们不知道衍生类别长什麽样子。 
 
注意:衍生的类别亦可“部份”取代(覆盖,override)掉基底的运作行为(如有必 
要,衍生类别的运作行为亦可呼叫它的基底类别版本)

Go confidently in the  directions of your dreams,live the life you have imagined!Just do it!
It is no use learning without thinking!
2006-12-23 22:37
yuyunliuhen
Rank: 6Rank: 6
等 级:贵宾
威 望:20
帖 子:1435
专家分:0
注 册:2005-12-12
收藏
得分:0 
C++ 怎样同时做到动态系结和静态型别? 
 
底下的讨论中,"ptr" 指的是「指标」或「参考」。 
 
一个 ptr 有两种型态:静态的 ptr 型态,与动态的「被指向的物件」的型态(该物 
件可能实际上是个由其他类别衍生出来的类别的 ptr)。 
 
「静态型别」("static typing") 是指:该呼叫的「合法性」,是以 ptr 的静态型 
别为侦测之依据,如果 ptr 的型别能处理成员函数,则「指向的物件」自然也能。 
 
「动态系结」("dynamic binding") 是指:「程式码」呼叫是以「被指向的物件」之 
型态为依据。被称为「动态系结」,是因为真正会被呼叫的程式码是动态地(於执行 
时期)决定的。 
 

Go confidently in the  directions of your dreams,live the life you have imagined!Just do it!
It is no use learning without thinking!
2006-12-23 22:37
yuyunliuhen
Rank: 6Rank: 6
等 级:贵宾
威 望:20
帖 子:1435
专家分:0
注 册:2005-12-12
收藏
得分:0 
衍生类别能否将基底类别的非虚拟函数覆盖(override)过去? 
 
可以,但不好。 
 
C++ 的老手有时会重新定义非虚拟的函数,以提升效率(换一种可能会运用到衍生类 
别才有的资源的作法),或是用以避开遮蔽效应(hiding rule,底下会提,或是看 
看 ARM ["Annotated Reference Manual"] sect.13.1),但是用户的可见性效果必 
须完全相同,因为非虚拟的函数是以指标/参考的静态型别为分派(dispatch)的依 
据,而非以指到的/被参考到的物件之动态型别来决定。 
 

Go confidently in the  directions of your dreams,live the life you have imagined!Just do it!
It is no use learning without thinking!
2006-12-23 22:37
yuyunliuhen
Rank: 6Rank: 6
等 级:贵宾
威 望:20
帖 子:1435
专家分:0
注 册:2005-12-12
收藏
得分:0 
"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) 

Go confidently in the  directions of your dreams,live the life you have imagined!Just do it!
It is no use learning without thinking!
2006-12-23 22:37
yuyunliuhen
Rank: 6Rank: 6
等 级:贵宾
威 望:20
帖 子:1435
专家分:0
注 册:2005-12-12
收藏
得分:0 
我该遮蔽住由基底类别继承来的公共成员函数吗? 
 
绝对绝对绝对绝对不要这样做! 
 
想去遮蔽(删去、撤消)掉继承下来的公共成员函数,是个很常见的错误。这通常是 
脑袋塞满了浆糊的人才会做的傻事。

Go confidently in the  directions of your dreams,live the life you have imagined!Just do it!
It is no use learning without thinking!
2006-12-23 22:37
yuyunliuhen
Rank: 6Rank: 6
等 级:贵宾
威 望:20
帖 子:1435
专家分:0
注 册:2005-12-12
收藏
得分:0 
圆形 "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()" 运作行为的名 
称重复了,就得注意前面提过的「遮蔽效应」)。 
 

Go confidently in the  directions of your dreams,live the life you have imagined!Just do it!
It is no use learning without thinking!
2006-12-23 22:38
yuyunliuhen
Rank: 6Rank: 6
等 级:贵宾
威 望:20
帖 子:1435
专家分:0
注 册:2005-12-12
收藏
得分:0 
对「圆形是/不是一种椭圆」这两难问题,有没有其他说法? 
 
如果你说:椭圆都可以不对称地挤压,又说:圆形是一种椭圆,又说:圆形不能不对 
称地挤压下去,那麽很明显的,你说过的某句话要做修正(老实说,该取消掉)。所 
以你不是得去掉 "Ellipse::setSize(x,y)",去掉圆形和椭圆间的继承关系,就是得 
承认你的「圆形」不一定是正圆。 
 
这儿有两个 OO/C++ 新手最易落入的陷阱。他们想用程式小技巧来弥补差劲的事前设 
计(他们重新定义 Circle::setSize(x,y),让它丢出一个例外,呼叫 "abort()" , 
或是选用两参数的平均数,或是不做任何事情),不幸的,这些技俩都会让使用者感 
到吃惊:他们原本都预期 "width() == x" 和 "height() == y" 这两个事实会成立。 
 
唯一合理的做法似乎是:降低椭圆形 "setSize(x,y)" 的保证事项(譬如,你可以改 
成:「这运作行为“可能”会把 width() 设成 x、height() 设成 y,也可能“不做 
任何事”」)。不幸的,这样会把界限冲淡,因为使用者没有任何有意义的物件行为 
足以依靠,整个类别阶层也就无毫价值可言了(很难说服别人去用一个:问你说它是 
做什麽的,你却只会耸耸肩膀说不知道的物件)。

Go confidently in the  directions of your dreams,live the life you have imagined!Just do it!
It is no use learning without thinking!
2006-12-23 22:38
快速回复:推荐:C++语言常见问题解答
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.019080 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved