转贴:C++语言常见问题解:#1 ~ #15
这是我从台湾的http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-Chinese/发现的《C++ Frequently Asked Questions》的繁体翻译,作者是:叶秉哲,也是《C++ Programming Language》3/e繁体版的译者,该文章是非常的好,出于学习用途而将它转贴,本人未取得作者的授权,原文章的版权仍然归属原作者.
C++语言常见问题解
=======================================================
■□ 第2节:我该如何参与讨论?(发信之前请务必一读)
=======================================================
Q1:我该在哪个讨论区中发问?
Comp.lang.c++ 是讨论 C++语言本身最好的地方(譬如:C++ 程序设计?p语法?p风格
)。其它讨论区是用来讨论特定的系统(譬如: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. 怎样读取?p建立?p更改及删除磁盘标名?
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," 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程序设计的信区。如果你能写个独立的小程序,而它会让编译器产生
和你那个 OWL程序同样的错误讯息或行为的话,就可以放到 comp.lang.c++ 了,
其它系统的 C++程序员可能帮得上忙。
2. 「信件标题」字段要有意义。像是「C++ 程序」这样的标题太空泛了,「new 一
个多维数组的问题」就很好。不要用一堆惊叹号,穷嚷嚷着「救命啊」,或是开
玩笑的用「SEX SEX SEX」这种标题。如果你认为该问题和你的编译器有关,最好
在标题栏中道出编译器和版本编号。
3. 列出完整的、可编译得过去的程序代码。要从人类的语言叙述里,去除错或是重建
回一个程序,是极为困难的事。「完整的程序代码」指的是:任何被用到的型别、
函数都要宣告出来,被用到的标头档都要 #include 进来……等等。请将程序代码
裁减到只留必要的部份,我们并不需要那些执行起来(甚至连结时)“有用的”
东西,我们只须能重现出你的错误讯息(可能在不同的编译器中)就行了。「可
编译得过去」指的是:不要含有一堆未批注掉的 ... 这种删节号,或是各行行首
的行号:
14: #include <iostream.h>
15: class Foo { ... }; // 像这样就是很讨人厌的东西!
将你的程序组织成线性结构,不要让我们再切割、制造些标头档案。请仔细输入
你的程序代码--我们通常不容易判断:某个地方只是你的打字错误,抑或它真的
就是你的问题所在。尽量改用编辑器的「剪贴」或「插入档案」功能。
4. 列出你用的编译器、编译器版本,以及你使用的系统。我知道我刚刚说过:特定
系统的问题要去特定的信区发问,但和编译器有关的信息,常常对侦查问题有帮
助(「喔,我记得 Acme 1.2 在这方面有很多毛病」),这也顺便提醒了那些编
译器的用户:小心那些毛病。
5. 把编译器、连结器的选项写出来,以及你用来建程序所用的链接库。
6. 把错误讯息和何处发生错误的资料写出来。像是「虚拟函数不能用了」并没告诉
我们这是个编译时段、连结时段还是执行期的问题。如果这问题是执行期发生的
,请把它的行为,和任何相关的系统设定信息列出来。
7. 在签名档中列出真的能用的 e-mail 地址。如果你信件的 "From:" 一栏有错的话
,请通知你的系统管理者。在它修复前,于你的信件标头中加入 "Reply-To:" 一
栏,填上你正确的 e-mail 地址。
8. 请读读这份 FAQ 的其它部份--可能你的问题,或是很相关的问题就在这儿。
谢谢您,并希望以上的建议能协助您找到问题的解答。
===================================
■□ 第3节:周遭的?p管理上的事项
===================================
Q3:什么是 OOP?什么是 C++?
对象导向(OO)程序技术,是我们所知发展大型而复杂的软件系统最好的方法。
C++ 是个对象导向的程序语言。C++ 可当成一个对象导向程序语言(OOPL),亦可只
当成一个“更好的 C 语言”来使用。不过,若你只把它当成“更好的 C”,你就无
法获得对象导向程序设计的好处。
提一则 OO 的广告词:软件工业刻正无法应付大型而复杂的软件系统需求。但这正是
肇因于我们的「成果」:我们过去的成功促使大家要求得更多,不幸的是,这份市场
的渴求却是「结构化」分析(analysis)?p设计(design)和程序设计所无法满足的
。因此,我们才得发展一个更好的典范(paradigm)。
========================================
Q4:C++ 的优点是什么?
「C++ 的成长」:C++ 是到目前为止最受欢迎的语言。每 7.5到 9个月 C++的使用者
都会加倍。「懂 C++」是个很好的求职资格(但你必须把它当成 OOPL,而不只是一
个更好的 C 来用才行)。
「封装性 encapsulation」:藉由隐藏内部的数据结构,让我们可以改变系统的某部
份,而不必更动其它部份。我们为软件组件(称之为 class,类别)提供一个安全的
接口,用户只碰得到这个接口而已;而相对起来比较容易变动的接口「实作」部份,
就被封装起来(就像被包在胶囊里),以避免用户过于依赖他一时的实作决定。在比
较简单的 C 里头,可由模块内的静态(static)数据来办到,以避免其它模块存取
到它。
「多重案例 multiple instances」:典型的 C 语言「封装」方法(刚才有提),做
不到多重的资料案例(我们很难替模块的 "static" 资料做出多重案例)。如果在 C
中要做到的话,我们得使用 "struct" 结构(但是它没有「封装性」)。在 C++里,
我们可用 "class"(对象类别)来做到多重案例与封装性:"public"公共部份包含了
它的接口(通常这里会有个特别的函数:成员函数),"private" 私有部份包含了它
的实作细节(通常这儿就是内部数据结构的所在)。
「行内函数呼叫」:在 C 中,可以在 struct 里放个 "void*"(该存取函数 [access
functions] 会用到指针转型)来达到「封装的 structs」。这样会丧失型别安全性
,而且会造成过多的函数呼叫,即使你只存取结构内的小小字段(假如你允许直接存
取结构内字段的话,它内部的数据结构就很难再变更了,因为你的程序有太多地方“
依赖”它以前的样子)。函数呼叫的额外负担不大,但是会累积起来。C++ 的类别允
许函数作 "inline" 行内扩展,就有以下好处:封装的安全性,多重案例的方便
性,直接存取的速度。而且,编译器也会检查行内函数的参数,这就比 C 的
#define 宏更好了。
「多载运操作数」:C++ 能对对象类别的运操作数加以多载(overload),以合乎我们的
直觉(譬如,"myString + yourString" 可做字符串串接,"myDate++"可用来递增日期
,"z1 * z2" 可将两复数 z1 及 z2 相乘,"a[i]" 可用来存取 "a" 这个连结串行的
第 i 个元素……等等)。你甚至可以做出个“聪明的指针”(smart pointer),以指
向磁盘或其它地方去("x = *p" 可 dereference [解参用] 指针,也就可以在磁盘
中找到 p 所“指到”的地方,并传回其值)。这可让使用者以切近该问题的方式来
写程序,而非以机器的语言来解题。
【译注】STL (Standard Template Library) 就大量利用到「聪明的指针」功能。
「继承性 inheritance」:我们还只是在表层而已,事实上,我们还没进入「对象导
向」的部份呢!假设你有个 Stack 堆栈型态,有 push?ppop 运算。若你还想要个
InvertableStack 型态,它“很像”Stack,只是它还有个 "invert" 运算。以 C 的
方式,你不是得修改现存的 Stack模块(如果它在其它地方也用到的话,就麻烦了
),就是得把 Stack拷贝到另一个档案,再加以修改之(这会导致过多重复的程序
码、容易破坏到 InvertableStack 里某些源自 Stack 的小地方,尤有甚者,得维护
双倍的程序代码)。C++提供了更干净的解决法:继承。你可以说:「InvertableStack
继承了 Stack的一切,且 InvertableStack又添加了 invert 运算。」这样子就好了
!Stack本身仍然是封闭的(未被更动到),而 InvertableStack也没重复 push/pop
等的程序代码。
「多型」与「动态系结」:OOP 真正的力量不仅是继承性,还有把 InvertableStack
当成是一个 Stack来传递的能力。这是安全的,因为(至少在 C++里)此乃「是一个
……」的关系("is-a" relation),透过公共继承达到的(亦即:InvertableStack
“是一个”Stack,且它还能自我 invert 反转)。多型(polymorphism)与动态系
结(dynamic binding)最容易从实例来理解了,所以我提个典型的例子:绘图软件
得处理圆形?p方形?p矩形?p多边形及直线,这些都是「形状 shape」。大部份绘图软
体的内部函数都需要个“形状”的参数(相对于某些像是“方形”这种特定的形状)
,譬如:当我们用鼠标选取某个图形,它就可能被拖曳放到屏幕某处。多型和动态系
结让程序能正确运作,即使编译器只知道该参数是个「形状」,而不知它到底是什么
形状。我们再假设刚才提到的 "pick_and_drag(Shape*)" 函数于星期二编译好了,
到了星期三,你打算再加个六边形。听起来很奇怪,但 pick_and_drag() 仍然能够
处理这个六边形,即使当 pick_and_drag() 编译时六边形还不存在!(若你明了
C++ 是怎么做的,它就再也不惊异了--但它仍然是很方便的!)
========================================
Q5:谁在用 C++?
很多很多的公司及政府部门。相当的多。
统计上来看:当你正在读这份 FAQ文字时,就有 5 个人正成为 C++的程序员。
========================================
Q6:有任何 C++ 标准化方案在进行吗?
有的;ANSI(美国的)和 ISO(国际的)组织正密切合作。ANSI-C++ 委员会称为
"X3J16" ,而 ISO C++ 标准团体称为 "WG21"。ANSI/ISO C++ 的标准过程中包含了
这些人:
AT&T, IBM, DEC, HP, Sun, MS, Borland, Zortech, Apple, OSF 等等等等。每次开
会约有 70 人,他们来自美、英、日、德、瑞典、丹麦、法国……(他们都有「区域
性」的委员会,派遣正式代表并主导「区域性」的会议)。
========================================
Q7:该到哪里索取最新的 ANSI-C++ 标准草案?
ISO Committee Draft for C++ 以及 ANSI C++ Draft(将要供 public review 的文
件)可如此取得:
http://www.cygnus.com/~mrs/wp-draft
你也可以拿到 Postscript 和 Adobe Acrobat 的版本:
ftp://research.att.com/dist/stdc++/WP
也能拿到 HTML 和 ASCII 的版本:
ftp://ftp.cygnus.com/pub/g++
也能拿到书面版本:
X3 Secretariat
1250 Eye Street NW
Suite 200
Washington, DC 20005
202-626-5738
你也可以用 email:
lbarra@itic.nw.dc.us (Lynn Barra)
注明要索取最新的 "Draft Proposed American National Standard for Information
Systems -- Programming Language C++",文件编号 CD14882。它通常是用2日期的
FedEx(美国境内)来递送的,所以很快就能收到。
========================================
Q8:C++ 对 ANSI-C 回溯兼容吗?
几乎是。
C++ 尽可能地和 C 兼容,但不能更兼容了。事实上,主要的不同在于 C++ 要求函数
原型:"f()" 宣告的是无参数的函数(在 C 里,"f()" 和 "f(...)" 是一样的)。
还有些细微的差别,像在 C++ 里 sizeof('x') 等同于 sizeof(char),但在 C 里面
却是等同于 sizeof(int)。 而且,C++ 直接就把结构的卷标(tag)当成是型别的名
字,但 C 就需要加个 "struct" 字("typedef struct Fred Fred" 这种技巧仍然能
用,但在 C++ 中是累赘的)。
========================================
Q9:多久才能学会 C++?
像 Paradigm Shift 公司,成功地教授过标准的工业界「短期课程」,将大学一学期
的课压缩到一周 40 小时。然而真正的精通得由实际经验而来:没有东西能取代时间
。需动手做的指定专题是必要的,因为它们能将你的观念「凝固成形」。
大约要 6-12 个月才能流利使用 C++/OOP,如果身边有高手的话,费时会短些;反之
若没有个“好的”通用型 C++对象链接库,则会耗时更久。想成为顾问级的高手,则
约需 3 年。
有些人却根本办不到。除非你是可造之材,且有强烈的个人驱动力,否则你也做不到
。「孺子可教」最起码的要求是:你必须能「觉今是而昨非」。「驱动力」最起码的
要求是:你愿意多投入时间精力(改变思考的方式〔典范转移 paradigm shift〕要
远比学些新的东西来得困难)。
=========================
■□ 第4节:C++ 的基础
=========================
Q10:什么是类别(class)?
对象导向系统的基石。
类别是用来定义资料型态(data type)的,就像 C 的 struct 一样。
以信息科学术语来说,一个型态包含了一组状态(state),以及在状态之间转移的
动作行为(operation)。因此 "int" 是个「型态」,因为它有一组状态,还有诸如
「加两个整数」、「整数相乘」等等的运作行为。同样的,「类别」提供一组(通常
是公共的)运算,及一组(通常是非公共的)数据域位,以代表该型态的案例所拥有
的抽象值。以 C 的角度来看,类别就是其成员(members)皆预设为 "private" 的
struct。
把 "int" 想成是个类别,它拥有 "operator++" 等等的运作行为(method)。
========================================
Q11:什么是对象(object)?
一块赋有某种语意的储存空间。
在宣告 "int i;" 之后,我们称「i 是个 int 型态的对象」。在 C++/OOP 里,「物
件」通常意指「类别的案例(an instance of a class)」,因此类别定义了数个物
件(案例)的行为。
========================================
Q12:什么是参考(reference)?
一个对象的“别名”(alias,另一个名称)。
参考通常用于传址呼叫(pass-by-reference):
void swap(int& i, int& j)
{
int tmp = i;
i = j;
j = tmp;
}
main()
{
int x, y;
//...
swap(x,y);
}
在这里 "i" 和 "j" 分别是是 main 函数中 "x" 与 "y" 的别名,换句话说,"i" 就
是 "x"--不是个指向 "x" 的指针,也不是 "x" 该值的复制品,而它的的确确就是
"x" 本身。你对 "i" 做的任何动作,都会反映到 "x" 上;反之亦然。
从最底层来看,参考最常用指针来实作,它的效果有点像 C 里头的「传指针呼叫」
(pass-by-pointer),但 "&" 取址运操作数由呼叫者换到被呼叫者之处了,你也要删
去所有的 "*" 运操作数。
========================================
Q13:如果设定某值给参考会怎么样?
会更动到被参考者(referrent,该「参考」所参考到的对象)。
记住:「参考」就是「被参考者」,因此动了参考就会改动到被参考者(「参考」是
「被参考者」的左值 "Lvalue"〔出现在设定陈述的左边〕)。
更进一步,我们也允许参考被传回。这样子函数呼叫就可放在设定陈述的左边,这对
运操作数多载的场合很有用。
========================================
Q14:怎样才能将参考改设成别的对象?
没有办法。
和指针不同,一旦参考被系结到某个对象,它就不能再被改设到其它对象去。「参考
」本身不是一个对象(它自己没有地址;「取参考的地址」只会得到被参考者的地址
;切记:「参考」就是「被参考者」)。
将「参考」与「被参考者」分离开来是不可能的。
========================================
Q15:何时该用参考,何时又该用指针?
可以时,用参考;必要时,就用指针。
当你不需要“重设”它时(见前一个问题),参考会比指针好。这通常意味着:在物
件类别的公共接口中参考最有用。参考大多用于对象的表层,而指针则多用于里层。
但有一个例外:当函数参数或传回值需要一个「临界」(sentinel)的参考值时,最
好是用指针来做,以 NULL 指针做为一个特别值(「参考」应该是个实质对象的「别
名」,而不是个解参用的〔dereferenced〕NULL 指针)。
注意:老资格的 C 程序员不喜欢参考,因为在父程序的地方,「参考」的语意并不
是那么明显。然而有了些 C++经验后,会发现这正是一种「信息隐藏」的作法,是利
而非弊。好比说,程序员应该以切近该问题的方式来写程序,而非以机器的语言来解
题。
========================================