以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 C/C++编程思想 』  (http://bbs.xml.org.cn/list.asp?boardid=61)
----  C++语言常见问题解  (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=52837)


--  作者:卷积内核
--  发布时间:9/19/2007 8:17:00 AM

--  C++语言常见问题解
这是从台湾的http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-Chinese/发现的《C++ Frequently Asked Questions》的繁体翻译,作者是:叶秉哲,也是《C++ Programming Language》3/e繁体版的译者,该文章是非常的好,出于学习用途而将它转贴,本人未取得作者的授权,原文章的版权仍然归属原作者.


C++语言常见问题解

C++语言常见问题解答

译后感言

叶秉哲 83.11.26.

翻译这份 USENET comp.lang.c++ FAQ 也算是始料未及的事。一开始是

研发组穆信成同学有个构想:将一些好的英文文件翻译出来,以造福苍生﹑

解民倒悬……。这点子很好,当下就赞同这提议。不过没想到,「赞同」该

方案似乎还不够,我看到了一抹神秘的微笑自他嘴角浮起……结局就是一时

不察,祸从口出啦!

前面所提的其实只是近因。事实上,我对「翻译」一事虽无作品,关怀

之忱却袅绕甚久。认识我的朋友应该知道:我对中译书籍品质之批评向来是

不假辞色的,不限于计算机类。两年来「PC Magazine 中文版」的无责任书评

专栏,侯 SIR对这课题亦时有针砭之言。这些都促使我对翻译一事比以往更

留上了神。

真正的导火线,应该是源自我的一篇书评(将刊于 83 年 12 月「PC

Magazine中文版」),里面也有满辛辣的讽刺批评,只差没有点名批斗而已

(不过有心人应该看得出我点名的对象)。为了避免予人「只会批评,不会

做事」之讥,也为了积贮多年翻译理论之余,有个落实的机会,便挑选了这

份文件。

对这份 FAQ:技术上我有掌握的能力,它的文字我有吸收的能力,对这

主题我也有热诚,所以才会选它为我的中长篇翻译处女作。

这真的是「中长篇」!该原文约有 150,000 bytes,(对原作者的费力

及慷慨,在此表示敬意!)中译后约 123,000 bytes,(由此可见中文之精

简﹑扼要﹑表现力强,若以文言文为之……哇!不敢想象!)不要说我翻得

累,各位光是看着就很累!断断续续花了近七个礼拜的时间才把它完成,底

下就把这过程的酸甜苦辣提供出来,聊作感言﹑后跋吧!

中文翻译向有信﹑达﹑雅之最高准则。对纯文学而言,信达雅三者都必

须兼顾(我将「雅」的意思扩大解释了),才能让读者无视语言的隔阂,而

能尽情领略原作的思想与风采。我所能见到最好的例子,就是罗珞珈译﹑志

文出版的《老人与海》一书,吾尝中英并列以观,醍醐灌顶之乐也。对哲学

类而言,微言大义,字字珠玑,「信」无疑是终极(或更严苛地说:唯一)

圭臬,任何取舍抉择都该以它为准。不过不要小看了它,一字之差,谬之千

里,对该主题没有绝对把握的话,会替地球多制造一份垃圾。在哲学这领域

,牟宗三先生译的康德三批判,实属翘楚。至于我们这种科技类,老实说,

大多数不怎么挑的读者但求「信」而已,甚至不必「太信」,只要「有」,

就勉强接受。侯捷就曾说:「我们的读者是最逆来顺受的一群……再挑就没

得看了!」是的,再挑,就只能死啃原文书看了。这无疑是科技族群的悲哀。

翻译科技文章,我的看法是:首要条件乃将原作的技术完整传达出来。

技术类不像通俗文学,很难由上下文来推敲某关键处真正含意,所以「信」

字该区首位。译者不应以自我的主见投射于译作上(不论它客观上看来是否

正确),顶多只能修正些显而易见的错误(如:笔误﹑排版错误),至于其

他进一步的更正﹑补充与诠释,则该以「译注」一节为之。

「信」做到了之后,「达」则是该追求的品质因子。毕竟译文是给人看

的,是给一般国人看的,而不是给密码学家看的。想做到「达」,就得勤练

「基本功」了:句型﹑连接词﹑特殊句构﹑惯用语﹑风俗典故……都是基本

功的范围,而且是双语皆然。没把这些练熟,就会翻译出我们市面上所见的

计算机书籍。白居易之诗老妪能解,虽不能至,但总该心向往之吧!至少也该

让人看得懂那是中国人写的,能通得过「文章作者国籍」的 Turing Test才

说得过去吧!

能做到信﹑达之后,已可算是忠实的译品了,读者也不太有认知上的困

难了(「文字上」没认知困难,但「内容上」则非臣之明所能逆睹也),已

可算是合格的作品。至于想更进一步追求风格的,就该朝「雅」字迈进。其

实我习惯把「雅」扩大解释成:合于原作的韵味。就像你不能以《命运交响

曲》的风格去指挥《田园》一样,若把「哈姆雷特」与「爱玛」说话的语气

译得一模一样,恐怕莎士比亚与珍‧奥斯汀地下有知,也不会放过你的。

追求单纯的「文雅」固然是好事,虽然翻译形同是个「再创造」,但若

因此将原作者所想表现的风韵破坏掉了,那就不算是「翻译」,顶多算是「

参考某作品」后的自我创作。译者应该认清自己的身份:原著的代言人,应

该将自我隐藏,而不该一味展现译者个人风格,除非他根本就不想尊重原作

。所以这篇译文,我尽量维持原作者的风格:平铺﹑简洁,偶尔带点繁复错

杂,间或参以诙谐。整体说来,你必须逐字细读,才能领会他想表达的技术

。在适当的地方,我也夹以【译注】来补充些细节。当然,若有任何误译之

处,完全是我的责任,但我不负任何实质责任(记得该文开头的「责任事项

」声明吗?)…… :-)

好不容易把它翻译完了,「自我隐藏」也够久了,终于能在这篇感言中

揭下面具,痛快淋漓地展现自我……。

科技类文章还有个令人头痛的问题:专有名词中译。

底下是本文第三段所提的那篇书评文章中,因杂志版面所限,未予刊载

的一个前言小段落,姑且放在这儿,当作此问题之我见吧:

行文中虽无法完全避免,但我会尽量降低中英夹杂的

比例。有些耳熟能详的英文缩写,会比相对应的中译名词

简短,如 OOP(对象导向程序设计),我也会采用前者,

反正文章开头有附缩写对照表。还有一些未有公认中译名

词的,或未能深契本意的,为避免百家争鸣,我也遵从吴

大猷先生于联经「理论物理丛书」序言中所提的观点:有

些名词不译为上。

将来各位或多或少都会有人从事科技传播的工作,想给您一个建议:中

文能力真的该加强;至少该把王鼎钧先生的《作文七巧》﹑《作文十九问》

熟读再三,才不会写出不忍卒睹的中文。行有余力,则以学《文心雕龙》﹑

《古文观止》矣!有志于翻译者,则该先练练基本功(前述在下的书评中,

有列举几本书供参考),研读前辈们的宏论,再多加揣摩斟酌,循序渐进,

必能有成。

翻译完了之后,有几项收获。比较明显的是:对 C++又多认识了一些。

以前看这份 FAQ时,「好象」全都看懂了,但是真的要把它翻译出来,就等

于是强迫自己彻彻底底的把它消化吸收掉,毕竟连自己都半懂不懂的东西,

怎么再传播出去呢?所以一些以前疏忽掉的地方,这回总算再度认真地研读

了一番。另一个收获,大概就是中打速度了。以前虽以「纯种注音输入法」

自豪,但有时对 1234567890 选字键不熟,还得用眼角余光偷瞄一下键盘,

才能顺利选同音字。经过近七周斯巴达式打字,现在对键盘上方数字键的「

感觉」好多了……。

最后要感谢 USENET comp.lang.c++ FAQ 的原作者 Marshall Cline 先

生,多亏了他,我们才有这么好的 FAQ文件可看;也多亏了他的慷慨同意,

不才敝人小弟在下我也才能中译之,我也才能有「润键盘」(听过「润笔」

一词吗)可领……(PS. 退格键坏了,请主编将最后这一句话砍掉……)。

接下来该翻译什么东西呢?USENET comp.object FAQ?偷看了一下,乖

乖……有近 600,000 bytes!!! 「祸从口出」﹑「驷不及舌」,前人教训得

是……上面那段计画当我没说过…… Menu -> Edit -> Undo...。


--  作者:卷积内核
--  发布时间:9/19/2007 8:18:00 AM

--  
C++语言常见问题解:索引

第1节:内容介绍

----------------

⊙1A:「FAQ 书」与「FAQ 文件」

⊙1B:目录

⊙1C:术语及常用的缩写

第2节:我该如何参与讨论?(发信之前请务必一读)

------------------------------------------------

Q1:我该在哪个讨论区中发问?

Q2:我该怎么提出「我的程序有毛病」的问题呢?

第3节:周遭的﹑管理上的事项

----------------------------

Q3:什么是 OOP?什么是 C++?

Q4:C++ 的优点是什么?

Q5:谁在用 C++?

Q6:有任何 C++ 标准化方案在进行吗?

Q7:该到哪里索取最新的 ANSI-C++ 标准草案?

Q8:C++ 对 ANSI-C 回溯兼容吗?

Q9:多久才能学会 C++?

第4节:C++ 的基础

------------------

Q10:什么是类别(class)?

Q11:什么是对象(object)?

Q12:什么是参考(reference)?

Q13:如果设定某值给参考会怎么样?

Q14:怎样才能将参考改设成别的对象?

Q15:何时该用参考,何时又该用指针?

Q16:行内函数是做什么的?

第5节:建构子和解构子

----------------------

Q17:建构子(constructor)是做什么的?

Q18:怎样才能让建构子呼叫另一个同处一室的建构子?

Q19:解构子(destructor)是做什么的?

第6节:运操作数多载

------------------

Q20:运操作数多载(operator overloading)是做什么的?

Q21:哪些运操作数可以/不能被多载?

Q22:怎样做一个 "**"「次方」运操作数?

第7节:伙伴

------------

Q23:伙伴(friend)是什么?

Q24:「伙伴」违反了封装性吗?

Q25:伙伴函数的优缺点?

Q26:「伙伴关系无继承及递移性」是什么意思?

Q27:应该替类别宣告个成员函数,还是伙伴函数?

第8节:输入/输出:<iostream.h> 和 <stdio.h>

---------------------------------------------

Q28:该怎样替 "class Fred" 提供输出功能?

Q29:为什么我该用 <iostream.h> 而不是以前的 <stdio.h>?

Q30:为什么我处理输入时,会超过档案的结尾?

Q31:为什么我的程序执行完第一次循环后,会对输入的要求不加理睬?

Q32:在 DOS 及 OS/2 的 binary 模式下,要怎样来 "reopen" cin 及 cout?

========== POSTING #2 ==========

第9节:自由内存管理

----------------------

Q33:"delete p" 会删去 "p" 指针,还是它指到的资料,"*p" ?

Q34:我能 "free()" 掉由 "new" 配置到的、"delete" 掉由 "malloc()" 配置到的

内存吗?

Q35:为什么该用 "new" 而不是老字号的 malloc() ?

Q36:为什么 C++ 不替 "new" 及 "delete" 搭配个 "realloc()" ?

Q37:我该怎样配置/释放数组?

Q38:万一我忘了将 "[]" 用在 "delete" 由 "new Fred[n]" 配置到的数组,会发生

什么事?

Q39:成员函数做 "delete this" 的动作是合法的(并且是好的)吗?

Q40:我该怎么用 new 来配置多维数组?

Q41:C++ 能不能做到在执行时期才指定数组的长度?

Q42:怎样确保某类别的对象都是用 "new" 建立的,而非区域或整体/静态变量?

第10节:除错与错误处理

------------------------

Q43:怎样处理建构子的错误?

Q44:如果建构子会丢出例外的话,该怎么处理它的资源?

第11节:Const 正确性

----------------------

Q45:什么是 "const correctness"?

Q46:我该早一点还是晚一点让东西有常数正确性?

Q47:什么是「const 成员函数」?

Q48:若我想在 "const" 成员函数内更新一个「看不见的」资料成员,该怎么做?

Q49:"const_cast" 会不会丧失最佳化的可能?

第12节:继承

--------------

Q50:「继承」对 C++ 来说很重要吗?

Q51:何时该用继承?

Q52:怎样在 C++ 中表现出继承?

Q53:把衍生类别的指针转型成指向它的基底,可以吗?

Q54:Derived* --> Base* 是正常的;那为什么 Derived** --> Base** 则否?

Q55:衍生类别的数组「不是」基底的数组,是否表示数组不好?

⊙12A:继承--虚拟函数

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

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

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

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

⊙12B:继承--一致性

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

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

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

⊙12C:继承--存取规则

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

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

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

⊙12D:继承--建构子与解构子

Q66:若基底类别的建构子呼叫一个虚拟函数,为什么衍生类别覆盖掉的那个虚拟函

数却不会被呼叫到?


--  作者:卷积内核
--  发布时间:9/19/2007 8:19:00 AM

--  
C++语言常见问题解:#1~#15

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

■□ 第2节:我该如何参与讨论?(发信之前请务必一读)

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

Q1:我该在哪个讨论区中发问?

Comp.lang.c++ 是讨论 C++语言本身最好的地方(譬如:C++ 程序设计﹑语法﹑风格

)。其它讨论区是用来讨论特定的系统(譬如:MS Windows 或是 UNIX),或是其它

和 C++语言不直接相关的主题(譬如:怎样使用你的编译器)。底下列出一些非常热

门的讨论区,并从它们的 FAQs 中摘录些片断,应该能让您明了它们最常讨论哪些课

题。

comp.os.ms-windows.programmer.tools

此区是用来讨论有关 Windows 软件开发系统工具的选择及使用。

comp.os.ms-windows.programmer.misc

此乃论及其余 Windows 软件开发之事项。

[有个 FAQ 列表,列出所有 comp.os.ms-windows.programmer.* 讨论区]

FAQ 5.7.1. 在 DLL 中存取 C++ 的对象类别

FAQ 6.1.1. 以 MDI 子窗口做出对话框 [用 OWL]

FAQ 6.2.1. 把禁能的选项致能起来 [用 MFC]

FAQ 8.1.5. 使用 windows.h 的 STRICT 符号定义

FAQ 10. 程序设计参考资料

comp.os.msdos.programmer

许多信件都是关于程序语言产品的(主要是 Borland 和 Microsoft)。

FAQ 301. 怎样才能读取字符而不 [等待] Enter 键?

FAQ 412. 怎样读取﹑建立﹑更改及删除磁盘标名?

FAQ 504. 怎样设定 COM 埠,以用它来传输资料?

FAQ 602. C 程序怎样才能送句柄给打印机?

FAQ 606. 怎样才能得知 Microsoft 鼠标的位置及按钮状态?

FAQ 707. 怎样写常驻程序(TSR)工具?

FAQ B0. 怎样连系 [Borland, Microsoft] 等公司?

[注意:这份 FAQ 不在 rtfm.mit.edu 里;而在 Simtel

(譬如 oak.oakland.edu) in /pub/msdos/info/faqp*.zip 以及 Garbo

(garbo.uwasa.fi) in /pc/doc-net/faqp*.zip]

comp.os.msdos.programmer.turbovision [Borland 的文字模式应用程序骨架]

comp.unix.programmer

FAQ 4.5) 怎样使用 popen() 开启行程以读写之?

FAQ 4.6) 怎样在 C 程序里 sleep() 一秒以内?

comp.unix.solaris (包含 SunOS 4.x 和 Solaris)

FAQ 4) Signal 入门

FAQ 5) 等待子行程 Exit

gnu.g++.help

FAQ: 到哪里找 C++ 的 demangler(反签名编码器)?

FAQ: 哪里有 Solaris 2.x 版的 gcc/g++ 位文件?

FAQ: 有 g++ 2.x 的文件吗?

gnu.g++.bug [g++ 的臭虫列表 -- 请见 g++ 的文件]

comp.lang.c

FAQ 1.10: 我搞胡涂了。NULL 保证一定是 0,但是 null 指针却不是?

FAQ 2.3: 那么,在 C 里头「指针和数组等价」是什么意思?

FAQ 4.2: [为什么 "printf("%d\n," i++ * i++);" 有问题?]

FAQ 7.1: 怎样写一个接收不定数目自变量的函数? [stdarg.h 或是 varargs.h]

FAQ 10.4: 怎么宣告一个指向某种函数的指针数组,而该函数的传回值为:

指向另一个传回字符指针的函数?

并请参考看看 comp.graphics、comp.sources.wanted、comp.programming,以及

comp.object(它的 FAQ 是个很棒的 OOP 入门、术语观念概论文件)。请记住:

comp.std.c++ 是专门讨论和研议中的 ANSI/ISO C++ 标准方案(下文会提)“直接

”相关的事项。

同时到上述信区和 comp.lang.c++ 去问同一个问题,几乎是没必要的(你是知道的

,特定系统信区的读者不用机器语言写程序)。只因你的问题「真的很要紧」,就到

处发问,是个很坏的习惯。如果你在「正确的」信区没得到回音,且认为你非得在这

儿发信不可,请至少考虑一下,将这儿的回信重导回原来那个适当的信区。

在任何信区发问之前,你应当先读读它的 FAQ。你想问的可能就在上面,这样就可省

下你发信的时间,以及全世界数以千计的人类读你的信的时间。回答已经是 FAQ问题

的人,可能会因为白白浪费时间而烦扰不已;他们也可能会给你错误或不完整的解答

,因为他们也没看过 FAQ。

「常见问题解答」文件每天 24 小时都可由 anonymous ftp (rtfm.mit.edu 的

/pub/usenet/comp.what.ever) 或是 e-mail server (寄一则内容为 "help" 的信到

mail-server@rtfm.mit.edu) 来取得。欲知详情,请见 "Introduction to the

*.answers newsgroups" 这份文件,它在 news.answers 或 news.announce.newusers

(这儿还有许多必须一读的文件)中找到。

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

Q2:我该怎么提出「我的程序有毛病」的问题呢?

底下是一些建议,让 comp.lang.c++ 的读者能帮你解决程序设计的问题。

1. 请读读上一个问题,以确定你的问题是针对 C++语言本身,而和你的程序设计系

统(譬如:绘图、打印机、设备……)或是编译环境(譬如:「整合环境挂了」

、「怎样消除xxxx警告讯息」、「怎样连结链接库」)完全无关。如果你想知道

为什么你 OWL程序中的虚拟函数 CmOk() 没被呼叫到,你的问题可能比较适合放

在 Windows程序设计的信区。如果你能写个独立的小程序,而它会让编译器产生


--  作者:卷积内核
--  发布时间:9/19/2007 8:19:00 AM

--  
C++语言常见问题解:#16~#32

Q16:行内函数是做什么的?

行内函数(inline function)是个程序代码会塞入呼叫者所在之处的函数。就像宏

一样,行内函数免除了函数呼叫的额外负担,以增进效率,并且(尤其是!)还能让

编译器对它施以最佳化(程序融合 "procedural integration")。不过和宏不同

的是:它只会对所有自变量求一次的值(在语意上,该“函数呼叫”和正常函数一样,

只是比较快速罢了),以避免某些不易察觉的宏错误。此外,它还会检测自变量的型

态,做必要的型别转换(宏对你有害;除非绝对必要,否则别再用它了)。

注意:过度使用行内函数会让程序代码肥胖,于分页(paging)环境下反而有负面的性

能影响。

宣告法:在函数定义处使用 "inline" 关键词:

inline void f(int i, char c) { /*...*/ }

或者是在类别内将定义包括进去:

class Fred {

public:

void f(int i, char c) { /*...*/ }

};

或是在类别外头,以 "inline" 来定义该成员函数:

class Fred {

public:

void f(int i, char c);

};

inline void Fred::f(int i, char c) { /*...*/ }

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

■□ 第5节:建构子和解构子

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

Q17:建构子(constructor)是做什么的?

建构子乃用来从零开始建立对象。

建构子就像个「初始化函数」;它把一堆散乱的字节成一个活生生的对象。最低限

度它会初始化内部用到的字段元,也可能会配置所须的资源(内存、档案、semaphore

、socket 等等)。

"ctor" 是建构子 constructor 最常见的缩写。

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

Q18:怎样才能让建构子呼叫另一个同处一室的建构子?

没有办法。

原因是:如果你呼叫另一个建构子,编译器会初始化一个暂时的区域性对象;但并没

有初始化“这个”你想要的对象。你可以用预设参数(default parameter),将两

个建构子合并起来,或是在私有的 "init()" 成员函数中共享它们的程序代码。

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

Q19:解构子(destructor)是做什么的?

解构子乃对象之葬礼。

解构子是用来释放该对象所配置到的资源,譬如:Lock 类别可能会锁住一个

semaphore,解构子则用来释放它。最常见的例子是:当建构子用了 "new" 以后,解

构子用 "delete"。

解构子是个「去死吧」的运作行为(method),通常缩写为 "dtor"。

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

■□ 第6节:运操作数多载

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

Q20:运操作数多载(operator overloading)是做什么的?

它可让使用类别的人以直觉来操作之。

运操作数多载让 C/C++ 的运操作数,能对自订的型态(对象类别)赋予自订的意义。它

们形同是函数呼叫的语法糖衣 (syntactic sugar):

class Fred {

public:

//...

};

#if 0

Fred add(Fred, Fred); //没有运操作数多载

Fred mul(Fred, Fred);

#else

Fred operator+(Fred, Fred); //有运操作数多载

Fred operator*(Fred, Fred);

#endif

Fred f(Fred a, Fred b, Fred c)

{

#if 0

return add(add(mul(a,b), mul(b,c)), mul(c,a)); //没有...

#else

return a*b + b*c + c*a; //有...

#endif

}

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

Q21:哪些运操作数可以/不能被多载?

大部份都可以被多载。

不能的 C 运操作数有 "." 和 "?:"(和以技术上来说,可算是运操作数的 "sizeof")。

C++ 增加了些自己的运操作数,其中除了 "::" 和 ".*". 之外都可以被多载。

底下是个足标(subscript)运操作数的例子(它会传回一个参考)。最前面是“不用

”多载的:

class Array {

public:

#if 0

int& elem(unsigned i) { if (i>99) error(); return data[i]; }

#else

int& operator[] (unsigned i) { if (i>99) error(); return data[i]; }

#endif

private:

int data[100];

};

main()

{

Array a;

#if 0

a.elem(10) = 42;

a.elem(12) += a.elem(13);

#else

a[10] = 42;

a[12] += a[13];

#endif

}

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

Q22:怎样做一个 "**"「次方」运操作数?

无解。

运操作数的名称、优先序、结合律以及元数(arity)都被语言所定死了。C++ 里没有

"**" 运操作数,所以你无法替类别订做一个它。


--  作者:卷积内核
--  发布时间:9/19/2007 8:19:00 AM

--  
C++语言常见问题解:#33~#53

== Part 2/4 ============================

comp.lang.c++ Frequently Asked Questions list (with answers, fortunately).

Copyright (C) 1991-96 Marshall P. Cline, Ph.D.

Posting 2 of 4.

Posting #1 explains copying permissions, (no)warranty, table-of-contents, etc

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

■□ 第9节:自由内存管理

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

Q33:"delete p" 会删去 "p" 指针,还是它指到的资料,"*p" ?

该指针指到的资料。

"delete" 真正的意思是:「删去指针所指到的东西」(delete the thing pointed

to by)。同样的英文误用也发生在 C 语言的「『释放』指针所指向的内存」上

("free(p)" 真正的意思是:"free_the_stuff_pointed_to_by(p)" )。

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

Q34:我能 "free()" 掉由 "new" 配置到的、"delete" 掉由 "malloc()" 配置到的

内存吗?

不行。

在同一个程序里,使用 malloc/free 及 new/delete 是完全合法、合理、安全的;

但 free 掉由 new 配置到的,或 delete 掉由 malloc 配置到的指针则是不合法、

不合理、该被痛骂一顿的。

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

Q35:为什么该用 "new" 而不是老字号的 malloc() ?

建构子/解构子、型别安全性、可被覆盖(overridability)。

建构子/解构子:和 "malloc(sizeof(Fred))" 不同,"new Fred()" 还会去呼叫

Fred 的建构子。同理,"delete p" 会去呼叫 "*p" 的解构子。

型别安全性:malloc() 会传回一个不具型别安全的 "void*",而 "new Fred()" 则

会传回正确型态的指针(一个 "Fred*")。

可被覆盖:"new" 是个可被对象类别覆盖的运操作数,而 "malloc" 不是以「各个类别

」作为覆盖的基准。

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

Q36:为什么 C++ 不替 "new" 及 "delete" 搭配个 "realloc()" ?

避免你产生意外。

当 realloc() 要拷贝配置区时,它做的是「逐位 bitwise」的拷贝,这会弄坏大

部份的 C++ 对象。不过 C++ 的对象应该要能自我拷贝才对:用它们自己的拷贝建构

子或设定运操作数。

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

Q37:我该怎样配置/释放数组?

用 new[] 和 delete[] :

Fred* p = new Fred[100];

//...

delete [] p;

每当你在 "new" 表达式中用了 "[...]",你就必须在 "delete" 陈述中使用 "[]"。

^^^^

这语法是必要的,因为「指向单一元素的指针」与「指向一个数组的指针」在语法上

并无法区分开来。

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

Q38:万一我忘了将 "[]" 用在 "delete" 由 "new Fred[n]" 配置到的数组,会发生

什么事?

灾难。

这是程序者的--而不是编译器的--责任,去确保 new[] 与 delete[] 的正确配

对。若你弄错了,编译器不会产生任何编译期或执行期的错误讯息。堆积(heap)被

破坏是最可能的结局,或是更糟的,你的程序会当掉。

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

Q39:成员函数做 "delete this" 的动作是合法的(并且是好的)吗?

只要你小心的话就没事。

我所谓的「小心」是:

1) 你得 100% 确定 "this" 是由 "new" 配置来的(而非 "new[]",亦非自订的

"new" 版本,一定要是最原始的 "new")。

2) 你得 100% 确定该成员函数是此对象最后一个会呼叫到的。

3) 做完自杀的动作 ("delete this;") 后,你不能再去碰 "this" 的对象了,包

括资料及运作行为在内。

4) 做完自杀的动作 ("delete this;") 后,你不能再去碰 "this" 指针了。

换句话说,你不能查看它﹑将它与其它指针或是 NULL 相比较﹑印出其值﹑

对它转型﹑对它做任何事情。

很自然的,这项警告也适用于:当 "this" 是个指向基底类别的指针,而解构子不是

virtual 的场合。

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

Q40:我该怎么用 new 来配置多维数组?

有很多方法,端视你对数组大小的伸缩性之要求而定。极端一点的情形,如果你在编

译期就知道所有数组的维度,你可以静态地配置(就像 C 一样):

class Fred { /*...*/ };

void manipulateArray()

{

Fred matrix[10][20];

//使用 matrix[i][j]...

//不须特地去释放该数组

}

另一个极端情况,如果你希望该矩阵的每个小块都能不一样大,你可以在自由内存

里配置之:

void manipulateArray(unsigned nrows, unsigned ncols[])

//'nrows' 是该数组之列数。

//所以合法的列数为 (0, nrows-1) 开区间。

//'ncols[r]' 则是 'r' 列的行数 ('r' 值域为 [0..nrows-1])。

{

Fred** matrix = new Fred*[nrows];


--  作者:卷积内核
--  发布时间:9/19/2007 8:20:00 AM

--  
== Part 3/4 ============================

comp.lang.c++ Frequently Asked Questions list (with answers, fortunately).

Copyright (C) 1991-96 Marshall P. Cline, Ph.D.

Posting 3 of 4.

Posting #1 explains copying permissions, (no)warranty, table-of-contents, etc

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

■□ 第14节:程序风格指导

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

Q81:有任何好的 C++ 程序写作的标准吗?

感谢您阅读这份文件,而不是再发明自己的一套。

但是请不要在 comp.lang.c++ 里问这问题。几乎所有软件工程师,或多或少都把这

种东西看成是「大玩具」。而且,一些想成为 C++ 程序撰写标准的东西,是由那些

不熟悉这语言及方法论的人弄出来的,所以最后它只能成为「过去式」的标准。这种

「摆错位置」的现象,让大家对程序写作标准产生不信任感。

很明显的,在 comp.lang.c++ 问这问题的人,是想使自己更精进,不会因自己的无

知而绊倒,然而一些回答却只是让情况更糟而已。

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

Q82:程序撰写标准是必要的吗?有它就够了吗?

程序撰写标准不会让不懂 OO 的人变懂;只有训练及经验才有可能。如果它有用处的

话,那就是抑制住那些琐碎无关紧要的程序片段--当大机构想把零散的程序设计组

织整合起来时,这些片段常常会出现。

但事实上你要的不光是这种标准而已。它们提供的架构让新手少去担心一些自由度,

但是系统化的方法论会比这些好看的标准做得更好。组织机构需要的是一致性的设计

与实行“哲学”,譬如:强型别或弱型别?用指针还是参考接口? stream I/O 还是

stdio? C++ 程序该不该呼叫 C 的?反过来呢? ABC 该怎么用?继承该用为实作的

技巧还是特异化的技巧?该用哪一种测试策略?一一去检查吗?该不该为每个资料成

员都提供一致的 "get" 和 "set" 接口?接口该由外往内还是由内往外设计?错误状

况该用 try/catch/throw 还是传回值来处理?……等等。

我们需要的是详细的“设计”部份的「半标准」。我推荐一个三段式标准:训练﹑谘

询顾问以及链接库。训练乃提供「密集教学」,咨询顾问让 OO 观念深刻化,而非仅

仅是被教过而已,高品质的链接库则是提供「长程的教学」。上述三种培训都有很热

门的市场景况。(【译注】无疑的,这是指美﹑加地区。)接受过上述培训的组织都

有如此的忠告:「买现成的吧,不要自己硬干 (Buy, Don't Build.)。」买链接库,

买训练课程,买开发工具,买咨询顾问。想靠自学来达到成功的工具厂商及应用/系

统厂商,都会发现成功很困难。

【译注】这一段十分具有参考价值。不过有些背景资料得提供给各位参考。别忘了:

作者是美国人,是以该地为背景,且留意一下他所服务的公司是做什么的..

... :-) 唉!国内有这么多的专业顾问公司吗? :-<

少数人会说:程序撰写标准只是「理想」而已,但在上述的组织机构中,它仍有其必

要性。

底下的 FAQs 提供一些基本的指导惯例及风格。

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

Q83:我们的组织该以以往 C 的经验来决定程序撰写标准吗?

No!

不论你的 C 经验有多丰富,不论你有多高深的 C 能力,好的 C 程序员并不会让你

直接就成为好的 C++ 程序员。从 C 移到 C++ 并不仅是学习 "++" 的语法语意而已

,一个组织想达到 OOP 的境界,却未将 "OO" 的精神放进 OOP 里的话,只是自欺罢

了;会计的资产负债表会把他们的愚蠢显现出来。

C++ 程序撰写标准应该由 C++ 专家来调整,不妨先在 comp.lang.c++ 里头问问题(

但是不要用 "coding standard" 这种字眼;只要这样子问:「这种技巧有何优缺点

?」)。找个能帮你避开陷阱的高手,上个训练课程,买链接库,看看「好的」程序

库是否合乎你的程序撰写标准。绝对不要光靠自己来制定标准,除非你对它已有某种

程度的掌握。没有标准总比有烂标准好,因为不恰当的「官方说法」会让不够聪明的

平民难以追随。现在 C++ 训练课程及链接库,已有十分兴盛的市场。

再提一件事:当某个东西炙手可热时,招摇撞骗者亦随之而生;务必三思而后行。也

要问一下从某处修过课的人,因为老手不见得也是个好教员。最后,选个懂得指导别

人的从业人员,而不是个对此语言/方法论只有过时知识的全职教师。

【译注】善哉斯言!

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

Q84:我该在函数中间或是开头来宣告区域变量?

在第一次用到它的地方附近。

对象在宣告的时候就会被初始化(被建构)。如果在初始化对象的地方没有足够的资

讯,直到函数中间才有的话,你可以在开头处初始个「空值」给它,等以后再「设定

」其值;你也可以在函数中间再初始个正确的东西给它。以执行效率来说,一开始就

让它有正确的值,会比先建立它,搞一搞它,之后再重建它来得好。以像 "String"

这种简单的例子来看,会有 350% 的速度差距。在你的系统上可能会不同;当然整个

系统可能不会降低到 300+%,但是“一定”会有不必要的性能衰退现象。

常见的反驳是:「我们会替对象的每个资料提供 "set" 运作行为,则建构时的额外

耗费就会分散开来。」这比效能负荷更糟,因为你添加了维护的梦靥。替每个资料提

供 "set" 运作行为就等于对资料不设防:你把内部实作技巧都显露出来了。你隐藏

到的只有成员对象的实体“名字”而已,但你用到的 List﹑String 和 float(举例

来说)型态都曝光了。通常维护会比 CPU 执行时间耗费的资源更多。

区域变量应该在靠近它第一次用到之处宣告。很抱歉,这和 C 老手的习惯不同,但

是「新的」不见得就是「不好的」。

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


--  作者:卷积内核
--  发布时间:9/19/2007 8:20:00 AM

--  
Q54: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".

如果一袋苹果可以当成一袋水果来传递,别人就可能把香蕉放到苹果袋里头去!

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

Q55:衍生类别的数组「不是」基底的数组,是否表示数组不好?

没错,「数组很烂」(开玩笑的 :-) 。

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>"

而非 "T[]"),这问题就可以在编译期被挑出来,而非在执行的时候。

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

● 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" 吗?


--  作者:卷积内核
--  发布时间:9/19/2007 8:21:00 AM

--  
Q94:Smalltalk/C++ 不同的继承,在现实里导致的结果是什么?

Smalltalk 让你做出不是子类别的子型别,做出不是子型别的子类别,它可让

Smalltalk 程序者不必操心该把哪种资料(位﹑表现型式﹑数据结构)放进类别里

面(譬如,你可能会把连结串行放到堆栈类别里)。毕竟,如果有人想要个以数组做

出的堆栈,他不必真的从堆栈继承过来;喜欢的话,他可以从数组类别 Array 中继

承过来,即使 ArrayBasedStack 并“不是”一种数组!)

在 C++ 中,你不可能不为此操心。只有机制(运作行为的程序代码),而非表现法(

资料位)可在子类别中被覆盖掉,所以,通常你“不要”把数据结构放进类别里比

较好。这会促成 Abstract Base Classes (ABCs) 的强烈使用需求。

我喜欢用 ATV 和 Maseratti 之间的差别来比喻。ATV(all terrain vehicle,越野

车)很好玩,因为你可以「到处逛」,任意开到田野﹑小溪﹑人行道等地。另一方面

,Maseratti 让你能高速行驶,但你只能在公路上面开。就算你喜欢「自由表现力」

,偏偏喜欢驶向丛林,但也请不要在 C++ 里这么做;它不适合。

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

Q95:学过「纯种」的 OOPL 之后才能学 C++ 吗?

不是(事实上,这样可能反而会害了你)。

(注意:Smalltalk 是个「纯种」的 OOPL,而 C++ 是个「混血」的 OOPL。)读这

之前,请先读读前面关于 C++ 与 Smalltalk 差别的 FAQs。

OOPL 的「纯粹性」,并不会让转移到 C++ 更容易些。事实上,典型的动态系结与非

子型别的继承,会让 Smalltalk 程序者更难学会 C++。Paradigm Shift 公司曾教过

数千人 OO 技术,我们注意到:有 Smalltalk 背景的人来学 C++,通常和那些根本

没碰过继承的人学起来差不多累。事实上,对动态型别的 OOPL(通常是,但不全都

是 Smalltalk)有高度使用经验的人,可能会“更难”学好,因为想把过去的习惯“

遗忘”,会比一开始就学习静态型别来得困难。

【译注】作者是以「语言学习」的角度来看的。事实上,若先有 Smalltalk 之类的

对象导向观念的背景知识,再来学 C++ 就不必再转换 "paradigm"--对象

导向的中心思维是不会变的,变的只是实行细节而已。

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

Q96:什么是 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。

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

■□ 第16节:参考与数值语意

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

Q97:什么是数值以及参考语意?哪一种在 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)没在这儿提到。

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

Q98:「虚拟数据」是什么?怎么样/为什么该在 C++ 里使用它?

虚拟资料让衍生类别能改变基底类别的对象成员所属的类别。严格说来,C++ 并不「

支持」虚拟数据,但可以仿真出来。不漂亮,但还能用。

欲仿真之,基底类别必须有个指针指向成员对象,衍生类别必须提供一个 "new" 到

的对象,以让原基底类别的指针所指到。该基底类别也要有一个以上正常的建构子,

以提供它们自己的参考(也是透过 "new"),且基底类别的解构子也要 "delete" 掉

被参考者。

举例来说,"Stack" 类别可能有个 Array 成员对象(采用指针),衍生类别

"StretchableStack" 可能会把基底类别的成员资料 "Array" 覆盖成

"StretchableArray"。想做到的话,StretchableArray 必须继承自 Array,这样子

Stack 就会有个 "Array*"。Stack 的正常建构子会用 "new Array" 来初始化它的

"Array*",但 Stack 也会有一个(可能是在 "protected:" 里)特别的建构子,以


--  作者:卷积内核
--  发布时间:9/19/2007 8:21:00 AM

--  
Q121:「泛型」(genericity)是什么?

另一种 "class template" 的说法。

不要和「一般化」(generality,指不要过于特定的解题)弄混了,「泛型」指的是

class template。

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

■□ 第20节:链接库

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

Q122:怎样拿到 "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。

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

Q123:怎样 ftp 到 "Numerical Recipes" 附的程序?

它是用卖的,把它放到网络上散布是违法的。不过它只需 $30 美元而已。

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

Q124:为什么我的执行档会这么大?

很多人对这么大的执行档感到惊讶,特别是当原始码只有一点点而已。例如一个简单

的 "hello world" 程序居然会产生大家都想不到的大小(40+K bytes)。

一个原因是:有些 C++ 执行期链接库被连结进去了。有多少被连结进去,就要看看

你用到多少,以及编译器把链接库切割成多少块而定。例如,iostream 很大,包含

一大堆类别及虚拟函数,即使你只用到一点点,因为各组件之间的交互参考依存关系

,可能会把整个 iostream 程序代码都塞进来了。(【译注】如果 linker 做得好的话

,应该能把完全用不到的组件 object code 砍掉,不随之塞入你的执行档中。)

不要用静态的,改用动态连结的链接库版本,就可以使你的程序变小。

欲知详情,请看看你的编译器手册,或是寻求厂商的技术支持。

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

■□ 第21节:特定系统的细节

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

Q125:GNU C++ (g++) 把小程序造出大大的执行档,为什么?

libg++(g++ 用到的链接库)可能在编译时带有除错的信息(-g)。有些机器上,不

带除错信息地重新编译它,会省下很大的磁盘空间(~1 MB;缺点是:不能追踪到

libg++ 的呼叫)。仅仅 "strip" 掉执行档,比不上先用 -g 重新编译,再 "strip"

掉 a.out 档来得有效。

用 "size a.out" 来看看执行码的程序与资料区段到底占了多大空间,而不要用

"ls -s a.out" 这种包括了符号表格(symbol table)的方式。

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

Q126:有 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".

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

Q127:什么是 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 将包含例外处理。

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

Q128:如果签名编码标准化了,我能否将不同厂商编译器产生的程序代码连结起来?

简短的回答:可能不行。

换句话说,有人希望标准化的签名编码规则能并入拟议中的 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 时,执行期的例外处理系统如何得知哪一个区域对象该被解


--  作者:卷积内核
--  发布时间:9/19/2007 8:22:00 AM

--  
== Part 4/4 ============================

comp.lang.c++ Frequently Asked Questions list (with answers, fortunately).

Copyright (C) 1991-96 Marshall P. Cline, Ph.D.

Posting 4 of 4.

Posting #1 explains copying permissions, (no)warranty, table-of-contents, etc

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

■□ 第17节:和 C 连结/和 C 的关系

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

Q105:怎样从 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, ...);

}

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

Q106:怎样才能建一个 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++ 编译器可能要是同一个牌子的,而且是兼容的版本(亦即:有相

同的呼叫惯例等等)。

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

Q107:为什么 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 编译器不懂该怎么对多重及虚拟继承的指针做转型。

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

Q109:C 的函数能不能存取 C++ 类别的对象资料?

有时可以。

(请先读一读前一则关于和 C 函数间传递 C++ 对象的 FAQ。)

你可以安全地从 C 函数中存取 C++ 对象的资料,只要 C++ 的对象类别:

* 没有虚拟函数(包含继承下来的虚拟函数).

* 所有资料都在同一个存取等级中 (private/protected/public).


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
125.000ms