注册 登录
编程论坛 C++教室

推荐:C++语言常见问题解答

yuyunliuhen 发布于 2006-12-23 22:28, 6332 次点击

这些是我在网上看到一些资料,感觉很有用,并把他与大家一起分享。很长,不必所有的都看,选自己比较感觉有兴趣的就好。希望对大家有所帮助。
下面是C++语言常见问答:有兴趣的可以看看,也可以做为查询的依据!
下面的东西都包括在这里:
只有本站会员才能查看附件,请 登录



   

[此贴子已经被作者于2006-12-27 21:49:56编辑过]

120 回复
#52
yuyunliuhen2006-12-23 22:44
哪一种标头档命名惯例最好? "foo.H"? "foo.hh"? "foo.hpp"? 
 
如果你已有个惯例,就用它吧。如果没有,而且你的编辑器不必去区分 C 和 C++ 档 
案的话,只要用 ".h" 就行了,否则就用编辑器所要的,像 ".H"、".hh" 或是 
".hpp"。 
 
在 Paradigm Shift 公司,我们用 ".h" 做为 C 和 C++ 的原始档案(然後,我们就 
不再建那些纯粹的 C 标头档案)。 
 
#53
yuyunliuhen2006-12-23 22:44
C++ 有没有像 lint 那样的指导原则? 
 
Yes,有一些常见的例子是危险的。 
但是它们都不尽然是「坏的」,因为有些情况下,再差的例子也得用上去。 
  * "Fred" 类别的设定运算子应该传回 "*this",当成是 "Fred&"(以允许成串的设 
    定指令)。 
  * 有任何虚拟函数的类别,都该有个虚拟解构子。 
  * 若一个类别有 {解构子,设定运算子,拷贝建构子} 其一的话,通常三者也都全 
    部需要。 
  * "Fred" 类别的拷贝建构子和设定运算子,都该将它们的参数加上 "const":分别 
    是 "Fred::Fred(const Fred&)" 和 "Fred& Fred::operator=(const Fred&)" 。 
  * 类别的子物件一定要用初始化串列 (initialization lists) 而不要用设定的方 
    式,因为对使用者自订类别而言,会有很大的效率差距(3x!)。 
  * 许多设定运算子都应该先测试:「我们」是不是「他们」;譬如: 
         Fred& Fred::operator= (const Fred& fred) 
         { 
           if (this == &fred) return *this; 
           //...normal assignment duties... 
           return *this; 
         } 
    有时候没必要测试,但一般说来,这些情况都是:没有必要由使用者提供外显的 
    设定运算子的时候(相对於编译器提供的设定运算子)。 
  * 在那些同时定义了 "+="、"+" 及 "=" 的类别中,"a+=b" 和 "a=a+b" 通常应该 
    做同样的事;其他类似的内建运算子亦同(譬如:a+=1 和 ++a; p[i] 和 *(p+i); 
    等等)。这可使用二元运算子 "op=" 之型式来强制做到;譬如: 
         Fred operator+ (const Fred& a, const Fred& b) 
         { 
           Fred ans = a; 
           ans += b; 
           return ans; 
         } 
    这样一来,有「建构性」的二元运算甚至可以不是夥伴。但常用的运算子有时可 
    能会更有效率地实作出来(譬如,如果 "Fred" 类别本来就是个 "String",且 
    "+=" 必须重新配置/拷贝字串记忆体的话,一开始就知道它的最後长度,可能会 
    比较好)。 
 
 
#54
yuyunliuhen2006-12-23 22:45
为什麽 C++ 的 FAQ 有一节讨论 Smalltalk?这是用来攻击 Smalltalk 的吗? 
 
世界上「主要的」两个 OOPLs 是 C++ 与 Smalltalk。由於这个流行的 OOPL 已有第 
二大的使用者总数量,许多新的 C++ 程式者是由 Smalltalk 背景跳过来的。这一节 
会回答以下问题: 
  * 这两个语言的差别? 
  * 从 Smalltalk 跳到 C++ 的程式者,要知道些什麽,才能精通 C++? 
 
这一节 *!*不会*!* 回答这些问题: 
  * 哪一种语言「最好」? 
  * 为什麽 Smalltalk「很烂」? 
  * 为什麽 C++「很烂」? 
 
这可不是对 Smalltalk 恐怖份子挑□,让他们趁我熟睡时戳我的轮胎(在我很难得 
有空休息的这段时间内 :-) 。 
 
#55
yuyunliuhen2006-12-23 22:45
C++ 和 Smalltalk 的差别在哪? 
 
最重要的不同是: 
 
  * 静态型别或动态型别? 
  * 继承只能用於产生子型别上? 
  * 数值语意还是参考语意 (value vs reference semantics)? 
 
头两个差异会在这一节中解释,第三点则是下一节的讨论主题。 
 
如果你是 Smalltalk 程式者,现在想学 C++,底下三则 FAQs 最好仔细研读。 
#56
yuyunliuhen2006-12-23 22:45
什麽是「静态型别」?它和 Smalltalk 有多相似/不像? 
 
静态型别(static typing)是说:编译器会“静态地”(於编译时期)检验各运算 
的型态安全性,而不是产生执行时才会去检查的程式码。例如,在静态型别之下,会 
去侦测比对函数引数的型态签名,不正确的配对会被编译器挑出错误来,而非在执行 
时才被挑出。 
 
OO 的程式里,最常见的「型态不符」错误是:欲对某物件启动个成员函数,但该物 
件并未准备好要处理该运算动作。譬如,如果 "Fred" 类别有成员函数 "f()" 但没 
有 "g()",且 "fred" 是 "Fred" 类别的案例,那麽 "fred.f()" 就是合法的, 
"fred.g()" 则是非法的。C++(静态地)在编译期捕捉型别错误,Smalltalk 则(动 
态地)在执行期捕捉。(技术上,C++ 很像 Pascal--“半”静态型别--因为指 
标转型与 union 都能用来破坏型别系统;这提醒了我们:你用指标转型与 union 的 
频率,只能像你用 "goto" 那样。) 
 
#57
yuyunliuhen2006-12-23 22:46
「静态型别」与「动态型别」哪一种比较适合 C++? 
 
若你想最有效率使用 C++,请把她当成静态型别语言来用。 
 
C++ 极富弹性,你可以(藉由指标转型、union 或 #define)让她「长得」像 
Smalltalk。但是不要这样做。这提醒了我们:少用 #define。 
 
有些场合,指标转型和 union 是必要的,甚至是很好的做法,但须谨慎为之。指标 
转型等於是叫编译器完全信赖你。错误的指标转型可能会毁坏堆积、在别的物件记忆 
体中乱搞、呼叫不存在的运作行为、造成一般性错误(general failure)。这是很 
糟糕的事。如果你避免用与这些相关的东西,你的 C++ 程式会更安全、更快,因为 
能在编译期就检测的东西,就不必留到执行期再做。 
 
就算你喜欢动态型别,也请避免在 C++ 里使用,或者请考虑换另一个将型态检查延 
迟到执行期才做的语言。C++ 将型态检验 100% 都放在编译时期;她没有任何执行期 
型态检验的内建机制。如果你把 C++ 当成一个动态型别的 OOPL 来用,你的命运将 
操之汝手。 
#58
yuyunliuhen2006-12-23 22:46
怎样分辨某个 C++ 物件程式库是否属於动态型别的? 
 
提示 #1:当所有东西都衍生自单一的根类别(root class),通常叫做 "Object"。 
提示 #2:当容器类别 container classes,像 List、Stack、Set 等,都不是 
          template 版的。 
提示 #3:当容器类别(List、Stack、Set 等)把插入/取出的元素,都视为指向 
          "Object" 的指标时。(你可以把 Apple 放进容器中,但当你取出时,编 
          译器只知道它是衍生自 Object,所以你得用指标转型将它转回 Apple* ; 
          你最好祈祷它真的是个 Apple,否则你会脑充血的。) 
 
你可用 "dynamic_cast"(於 1994 年才加入的)来使指标转型「安全些」,但这种 
动态测试依旧是“动态”的。这种程式风格是 C++ 动态型别的基本要素,你可以呼 
叫函数:「把这个 Object 转换成 Apple,或是给我个 NULL,如果它不是 Apple的 
话」,你就得到动态型别了:直到执行时期才知道会发生什麽事。 
 
若你用 template 去实作出容器类别,C++ 编译器会静态侦测出 99% 的型态资讯( 
"99%" 并不是真的;有些人宣称能做到 100%,而那些需要持续性 (persistence) 的 
人,只能得到低於 100% 的静态型别检验)。重点是:C++ 透过 template 来做到泛 
型(genericity),而非透过继承。 
#59
yuyunliuhen2006-12-23 22:46
在 C++ 里怎样用继承?它和 Smalltalk 有何不同? 
 
有些人认为继承是用来重用程式码的。在 C++ 中,这是不对的。说明白点,「继承 
不是『为』重用程式码而设计的。」 
 
【译注】这一个分野相当重要。否则,C++ 使用者就会感染「继承发烧症」 
         (inheritance fever)。 
 
C++ 继承的目的是用来表现介面一致性(产生子类别),而不是重用程式码。C++ 中 
,重用程式码通常是靠「成份」(composition) 而非继承。换句话说,继承主要是用 
来当作「特异化」(specialization) 的技术,而非实作上的技巧。 
 
这是与 Smalltalk 主要的不同之处,在 Smalltalk 里只有一种继承的型式(C++ 有 
"private" 继承--「共享程式码,但不承袭其介面」,有 "public" 继承--表现 
"kind-of" 关系)。Smalltalk 语言非常(相对於只是程式的习惯)允许你置放一个 
override 覆盖(它会去呼叫个「我看不懂」的运作行为),以达到「隐藏住」继承 
下来的运作行为的“效果”。更进一步,Smalltalk 可让观念界的 "is-a" 关系“独 
立於”子类别阶层之外(子型别不必也是子类别;譬如,你可以让某个东西是一个 
Stack,却不必继承自 Stack 类别)。 
 
相反的,C++ 对继承的限制更严:没办法不用到继承就做出“观念上的 is-a”关系 
(有个 C++ 的解决方法:透过 ABC 来分离介面与实作)。C++ 编译器利用公共继承 
额外附的语意资讯,以提供静态型别。 
#60
yuyunliuhen2006-12-23 22:46
Smalltalk/C++ 不同的继承,在现实里导致的结果是什麽? 
 
Smalltalk 让你做出不是子类别的子型别,做出不是子型别的子类别,它可让 
Smalltalk 程式者不必操心该把哪种资料(位元、表现型式、资料结构)放进类别里 
面(譬如,你可能会把连结串列放到堆叠类别里)。毕竟,如果有人想要个以阵列做 
出的堆叠,他不必真的从堆叠继承过来;喜欢的话,他可以从阵列类别 Array 中继 
承过来,即使 ArrayBasedStack 并“不是”一种阵列!) 
 
在 C++ 中,你不可能不为此操心。只有机制(运作行为的程式码),而非表现法( 
资料位元)可在子类别中被覆盖掉,所以,通常你“不要”把资料结构放进类别里比 
较好。这会促成 Abstract Base Classes (ABCs) 的强烈使用需求。 
 
我喜欢用 ATV 和 Maseratti 之间的差别来比喻。ATV(all terrain vehicle,越野 
车)很好玩,因为你可以「到处逛」,任意开到田野、小溪、人行道等地。另一方面 
,Maseratti 让你能高速行驶,但你只能在公路上面开。就算你喜欢「自由表现力」 
,偏偏喜欢驶向丛林,但也请不要在 C++ 里这麽做;它不适合。 
 
#61
yuyunliuhen2006-12-23 22:46
学过「纯种」的 OOPL 之後才能学 C++ 吗? 
 
不是(事实上,这样可能反而会害了你)。 
 
(注意:Smalltalk 是个「纯种」的 OOPL,而 C++ 是个「混血」的 OOPL。)读这 
之前,请先读读前面关於 C++ 与 Smalltalk 差别的 FAQs。 
 
OOPL 的「纯粹性」,并不会让转移到 C++ 更容易些。事实上,典型的动态系结与非 
子型别的继承,会让 Smalltalk 程式者更难学会 C++。Paradigm Shift 公司曾教过 
数千人 OO 技术,我们注意到:有 Smalltalk 背景的人来学 C++,通常和那些根本 
没碰过继承的人学起来差不多累。事实上,对动态型别的 OOPL(通常是,但不全都 
是 Smalltalk)有高度使用经验的人,可能会“更难”学好,因为想把过去的习惯“ 
遗忘”,会比一开始就学习静态型别来得困难。 
 
【译注】作者是以「语言学习」的角度来看的。事实上,若先有 Smalltalk 之类的 
         物件导向观念的背景知识,再来学 C++ 就不必再转换 "paradigm"--物件 
         导向的中心思维是不会变的,变的只是实行细节而已。
#62
yuyunliuhen2006-12-23 22:47
什麽是 NIHCL?到哪里拿到它? 
 
NIHCL 代表 "national-institute-of-health's-class-library",美国国家卫生局 
物件程式库。取得法:anonymous ftp 到 [128.231.128.7], 
档案:pub/nihcl-3.0.tar.Z 。 
 
NIHCL(有人念作 "N-I-H-C-L",有人念作 "nickel")是个由 Smalltalk 转移过来 
的 C++ 物件程式库。有些 NIHCL 用到的动态型别很棒(譬如:persistent objects 
,持续性物件),也有些地方动态型别会和 C++ 语言的静态型别相冲突,造成紧张 
关系。 
 
详见前面关於 Smalltalk 的 FAQs。 
 
 
#63
yuyunliuhen2006-12-23 22:47
什麽是数值以及参考语意?哪一种在 C++ 里最好? 
 
在参考语意 (reference semantics) 中,「设定」是个「指标拷贝」的动作(也就 
是“参考”这个词的本意),数值语意 (value semantics,或 "copy" semantics) 
的设定则是真正地「拷贝其值」,而不是做指标拷贝的动作。C++ 让你选择:用设定 
运算子来拷贝其值(copy/value 语意),或是用指标拷贝方式来拷贝指标 
(reference 语意)。C++ 让你能覆盖掉 (override) 设定运算子,让它去做你想要 
的事,不过系统预设的(而且是最常见的)方式是拷贝其「数值」。 
 
参考语意的优点:弹性、动态系结(在 C++ 里,你只能以传指标或传参考来达到动 
态系结,而不是用传值的方式)。 
 
数值语意的优点:速度。对需要物件(而非指标)的场合来说,「速度」似乎是很奇 
怪的特点,但事实上,我们比较常存取物件本身,较不常去拷贝它。所以偶尔的拷贝 
所付出的代价,(通常)会被拥有「真正的物件本身」、而非仅是指向物件的指标所 
带来的效益弥补过去。 
 
有三个情况,你会得到真正的物件,而不是指向它的指标:区域变数、整体/静态变 
数、完全被某类别包含在内 (fully contained) 的成员物件。这里头最重要的就是 
最後一个(也就是「成份」)。 
 
後面的 FAQs 会有更多关於 copy-vs-reference 语意的资讯,请全部读完,以得到 
较平衡的观点。前几则会刻意偏向数值语意,所以若你只读前面的,你的观点就会有 
所偏颇。 
 
设定 (assignment) 还有别的事项(譬如:shallow vs deep copy)没在这儿提到。 
#64
yuyunliuhen2006-12-23 22:47
「虚拟资料」是什麽?怎麽样/为什麽该在 C++ 里使用它? 
 
虚拟资料让衍生类别能改变基底类别的物件成员所属的类别。严格说来,C++ 并不「 
支援」虚拟资料,但可以模拟出来。不漂亮,但还能用。 
 
欲模拟之,基底类别必须有个指标指向成员物件,衍生类别必须提供一个 "new" 到 
的物件,以让原基底类别的指标所指到。该基底类别也要有一个以上正常的建构子, 
以提供它们自己的参考(也是透过 "new"),且基底类别的解构子也要 "delete" 掉 
被参考者。 
 
举例来说,"Stack" 类别可能有个 Array 成员物件(采用指标),衍生类别 
"StretchableStack" 可能会把基底类别的成员资料 "Array" 覆盖成 
"StretchableArray"。想做到的话,StretchableArray 必须继承自 Array,这样子 
Stack 就会有个 "Array*"。Stack 的正常建构子会用 "new Array" 来初始化它的 
"Array*",但 Stack 也会有一个(可能是在 "protected:" 里)特别的建构子,以 
自衍生类别中接收一个 "Array*"; StretchableArray 的建构子会用 
"new StretchableArray" 把它传给那个特别的建构子。 
 
优点: 
  * 容易做出 StretchableStack(大部份的程式都继承下来了)。 
  * 使用者可把 StretchableStack 当成“是一种”Stack 来传递。 
 
缺点: 
  * 多增加额外的间接存取层,才能碰到 Array。 
  * 多增加额外的自由记忆体配置负担(new 与 delete)。 
  * 多增加额外的动态系结负担(原因请见下一则 FAQ)。 
 
换句话说,在“我们”这一边,很轻松就成功做出 StretchableStack,但所有用户 
却都为此付出代价。不幸的,额外负荷不仅在 StretchableStack 会有,连 Stack 
也会。 
 
请看下下一则 FAQ,看看使用者会「付出」多少代价。也请读读下一则 FAQ 以後的 
几则(不看其他的,你将得不到平衡的报导)。 
#65
yuyunliuhen2006-12-23 22:47
虚拟资料和动态资料有何差别? 
 
最容易分辨出来的方法,就是看看颇为类似的「虚拟函数」。虚拟成员函数是指:在 
所有子类别中,它的宣告(型态签名)部份必须保持不变,但是定义(本体)的部份 
可以被覆盖(override)。继承下来的成员函数可被覆盖,是子类别的静态性质 
(static property);它不随任何物件之生命期而动态地改变,同一个子类别的不同 
物件,也不可能会有不同的成员函数的定义。 
 
现在请回头重读前面这一段,但稍作些代换: 
  * 「成员函数」 --> 「成员物件」 
  * 「型态签名」 --> 「型别」 
  * 「本体」     --> 「真正所属的类别」 
这样子,你就看到虚拟资料的定义。 
 
从另一个角度来看,就是把「各个物件」的成员函数与「动态」成员函数区分开来。 
「各个物件」成员函数是指:在任何物件案例中,该成员函数可能会有所不同,可能 
会塞入函数指标来实作出来;这个指标可以是 "const",因为它在物件生命期中不会 
变更。「动态」成员函数是指:该成员函数会随时间动态地改变;也可能以函数指标 
来做,但该指标不会是 const 的。 
 
推而广之,我们得到三种不同的资料成员概念: 
  * 虚拟资料:成员物件的定义(真正所属的类别)可被子类别覆盖,只要它的宣告 
    (型别)维持不变,且此覆盖是子类别的静态性质。 
  * 各物件的资料:任何类别的物件在初始化时,可以案例化不同型式(型别)的成 
    员物件(通常是一个 "wrapper" 包起来的物件),且该成员物件真正所属的类别 
    ,是把它包起来的那个物件之静态性质。 
  * 动态资料:成员物件真正所属的类别,可随时间动态地改变。 
 
它们看起来都差不多,是因为 C++ 都不「直接支援」它们,只是「能做得出来」而 
已;在这种情形下,模拟它们的机制也都一样:指向(可能是抽象的)基底类别的指 
标。在直接提供这些 "first class" 抽象化机制的语言中,这三者间的差别十分明 
显,它们各有不同的语法。 
#66
yuyunliuhen2006-12-23 22:47
我该正常地用指标来配置资料成员,还是该用「成份」(composition)? 
 
成份。 
 
正常情况下,你的成员资料应该被「包含」在合成的物件里(但也不总是如此; 
"wrapper" 物件就是你会想用指标/参考的好例子;N-to-1-uses-a 的关系也需要某 
种指标/参考之类的东西)。 
 
有三个理由说明,完全被包含的成员物件(「成份」)的效率,比自由配置物件的指 
标还要好: 
 
  * 额外的间接层,每当你想存取成员物件时。 
  * 额外的动态配置("new" 於建构子中,"delete" 於解构子中)。 
  * 额外的动态系结(底下会解释)。 
#67
yuyunliuhen2006-12-23 22:48
动态配置成员物件有三个效率因素,它们的相对代价是多少? 
 
这三个效率因素,上一则 FAQ 有列举出来: 
  * 以它本身而言,额外的间接层影响不大。 
  * 动态配置可能是个效率问题(当有许多配置动作时,典型的 malloc 会拖慢速度 
    ;OO 软体会被动态配置拖垮,除非你事先就留意到它了)。 
  * 用指标而非物件的话,会带来额外的动态系结。每当 C++ 编译器能知道某物件「 
    真正的」类别,该虚拟函数呼叫就能“静态”地系结住,能够被 inline。Inline 
    可能有无限大的 (但你可能只会相信有半打的 :-) 最佳化机会,像是 procedural 
    integration、暂存器生命期等等事项。三种情形之下,C++ 编译器能知道物件真 
    正的类别:区域变数、整体/静态变数、完全被包含的成员物件。 
 
完全被包含的成员物件,可达到很大的最佳化效果,这是「成员物件的指标」所不可 
能办到的。这也就是为什麽采用参考语意的语言,会「与生俱来」就效率不彰的原因 
了。 
 
注意:请读读下面三则 FAQs 以得到平衡的观点!
#68
yuyunliuhen2006-12-23 22:48
"inline virtual" 的成员函数真的会被 "inline" 吗? 
 
Yes,可是... 
 
一个透过指标或参考的 virtual 呼叫总是动态决定的,可能永远都不会被 inline。 
原因:编译器直到执行时(亦即:动态地),才会知道该呼叫哪个程式,因为那一段 
程式,可能会来自呼叫者编译过後才出现的衍生类别。 
 
因此,inline virtual 的呼叫可被 inline 的唯一时机是:编译器有办法知道某物 
件「真正所属的类别」之时,这是虚拟函数呼叫里所要知道的事情。这只会发生在: 
编译器拥有真正的物件,而非该物件的指标或参考。也就是说:不是区域变数、整体 
/静态物件,就是合成物件里的完全包含物件。 
 
注意:inline 和非 inline 的差距,通常会比正常的和虚拟的函数呼叫之差别更为 
显著。譬如,正常的与虚拟的函数呼叫,通常只差两个记忆体参考的动作而已,可是 
inline 与非 inline 函数就会有一个数量级的差距(与数万次影响不大的成员函数 
呼叫相比,函数没有用 inline virtual 的话,会造成 25X 的效率损失! 
[Doug Lea, "Customization in C++," proc Usenix C++ 1990]). 
 
针对此现象的对策:不要陷入编译器/语言厂商之间,对彼此产品的虚拟函数呼叫, 
做永无止尽的性能比较争论(或是广告噱头!)之中。和语言/编译器能否将成员函 
数呼叫做「行内展开」相比,这种比较完全没有意义。也就是说,许多语言编译器厂 
商,拼命强调他们的函数分派方式有多好,但如果他们没做“行内”成员函数呼叫的 
话,整体性能还是会很差,因为 inline--而非分派--才是最重要的性能影响因 
素。 
 
注意:请读读下两则 FAQs 以看看另一种说法! 
#69
yuyunliuhen2006-12-23 22:48
看起来我不应该用参考语意了,是吗? 
 
错。 
 
参考语意是个好东西。我们不能抛弃指标,我们只要不让软体的指标变成一个大老鼠 
窝就行了。在 C++ 里,你可以选择该用参考语意(指标/参考)还是数值语意(物 
件真正包含其他物件)的时机。在大型系统中,两者应该取得平衡。然而如果你全都 
用指标来做的话,速度会大大的降低。 
 
接近问题层次的物件,会比较高阶的物件还要大。这些针对「问题空间」抽象化的个 
体本身,通常比它们内部的「数值」更为重要。参考语意应该用於问题空间的物件上 
。 
 
注意:问题空间的物件,通常会比解题空间的更为高阶抽象化,所以相对地问题空间 
的物件通常会有较少的交谈性。因此 C++ 给我们一个“理想的”解决法:我们用参 
考语意,来对付那些需要独立的个体识别 (identity) 者,或是大到不适合直接拷贝 
的物件;其他情形则可选择数值语意。因此,使用频率较高的就用数值语意,因为( 
只有)在不造成伤害的场合下,我们才去增加弹性;必要时,我们还是选择效率! 
 
还有其他关於实际 OO 设计方面的问题。想精通 OO/C++ 得花时间,以及高素质的训 
练。若你想有个强大的工具,你必须投资下去。 
 
#70
yuyunliuhen2006-12-23 22:48
参考语意效率不高,那麽我是否应该用传值呼叫? 
 
不。 
 
前面的 FAQ 是讨论“成员物件”(member object) 的,而不是函数参数。一般说来 
,位於继承阶层里的物件,应该用参考或指标来传递,而非传值,因为惟有如此你才 
能得到(你想要的)动态系结(传值呼叫和继承不能安全混用,因为如果把大大的子 
类别物件当成基底的物件来传值的话,它会被“切掉”)。 
 
除非有足以令人信服的反方理由,否则成员物件应该用数值,而参数该用参考传递。 
前几则 FAQs 提到一些「足以信服的理由」,以支持“成员物件该用参考”一事了。 
#71
yuyunliuhen2006-12-23 22:50
怎样从 C++ 中呼叫 C 的函数 "f(int,char,float)"? 
 
告诉 C++ 编译器说:它是个 C 的函数: 
         extern "C" void f(int,char,float); 
 
确定你有 include 进来完整的函数原型 (function prototype)。一堆 C 的函数可 
以用大括号框起来,如下: 
 
         extern "C" { 
           void* malloc(size_t); 
           char* strcpy(char* dest, const char* src); 
           int   printf(const char* fmt, ...); 
         } 
 
#72
yuyunliuhen2006-12-23 22:50
怎样才能建一个 C++ 函数 "f(int,char,float)",又能被 C 呼叫? 
 
想让 C++ 编译器知道 "f(int,char,float)" 会被 C 编译器用到的话,就要用到前 
一则 FAQ 已详述的 "extern C" 语法。接著在 C++ 模组内定义该函数: 
 
         void f(int x, char y, float z) 
         { 
           //... 
         } 
 
"extern C" 一行会告诉编译器:送到 linker 的外部资讯要采用 C 的呼叫惯例及签 
名编码法(譬如,前置一个底线)。既然 C 没有多载名称的能力,你就不能让 C 程 
式能同时呼叫得到多载的函数群。 
 
警告以及实作相关事项: 
  * 你的 "main()" 应该用 C++ 编译之(为了静态物件的初始化)。 
  * 你的 C++ 编译器应该能设定连结的程序(为某些特殊的程式库)。 
  * 你的 C 和 C++ 编译器可能要是同一个牌子的,而且是相容的版本(亦即:有相 
    同的呼叫惯例等等)。 
 
#73
yuyunliuhen2006-12-23 22:50
为什麽 linker 有这种错误讯息:C/C++ 函数被 C/C++ 函数呼叫到? 
 
看前两则 FAQs 关於 extern "C" 的使用。 
 
======================================== 
 
Q108:该怎麽把 C++ 类别的物件传给/传自 C 的函数? 
 
例子: 
 
         /****** C/C++ header file: Fred.h ******/ 
         #ifdef __cplusplus    /*"__cplusplus" is #defined if/only-if 
                                  compiler is C++*/ 
           extern "C" { 
         #endif 
 
         #ifdef __STDC__ 
           extern void c_fn(struct Fred*);       /* ANSI-C prototypes */ 
           extern struct Fred* cplusplus_callback_fn(struct Fred*); 
         #else 
           extern void c_fn();                   /* K&R style */ 
           extern struct Fred* cplusplus_callback_fn(); 
         #endif 
 
         #ifdef __cplusplus 
           } 
         #endif 
 
         #ifdef __cplusplus 
           class Fred { 
           public: 
             Fred(); 
             void wilma(int); 
           private: 
             int a_; 
           }; 
         #endif 
 
"Fred.C" 是个 C++ 模组: 
 
         #include "Fred.h" 
         Fred::Fred() : a_(0) { } 
         void Fred::wilma(int a) : a_(a) { } 
 
         Fred* cplusplus_callback_fn(Fred* fred) 
         { 
           fred->wilma(123); 
           return fred; 
         } 
 
"main.C" 是个 C++ 模组: 
 
         #include "Fred.h" 
 
         int main() 
         { 
           Fred fred; 
           c_fn(&fred); 
           return 0; 
         } 
 
"c-fn.c" 是个 C 模组: 
 
         #include "Fred.h" 
         void c_fn(struct Fred* fred) 
         { 
           cplusplus_callback_fn(fred); 
         } 
 
把指向 C++ 物件的指标传到/传自 C 的函数,如果传出与收回的指标不是“完全相 
同”的话,就会失败。譬如,不要传出一个基底类别的指标却收回一个衍生类别的指 
标,因为 C 编译器不懂该怎麽对多重及虚拟继承的指标做转型。
#74
yuyunliuhen2006-12-23 22:50
C 的函数能不能存取 C++ 类别的物件资料? 
 
有时可以。 
 
(请先读一读前一则关於和 C 函数间传递 C++ 物件的 FAQ。) 
 
你可以安全地从 C 函数中存取 C++ 物件的资料,只要 C++ 的物件类别: 
  * 没有虚拟函数(包含继承下来的虚拟函数). 
  * 所有资料都在同一个存取等级中 (private/protected/public). 
  * 完全被包含的子物件中也都没有虚拟函数. 
 
如果 C++ 类别有任何基底类别(或是任何被完全包含的子物件中有基底类别)的话 
,技术上来说,存取该资料没有可携性的,因为语言没规定在继承之下的类别配置是 
什麽样子。不过经验上,所有 C++ 编译器的做法都一样:基底类别物件先出现(在 
多重继承之下,则由左到右排列之),子物件次之。 
 
还有,如果类别(或是任何基底类别)含有任何虚拟函数,你时常可以(但不是一直 
都可以)假设有一个 "void*" 出现在物件第一个虚拟函数之所在,或是在该物件的 
第一个 word 那里。同样的,语言对它也没规定到,但这似乎是「大家」都采取的做 
法。 
 
如果该类别有任何虚拟基底类别,情况会更复杂而且更没有可携性。常见的做法是: 
让物件最後才包含基底类别之物件 (V)(不管 "V" 在继承阶层中在哪儿出现),物 
件的其他部份则以正常的次序出现。每个有 V 这个虚拟基底类别的衍生类别,实际 
上都有个“指标”指向最後一个物件的 V 的部份。 
#75
yuyunliuhen2006-12-23 22:50
为什麽我总觉得 C++ 让我「离机器更远了」,不像 C 那样? 
 
因为事实上正是如此。 
 
做为一个 OOPL,C++ 让你以该问题的领域来思考,让你以问题领域的语言来设计程 
式,而非以解题的领域来著手。 
 
一个 C 最强的地方是:它没有「隐藏的机制」:你看到的就是你得到的,你可以一 
边阅读 C 的程式,一边「看到」每个系统时脉。C++ 则不然; C 的老手(像从前的 
我们)对这种特性常会有矛盾的心理(或是说「敌视」),但是很快的他们会发现: 
C++ 提供了抽象化的层次及经济的表现能力,大大降低维护成本,又不会损及执行效 
率。 
 
很自然的,用任何语言都会写出坏程式;C++ 并不会确保任何高品质、可重用性、抽 
象化,或是任何「正字标记」的品质因子。C++ 不会让差劲的程式者写不出差劲的程 
式;她只是协助明智的发展者做出高人一等的软体。 
 
#76
yuyunliuhen2006-12-23 22:50
「指向成员函数的指标」和「指到函数的指标」的型态有差别吗? 
 
是的。 
 
考虑底下的函数: 
 
         int f(char a, float b); 
 
如果它是普通的函数,它的型态是:            int (*)      (char,float); 
如果它是 Fred 类别的运作行为,它的型态是:  int (Fred::*)(char,float); 
#77
yuyunliuhen2006-12-23 22:51
怎样把指向成员函数的指标传给 signal handler、X event callback 等等? 
 
【译注】这是和 UNIX、X Window System 相关的问题,但其他系统亦可推而广之。 
 
不要这样做。 
 
因为若无物件去启动它,成员函数是无意义的,你不能直接使用它(如果 X 视窗系 
统是用 C++ 写的话,或许就可以直接传物件的参考值了,而不光是传个指向函数的 
指标;自然地,物件会包含所有要用到的函数,甚至更多)。 
 
若想修改现有的软体,可拿最顶层的(非成员的)函数当作一层包装 (wrapper),透 
过其他技巧(或许是放在全域变数中),把该物件包起来。这个最顶层的函数,会透 
过适当的成员函数去使用该全域变数。 
 
譬如,你想在中断处理中呼叫 Fred::memfn() 的话: 
 
         class Fred { 
         public: 
           void memfn(); 
           static void staticmemfn();    // 用个 static 成员函数就行了 
           //... 
         }; 
 
         //wrapper 函数会记得哪个物件该去启动全域物件的成员函数: 
         Fred* object_which_will_handle_signal; 
         void Fred_memfn_wrapper() { object_which_will_handle_signal->memfn(); } 
 
         main() 
         { 
           /* signal(SIGINT, Fred::memfn); */   //不能这样做 
           signal(SIGINT, Fred_memfn_wrapper);  //Ok 
           signal(SIGINT, Fred::staticmemfn);   //Also Ok 
         } 
 
注意:静态成员函数不需要真正的物件才能启动,所以指向静态成员函数的指标,和 
普通的指向函数的指标,具有相容的型态(详见 ARM ["Annotated Reference 
Manual"] p.25, 158)。 
#78
yuyunliuhen2006-12-23 22:51
当我想以成员函数做为中断服务常式 (ISR) 时,为什麽编译器产生(型态不 
       符)的错误? 
 
这是前两个问题的特例,所以请先看看前两则解答。 
 
非静态的成员函数,都有一个隐藏的参数,对应到 'this' 指标,该 'this' 指标会 
指向该物件的案例资料 (instance data),可是系统中断的硬体/韧体并未提供这个 
'this' 参数。你得用「正常的」函数(不是类别的成员)或是静态成员函数来做为 
中断服务常式才行。 
 
一个可行的解法是:用一个静态成员做为中断服务常式,让它能自己到某处去找案例 
/成员的配对,以供中断呼叫之用。这麽一来,当中断产生时,正常的 method 就会 
被启动,不过以技术观点来看,你得先呼叫一个中介函数。 
 
#79
yuyunliuhen2006-12-23 22:51
为什麽我取不出 C++ 函数的位址? 
 
这可由前一则 FAQ 推论过来。 
 
详细的解答:在 C++ 里,成员函数有一个隐含的参数,指向该物件本身(成员函数 
内的 "this" 指标)。正常的 C 函数与成员函数的呼叫惯例可视为不同,所以它们 
指标的型态(指向成员函数 vs 指向函数)既不同也不相容。C++ 引进一个新的指标 
型态:指向成员的指标,要提供一个物件才能启动之(见 ARM ["Annotated 
Reference Manual"] 5.5)。 
 
注意:不要去把指向成员函数的指标强制转型成指向函数的指标;这样做的结果是未 
定义的,且下场可能会很惨。譬如,指向成员函数的指标,“不必然”会包含某正常 
函数的机器位址(看 ARM, 8.1.2c, p.158)。如前例所提,如果你有个指向正常 C 
函数的指标的话,请用上层的(非成员的)函数,或是用 "static" 成员函数(类别 
成员函数)。 
#80
yuyunliuhen2006-12-23 22:52
怎样宣告指向成员函数的指标阵列? 
 
用 "typedef" 好让你的脑筋保持清醒。 
 
         class Fred { 
         public: 
           int f(char x, float y); 
           int g(char x, float y); 
           int h(char x, float y); 
           int i(char x, float y); 
           //... 
         }; 
 
         typedef  int (Fred::*FredPtr)(char x, float y); 
 
这是指向成员函数的指标阵列:Here's the array of pointers to member functions: 
 
         FredPtr a[4] = { &Fred::f, &Fred::g, &Fred::h, &Fred::i }; 
 
呼叫物件 "fred" 的某一个成员函数: 
 
         void userCode(Fred& fred, int methodNum, char x, float y) 
         { 
           //假设 "methodNum" 在 [0,3] 区间内 
           (fred.*a[methodNum])(x, y); 
         } 
 
你可以用 #define 让这个呼叫清楚些: 
 
         #define  callMethod(object,ptrToMethod)   ((object).*(ptrToMethod)) 
         callMethod(fred, a[methodNum]) (x, y); 
#81
yuyunliuhen2006-12-23 22:52
怎样自一个连结串列/杂凑表等等里面,插入/存取/改变元素? 
 
我将以最简单的「插入连结串列」为例。想把元素插入串列的头尾很容易,但只限 
於这些功能的话,会使程式库过於低能(太低能的程式库比没有更糟)。 
 
完整的解答会让 C++ 新手消化不良,所以我只提几个项目。第一个是最简单的,第 
二和第三是比较好的。 
 
[1] 替 "List" 加入一个「现在位置」的性质,加入像是 advance()、backup()、 
     atEnd()、atBegin()、getCurrElem()、setCurrElem(Elem)、insertElem(Elem) 
     、removeElem() 等等的运作行为。 
 
     即使在这个小例子里已经够用了,但「只有一个」现在位置的记号的话,想存取 
     串列中两个以上位置的元素就不太容易(譬如:「对所有 x,y 序对,做底下的 
     事情……」)。 
 
[2] 把上述的 List 运作行为拿掉,移到独立的类别 "ListPosition" 中。 
 
     ListPosition 的作用是:代表 List 里「现在的位置」,这样就允许许多位置 
     并存於同一个串列中。ListPosition 是 List 的夥伴,所以 List 的内部可对 
     外界隐藏起来(否则 List 的内部就会被它的公共运作行为所公开)。注意: 
     ListPosition 可以把运算子多载起来,像是 advance()、backup(),因为运算 
     子多载只是正常运作行为的语法糖衣而已。 
 
[3] 把整个位置处理(iteration)当成是一个基元事件(atomic event),建一个 
     class template 去涵盖该事件。 
 
     它不会在内部回圈中使用公共存取运作行为(它有可能是虚拟函数),所以效率 
     能增进。不幸的,你的应用软体会多出些额外的二元码,因为 template 是以空 
     间换取时间的。欲知详情,请见 [Koenig, "Templates as interfaces," 
     JOOP, 4, 5 (Sept 91)], 以及 [Stroustrup, "The C++ Programming Language 
     Second Edition," under "Comparator"]. 
#82
yuyunliuhen2006-12-23 22:52
「样版」(template)的用意是什麽? 
 
Template 本意是个压饼模子,它把饼乾都压成差不多一个样子(虽然饼乾的原料不 
尽相同,但它们都有相同的基本外形)。同理,class template 是个样版模子,用 
来描述如何将一系列的物件类别弄成同一个基本型式;而 function template 则是 
用以描述一系列看起来差不多的函数。 
 
Class template 常用於制造型别安全的容器(即使这仅止於「如何使用它」而已)。 
 
#83
yuyunliuhen2006-12-23 22:52
"function template" 的语法/语意是什麽? 
 
考虑底下这个交换两个整数引数的函数: 
 
         void swap(int& x, int& y) 
         { 
           int tmp = x; 
           x = y; 
           y = tmp; 
         } 
 
假如我们想交换 float、long、String、Set 和 FileSystems,我们还得写那些 
大致看起来都一样、只有型态不同的程式码,有够烦人。这种不花脑筋的重复性工作 
,正是电脑的专长,於是我们想出了 function template: 
 
         template 
         void swap(T& x, T& y) 
         { 
           T tmp = x; 
           x = y; 
           y = tmp; 
         } 
 
每次我们以一组型别来使用 "swap()",编译器会找到上面这定义,并造出另一个 
"template function" ,来当作它的「案例」(instantiation)。譬如: 
 
         main() 
         { 
           int    i,j;  /*...*/  swap(i,j);  // 案例化 "int"    的 swap 
           float  a,b;  /*...*/  swap(a,b);  // 案例化 "float"  的 swap 
           char   c,d;  /*...*/  swap(c,d);  // 案例化 "char"   的 swap 
           String s,t;  /*...*/  swap(s,t);  // 案例化 "String" 的 swap 
         } 
 
(注意:"template function" 是 "function template" 实体化之後的案例。) 
#84
yuyunliuhen2006-12-23 22:52
"class template" 的语法/语意是什麽? 
 
考虑像是个整数阵列的容器类别: 
 
         // 这会放在像是 "Array.h" 的标头档中 
         class Array { 
         public: 
           Array(int len=10)                  : len_(len), data_(new int[len]){} 
          ~Array()                            { delete [] data_; } 
           int len() const                    { return len_;     } 
           const int& operator[](int i) const { data_[check(i)]; } 
                 int& operator[](int i)       { data_[check(i)]; } 
           Array(const Array&); 
           Array& operator= (const Array&); 
         private: 
           int  len_; 
           int* data_; 
           int  check(int i) const 
             { if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_); 
               return i; } 
         }; 
 
如同前述的 "swap()" ,一再为 float、char、String、Array-of-String 等等来重 
复设计 Array 类别,是很烦人的。 
 
         // 这会放在像是 "Array.h" 的标头档中 
         template 
         class Array { 
         public: 
           Array(int len=10)                : len_(len), data_(new T[len]) { } 
          ~Array()                          { delete [] data_; } 
           int len() const                  { return len_;     } 
           const T& operator[](int i) const { data_[check(i)]; } 
                 T& operator[](int i)       { data_[check(i)]; } 
           Array(const Array&); 
           Array& operator= (const Array&); 
         private: 
           int len_; 
           T*  data_; 
           int check(int i) const 
             { if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_); 
               return i; } 
         }; 
 
不像 template function 那样,template classes(案例化的 class template)必 
须将那些用来案例化的参数型态明示出来: 
 
         main() 
         { 
           Array           ai; 
           Array         af; 
           Array         ac; 
           Array        as; 
           Array< Array >  aai; 
         }              // ^^^-- 注意这空格;不要用 "Array>" 
                        //       (编译器会把 ">>" 看成单一的元素) 
#85
yuyunliuhen2006-12-23 22:53
什麽是「参数化型别」(parameterized type)? 
 
另一种 "class template" 的说法。 
 
「参数化型别」是一种型别,它被另一个型别或数值所参数化(parameterized)了。 
像 List 是一个型别 ("List") ,它被另一个型别 ("int") 所参数化。 
 
======================================== 
 
Q121:「泛型」(genericity)是什麽? 
 
另一种 "class template" 的说法。 
 
不要和「一般化」(generality,指不要过於特定的解题)弄混了,「泛型」指的是 
class template。 
 
#86
yuyunliuhen2006-12-23 22:53
怎样拿到 "STL"? 
 
"STL" 代表 "Standard Templates Library",标准模版程式库。取得法: 
 
STL HP official site:   ftp://butler.hpl.hp.com/stl 
STL code alternate:     ftp://ftp.cs.rpi.edu/stl 
STL code + examples:    http://www.cs.rpi.edu/~musser/stl.html 
 
STL hacks for GCC-2.6.3 已经在 GNU libg++ 2.6.2.1 或更新版本里了(可能较早 
的版本也有)。多谢 Mike Lindner。 
 
#87
yuyunliuhen2006-12-23 22:53
怎样 ftp 到 "Numerical Recipes" 附的程式? 
 
它是用卖的,把它放到网路上散布是违法的。不过它只需 $30 美元而已。 
 
======================================== 
 
Q124:为什麽我的执行档会这麽大? 
 
很多人对这麽大的执行档感到惊讶,特别是当原始码只有一点点而已。例如一个简单 
的 "hello world" 程式居然会产生大家都想不到的大小(40+K bytes)。 
 
一个原因是:有些 C++ 执行期程式库被连结进去了。有多少被连结进去,就要看看 
你用到多少,以及编译器把程式库切割成多少块而定。例如,iostream 很大,包含 
一大堆类别及虚拟函数,即使你只用到一点点,因为各元件之间的交互参考依存关系 
,可能会把整个 iostream 程式码都塞进来了。(【译注】如果 linker 做得好的话 
,应该能把完全用不到的元件 object code 砍掉,不随之塞入你的执行档中。) 
 
不要用静态的,改用动态连结的程式库版本,就可以使你的程式变小。 
 
欲知详情,请看看你的编译器手册,或是寻求厂商的技术支援。 
 
#88
yuyunliuhen2006-12-23 22:53
GNU C++ (g++) 把小程式造出大大的执行档,为什麽? 
 
libg++(g++ 用到的程式库)可能在编译时带有除错的资讯(-g)。有些机器上,不 
带除错资讯地重新编译它,会省下很大的磁碟空间(~1 MB;缺点是:不能追踪到 
libg++ 的呼叫)。仅仅 "strip" 掉执行档,比不上先用 -g 重新编译,再 "strip" 
掉 a.out 档来得有效。 
 
用 "size a.out" 来看看执行码的程式与资料区段到底占了多大空间,而不要用 
"ls -s a.out" 这种包括了符号表格(symbol table)的方式。 
 
#89
yuyunliuhen2006-12-23 22:53
有 YACC 的 C++ 文法吗? 
 
Jim Roskind 是 C++ 的 YACC 文法作者,它大体上和部份 USL cfront 2.0 所实作 
出来的语言相容(没有 template、例外、执行期型态识别功能)。这份文法有些地 
方和 C++有细小而微妙的差别。 
 
它可用 anonymous ftp 到下列地方取得: 
  * ics.uci.edu (128.195.1.1) in "gnu/c++grammar2.0.tar.Z". 
  * mach1.npac.syr.edu (128.230.7.14) in "pub/C++/c++grammar2.0.tar.Z". 
#90
yuyunliuhen2006-12-23 22:53
什麽是 C++ 1.2?  2.0?  2.1?  3.0? 
 
这些不是“语言”的版本,而是 cfront 这个由 AT&T 做出来的、最早的 C++转译程 
式的版本编号。以这编号来“代表”C++ 语言的演进,已经是公认的惯例了。 
 
“非常”粗略地讲,主要的特徵有: 
  * 2.0 包含多重/虚拟继承,以及纯虚拟函数。 
  * 2.1 包含半巢状 (semi-nested) 类别,及 "delete [] 阵列指标"。 
  * 3.0 包含全巢状 (fully-nested) 类别、template 和 "i++" vs "++i"。 
  * 4.0 将包含例外处理
#91
yuyunliuhen2006-12-23 22:54
如果签名编码标准化了,我能否将不同厂商编译器产生的程式码连结起来? 
 
简短的回答:可能不行。 
 
换句话说,有人希望标准化的签名编码规则能并入拟议中的 C++ ANSI 标准,避免还 
要为不同厂商的编译器购买不同版本的物件程式库。然而不同的系统实作中,签名编 
码的差异性只占一小部份而已,即使是在同一个基台(platform)上。这里列出一部 
份其他的差异处: 
 
1) 成员函数隐含的引数个数和型态。 
    1a) 'this' 有被特殊处理吗? 
    1b) 传值的指标放在哪里? 
2) 假设有用到 vtable 虚拟表格的话: 
    2a) 它的内容及配置? 
    2b) 多重继承时,'this' 在何处/如何调整? 
3) 类别如何配置,包含: 
    3a) 基底类别的位置? 
    3b) 虚拟基底类别的处理? 
    3c) 虚拟表格指标的位置,如果有用虚拟表格的话? 
4) 函数的呼叫惯例,包含: 
    4a) 呼叫者还是被呼叫者负责调整堆叠? 
    4b) 实际参数放到哪里? 
    4c) 实际参数传递之顺序? 
    4d) 暂存器如何存放? 
    4e) 传回值放到哪里? 
    4f) 对传入/传回 struct 或 double 有无特殊的规定? 
    4g) 呼叫末端函数(leaf function)有无特殊的暂存器存放规定? 
5) run-time-type-identification 如何配置? 
6) 当一个例外被 throw 时,执行期的例外处理系统如何得知哪一个区域物件该被解 
    构? 
#92
yuyunliuhen2006-12-23 22:54
为什麽有 static 资料成员的物件类别产生了 linker 错误? 
 
Static 的资料成员必须外显地在唯一的模组中定义。 
                      ^^^^^^  ~~~~~~^^^^  ^^^^ 
【译注】这句话要逐字细读。原文是:Static data members must be 
         explicitly defined in exactly one module. 
 
譬如: 
         class Fred { 
         public: 
           //... 
         private: 
           static int i_;  // 宣告 static 资料成员 "Fred::i_" 
           //... 
         }; 
 
Linker 会告诉你 "Fred::i_ is not defined(未定义)" ,除非你在任何一个(且 
唯一)原始档中定义(而非宣告)了 "Fred::i_" : 
 
         int Fred::i_ = 某个会产生 int 的运算式; 
或是: 
         int Fred::i_; 
 
通常我们会在 "Fred.C" 档中定义 "Fred" 类别的 static 资料成员(或 "Fred.cpp" 
等等你使用的副档名)。 
#93
yuyunliuhen2006-12-23 22:54
"struct" 和 "class" 关键字差别在哪? 
 
struct 的成员和基底类别, 都是预设为 public 的,而 class 则预设为 private。 
注意:你应该“明显地”把基底类别设为 public、private 或是 protected,而不 
要依赖预设值。 
 
除此之外,两者的功能是相等的。 
 
#94
yuyunliuhen2006-12-23 22:54
为什麽不能以函数的传回值来多载(overload)它? 
 
如果你同时宣告了 "char f()" 及 "float f()" ,编译器会给你个错误讯息,因为 
呼叫 "f()" 会造成模拟两可的情况。 
#95
yuyunliuhen2006-12-23 22:54
什麽是「持续性」?什麽是「持续性物件」? 
 
一个持续性物件 (persistent object),在创造它的程式执行结束後,仍可存活下来 
。它甚至可存活於不同的父程式,存活於磁碟系统、作业系统、甚至於作业系统所处 
的硬体上。 
 
持续性物件的困难在於:如何有效地在次储存体中,存放它们的运作行为(method) 
及资料位元(以及所有成员物件的资料和运作行为,及它们所有的成员物件、基底类 
别……等等)。这一切都得自己来做的话,可不是件容易的事。在 C++中,你就得自 
己来。C++/OO 的资料库系统,会替你把这些机制都隐藏起来。 
 
#96
yuyunliuhen2006-12-23 22:55
为什麽浮点数 (floating point) 这麽不精确?为什麽这段程式不会印出 0.43? 
 
         #include 
 
         main() 
         { 
           float a = 1000.43; 
           float a = 1000.0; 
           cout << a - b << '\n'; 
         } 
 
(附注,有些 C++ 环境下会印出 0.429993) 
 
声明:受进位/舍位/近似值之苦,其实并不是 C++ 的问题,而是电脑科学界的问 
题。不过还是一直有人在 comp.lang.c++ 里发问,所以我给你一个答案意思一下。 
 
答案:浮点数本来就是个近似值。在 IEEE 的 32 位元浮点数标准里,有 1 位元的 
正负号,8 位元的指数,23 位元的假数。因为正规化後的二进位假数都会变成像是 
1.xxxxx... 的型式,所以头一项的 1 不予计入,就能得到 24 位元的有效假数。 
1000.43(以及其他很多很多数字)都不是 float 或 double 的表示法,其实 
1000.43 的位元内容是这样子的('s' 代表正负号,'e' 代表指数,'m' 代表假数) 
: 
 
     seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm 
     01000100011110100001101110000101 
 
假数移位後变成 1111101000.01101110000101 或是 1000 + 7045/16384。 
分数部份为 0.429992675781。 
float 的假数占 24 位元,所以你只得到 16M 分之一的精确度。 
double 有较高的精确度(53 位元的假数)。 
 

 
 
有任何 TeX 或 LaTeX 的巨集,能处理 "C++" 的留白效果(spacing)吗? 
 
有的,底下列出两个: 
 
\def\CC{C\raise.22ex\hbox{{\footnotesize +}}\raise.22ex\hbox{\footnotesize +}} 
 
\def\CC{{C\hspace{-.05em}\raisebox{.4ex}{\tiny\bf ++}}} 
#97
yuyunliuhen2006-12-23 22:55
在哪儿可拿到 C++2LaTeX 这个 C++原始码的 LaTeX 美编工具(pretty 
       printer)? 
 
这儿列出一些 ftp 地点: 
 
Host aix370.rrz.uni-koeln.de   (134.95.80.1) Last updated 15:41 26 Apr 1991 
     Location: /tex 
       FILE      rw-rw-r--     59855  May  5  1990   C++2LaTeX-1.1.tar.Z 
Host utsun.s.u-tokyo.ac.jp   (133.11.11.11) Last updated 05:06 20 Apr 1991 
     Location: /TeX/macros 
       FILE      rw-r--r--     59855  Mar  4 08:16   C++2LaTeX-1.1.tar.Z 
Host nuri.inria.fr   (128.93.1.26) Last updated 05:23  9 Apr 1991 
     Location: /TeX/tools 
       FILE      rw-rw-r--     59855  Oct 23 16:05   C++2LaTeX-1.1.tar.Z 
Host iamsun.unibe.ch   (130.92.64.10) Last updated 05:06  4 Apr 1991 
     Location: /TeX 
       FILE      rw-r--r--     59855  Apr 25  1990   C++2LaTeX-1.1.tar.Z 
Host iamsun.unibe.ch   (130.92.64.10) Last updated 05:06  4 Apr 1991 
     Location: /TeX 
       FILE      rw-r--r--     51737  Apr 30  1990 
       C++2LaTeX-1.1-PL1.tar.Z 
Host tupac-amaru.informatik.rwth-aachen.de   (192.35.229.9) 
Last updated 05:07 18 Apr 1991 
     Location: /pub/textproc/TeX 
       FILE      rw-r--r--     72957  Oct 25 13:51  C++2LaTeX-1.1-PL4.tar.Z 
Host wuarchive.wustl.edu   (128.252.135.4) Last updated 23:25 30 Apr 1991 
     Location: /packages/tex/tex/192.35.229.9/textproc/TeX 
       FILE      rw-rw-r--     49104  Apr 10  1990   C++2LaTeX-PL2.tar.Z 
       FILE      rw-rw-r--     25835  Apr 10  1990   C++2LaTeX.tar.Z 
Host tupac-amaru.informatik.rwth-aachen.de   (192.35.229.9) 
Last updated 05:07 18 Apr 1991 
     Location: /pub/textproc/TeX 
       FILE rw-r--r-- 74015  Mar 22 16:23 C++2LaTeX-1.1-PL5.tar.Z 
     Location: /pub 
       FILE rw-r--r-- 74015  Mar 22 16:23 C++2LaTeX-1.1-PL5.tar.Z 
Host sol.cs.ruu.nl   (131.211.80.5) Last updated 05:10 15 Apr 1991 
     Location: /TEX/TOOLS 
       FILE      rw-r--r--     74015  Apr  4 21:02x   C++2LaTeX-1.1-PL5.tar.Z 
Host tupac-amaru.informatik.rwth-aachen.de (192.35.229.9) 
Last updated 05:07 18 Apr 1991 
     Location: /pub/textproc/TeX 
       FILE      rw-r--r--      4792  Sep 11  1990 C++2LaTeX-1.1-patch#1 
       FILE      rw-r--r--      2385  Sep 11  1990 C++2LaTeX-1.1-patch#2 
       FILE      rw-r--r--      5069  Sep 11  1990 C++2LaTeX-1.1-patch#3 
       FILE      rw-r--r--      1587  Oct 25 13:58 C++2LaTeX-1.1-patch#4 
       FILE      rw-r--r--      8869  Mar 22 16:23 C++2LaTeX-1.1-patch#5 
       FILE      rw-r--r--      1869  Mar 22 16:23 C++2LaTeX.README 
Host rusmv1.rus.uni-stuttgart.de   (129.69.1.12) 
Last updated 05:13 13 Apr 1991 
     Location: /soft/tex/utilities 
       FILE      rw-rw-r--    163840  Jul 16  1990   C++2LaTeX-1.1.tar 
#98
yuyunliuhen2006-12-23 22:56
该到哪里取得 "tgrind" 这个 C++/C/etc 的原始码美编工具? 
 
"tgrind" 读入 C++ 原始档案,并输出能让 Unix 印表机印出美观文件的东西。它常 
会伴随在 TeX 和 LaTeX 的套件里;请找找这个目录: 
  "...tex82/contrib/van/tgrind" 。 由 Jerry Leichter 所做更新的版本,可在 
venus.ycc.yale.edu in [.TGRIND] 里找到。 
 
#99
yuyunliuhen2006-12-23 22:56
有给 GNU emacs 编辑器用的 C++-mode 吗?有的话,该怎麽拿? 
 
Yes,有一个给 GNU emacs 用的 C++-mode。 
 
最新、最好的 C++-mode(以及 c-mode)版本是 cc-mode.el 档,是 Detlef & 
Clamen 版本的延伸。Emacs 里头有一个了,较新的则在 elisp 里面。 
 
#100
yuyunliuhen2006-12-23 22:56
我要到哪儿得到和作业系统相关的 FAQs( 譬如:BC++、DOS、Windows 等等 
       )? 
 
请参考: 
  * comp.os.msdos.programmer 
  * comp.windows.ms.programmer 
  * comp.unix.programmer 
 
[如果您有 BC++、VC++ 的 email address,或是 Semantic C++ 的臭□清单或可供 
讨论的 mailing list,请告诉我该如何加入,我会在这儿提出的。] 
#101
yuyunliuhen2006-12-23 22:56
为什麽我的 DOS C++ 程式说 "Sorry: floating point code not linked" 
       “抱歉,浮点运算程式码未连结进来”? 
 
编译器会试著节省执行档的大小,所以除非必要,否则不引入浮点数→字串格式转换 
的副程式,可是有时候它会猜错,就会产生上述的错误讯息了。解决法:(1) 使用 
  而不要用 ,或是 (2) 在您程式的某个地方,置入如下的函 
数(但是不要真的去呼叫它!): 
 
         static void dummyfloat(float *x) { float y; dummyfloat(&y); } 
 
请参考关於 stream I/O 的 FAQ项目,有提到更多使用  vs  
的理由。 
123