C++语言常见问题解

C++语言常见问题解

C++语言常见问题解
出处 http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-Chinese/

这是我从台湾的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++ 程序设计﹑语法﹑风格
)。其它讨论区是用来讨论特定的系统(譬如: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程序设计的信区。如果你能写个独立的小程序,而它会让编译器产生
和你那个 OWL程序同样的错误讯息或行为的话,就可以放到 comp.lang.c++ 了,
其它系统的 C++程序员可能帮得上忙。

2. 「信件标题」字段要有意义。像是「C++ 程序」这样的标题太空泛了,「new 一
个多维数组的问题」就很好。不要用一堆惊叹号,穷嚷嚷着「救命啊」,或是开
玩笑的用「SEX SEX SEX」这种标题。如果你认为该问题和你的编译器有关,最好
在标题栏中道出编译器和版本编号。

3. 列出完整的、可编译得过去的程序代码。要从人类的语言叙述里,去除错或是重建
回一个程序,是极为困难的事。「完整的程序代码」指的是:任何被用到的型别、
函数都要宣告出来,被用到的标头档都要 #include 进来……等等。请将程序代码
裁减到只留必要的部份,我们并不需要那些执行起来(甚至连结时)“有用的”
东西,我们只须能重现出你的错误讯息(可能在不同的编译器中)就行了。「可
编译得过去」指的是:不要含有一堆未批注掉的 ... 这种删节号,或是各行行首
的行号:

14: #include
15: class Foo { ... }; // 像这样就是很讨人厌的东西!

将你的程序组织成线性结构,不要让我们再切割、制造些标头档案。请仔细输入
你的程序代码--我们通常不容易判断:某个地方只是你的打字错误,抑或它真的
就是你的问题所在。尽量改用编辑器的「剪贴」或「插入档案」功能。

4. 列出你用的编译器、编译器版本,以及你使用的系统。我知道我刚刚说过:特定
系统的问题要去特定的信区发问,但和编译器有关的信息,常常对侦查问题有帮
助(「喔,我记得 Acme 1.2 在这方面有很多毛病」),这也顺便提醒了那些编
译器的用户:小心那些毛病。

5. 把编译器、连结器的选项写出来,以及你用来建程序所用的链接库。

6. 把错误讯息和何处发生错误的资料写出来。像是「虚拟函数不能用了」并没告诉
我们这是个编译时段、连结时段还是执行期的问题。如果这问题是执行期发生的
,请把它的行为,和任何相关的系统设定信息列出来。

7. 在签名档中列出真的能用的 e-mail 地址。如果你信件的 "From:" 一栏有错的话
,请通知你的系统管理者。在它修复前,于你的信件标头中加入 "Reply-To:" 一
栏,填上你正确的 e-mail 地址。

8. 请读读这份 FAQ 的其它部份--可能你的问题,或是很相关的问题就在这儿。

谢谢您,并希望以上的建议能协助您找到问题的解答。


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

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

对象导向(OO)程序技术,是我们所知发展大型而复杂的软件系统最好的方法。

C++ 是个对象导向的程序语言。C++ 可当成一个对象导向程序语言(OOPL),亦可只
当成一个“更好的 C 语言”来使用。不过,若你只把它当成“更好的 C”,你就无
法获得对象导向程序设计的好处。

提一则 OO 的广告词:软件工业刻正无法应付大型而复杂的软件系统需求。但这正是
肇因于我们的「成果」:我们过去的成功促使大家要求得更多,不幸的是,这份市场
的渴求却是「结构化」分析(analysis)﹑设计(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﹑pop 运算。若你还想要个
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)最容易从实例来理解了,所以我提个典型的例子:绘图软件
得处理圆形﹑方形﹑矩形﹑多边形及直线,这些都是「形状 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++经验后,会发现这正是一种「信息隐藏」的作法,是利
而非弊。好比说,程序员应该以切近该问题的方式来写程序,而非以机器的语言来解
题。

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

 

作者Blog:http://blog.csdn.net/vcmfc/

 

 

 


C++语言常见问题解
出处 http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-Chinese/

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

C++语言常见问题解
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++ 里没有
"**" 运操作数,所以你无法替类别订做一个它。

还怀疑的话,考虑看看 "x ** y" 和 "x * (*y)",这两者是完全一样的(换句话说
,编译器会假设 "y" 是个指针)。此外,运操作数多载只是函数呼叫的语法糖衣而已
,虽然甜甜的,但本质上并未增加什么东西。我建议你多载 "pow(base,exponent)"
这个函数(它的倍精确度版本在 中)。

附带一提:operator^ 可以用,但它的优先序及结合律不符「次方」所需。


===================
■□ 第7节:伙伴
===================

Q23:伙伴(friend)是什么?

让别的类别或函数能存取到你的类别内部的东西。

伙伴可以是函数或其它类别。类别会对它的伙伴开放存取权限。正常情况下,程序员
会下意识﹑技术性地控制该类别的伙伴与运作行为(否则当你想更动类别时,还得先
有其它部份的拥有者之同意才行)。

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

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

若善用之,反而会「强化」封装性。

我们经常得将一个类别切成两半,当这两半各有不同的案例个数及生命期时。在此情
形之下,它们通常需要直接存取对方的内部(这两半“本来”是在同一个类别里面,
所以你并未“增加”存取数据结构的运作行为个数;你只是在“搬动”这些运作行为
所在之处而已)。最安全的实作方式,就是让这两半互为彼此的「伙伴」。

若你如上述般的使用伙伴,你依然是将私有的东西保持在私有的状态。遇到上述的情
况,如果还呆呆的想避免使用伙伴关系,许多人不是采用公共资料(糟透了!),就
是弄个公共的 get/set 存取运作行为来存取彼此的资料,事实上这些都破坏了封装
性。只有在类别的外面该私有资料「仍有其意义」(以使用者的角度来看)时,开放
出私有资料的存取运作行为才称得上是恰当的做法。多数情况下,「存取运作行为」
就和「公共资料」一样糟糕:它们对私有资料成员只隐其“名”而已,却未隐藏其“
存在”。

同样的,如果将「伙伴函数」做为另一种类别公共存取函数的语法,那就和违反封装
性的成员函数一样破坏了封装。换句话说,对象类别的伙伴及成员都是「封装的界线
」,如同「类别定义」本身一样。

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

Q25:伙伴函数的优缺点?

它提供了某种接口设计上的自由。

成员函数和伙伴函数都有同等的存取特权(100% 的权利),主要的差别在于:伙伴
函数用起来像是 "f(x)",而成员函数则是 "x.f()"。因此,伙伴函数可让对象类别
设计者挑选他看得最顺眼的语法,以降低维护成本。

伙伴函数主要的缺点在于:当你想做动态系结(dynamic binding)时,它需要额外
的程序代码。想做出「虚拟伙伴」的效果,该伙伴函数应该呼叫个隐藏的(通常是放在
"protected:" 里)虚拟成员函数;像这个样子:"void f(Base& b) { b.do_f(); }"
。衍生类别会覆盖(override)掉那个隐藏的成员函数("void Derived::do_f()")
,而不是该伙伴函数。

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

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

伙伴关系的特权性无法被继承下来:伙伴的衍生类别不必然还是伙伴(我把你当朋友
,但这不代表我也一定会信任你的孩子)。如果 "Base" 类别宣告了 "f()" 为它的
伙伴,"f()" 并不会自动对由 "Base" 衍生出来的 "Derived" 类别所多出来的部份
拥有特殊的存取权力。

伙伴关系的特权无递移性:伙伴类别的伙伴不必然还是原类别的伙伴(朋友的朋友不
一定也是朋友)。譬如,如果 "Fred" 类别宣告了 "Wilma" 类别为它的伙伴,而且
"Wilma" 类别宣告了 "f()" 为它的伙伴,则 "f()" 不见得对 "Fred" 有特殊的存取
权力。

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

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

可能的话,用成员函数;必要时,就用伙伴。

有时在语法上来看,伙伴比较好(譬如:在 "Fred" 类别中,伙伴函数可把 "Fred"
弄成是第二个参数,但在成员函数中则一定得放在第一个)。另一个好例子是:二元
中序式算数运操作数(譬如:"aComplex + aComplex" 可能应该定义成伙伴而非成员函
数,因为你想让 "aFloat + aComplex" 这种写法也能成立;回想一下:成员函数无
法提升它左侧的参数,因为那样会把引发该成员函数的对象所属之类别给改变掉)。

在其它情况下,请选成员函数而不要用伙伴函数。


====================================================
■□ 第8节:输入/输出: 和
====================================================

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

用伙伴函数 operator<<:

class Fred {
public:
friend ostream& operator<< (ostream& o, const Fred& fred)
{ return o << fred.i; }
//...
private:
int i; //只为了说明起见而设的
};

我们用伙伴而不用成员函数,因为 "Fred" 是第二个参数而非第一个。输入的功能亦
类似,只是要改写成:

istream& operator>> (istream& i, Fred& fred);
// ^^^^^------- 不是 "const Fred& fred"!

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

Q29:为什么我该用 而不是以前的 ?

增加型别安全、减少错误、增进效率、有延展性、提供衍生能力。

Printf 还好,而 scanf 除了容易写错之外也还算可以,然而和 C++ 的 I/O 系统相
比,它们都有其限制。C++ 的 I/O(用 "<<" 及 ">>" ),和 C( "printf()" 和
"scanf()" )相比:

* 型别安全--要做 I/O 的对象,编译器会静态地事先得知其型别,而不是动态地
由 "%" 一栏查知。

* 不易出错--冗余的信息会增加错误的机会。C++ 的 I/O 就不需要多余的 "%"。

* 更快速--printf 是个小型语言的「解译器」,该语言主要是由 "%" 这种东西
构成的;在执行期它用这些字段来选择正确的格式化方式。C++ 的 I/O 系统则是
静态的依各自变量真正的型别来挑选子程序,以增进执行效率。

* 延展性--C++ I/O 机制可在不改动原有程序代码的情况下,就加进使用者新设计
的型态(能想象如果大家同时把互不兼容的 "%" 字段塞入 printf 和 scanf,会
是怎样的混乱场面?!)。

* 可衍生(subclassable)--ostream 和 istream(C++ 的 FILE* 代替品)都是
真正的类别,因此可以被衍生下去。这意味着:你可以让其它自定的东西有着和
stream 雷同的外表与行为,但实际上做的却是你想做的特定事情。你自动就重用
了数以万计别人(你甚至不认识它们)写好的 I/O 程序代码,而他们也不需要知道
你所做的「延伸 stream」类别。

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

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

因为 eof(档案结尾)的状态,是到「将要超过档案结尾的动作」才会被设定。也就
是说,读档案的最后一个字节并不会设定 eof 的状态。

【译注】这也是 C 常见的错误。

如果你的程序像这样:

int i = 0;
while (! cin.eof()) {
cin >> x;
++i;
// work with x
}

你的 i 变量就会多了一。
你真正该做的是这样:

int i;
while (cin >> x) {
++i;
// work with x
}

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

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

因为读取数值的程序,把非数字的字符留在输入缓冲区 (input buffer) 里头了。

【译注】这也是 C,甚至 Pascal 常见的错误。

如果你的程序如下:

char name[1000];
int age;

for (;;) {
cout << "Name: ";
cin >> name;
cout << "Age: ";
cin >> age;
}

你应该这样写:

for (;;) {
cout << "Name: ";
cin >> name;
cout << "Age: ";
cin >> age;
cin.ignore(INT_MAX, '/n');
}

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

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

有这个问题,最典型的情况就是:有人想对 cin、cout 做 binary 的 I/O,但是作
业系统(像是 DOS 或 OS/2)却总是会做 CR-LF 的转换动作。

解决法:cin、cout、cerr 这些事先定义好的串流,都是 text 的串流,没有标准做
法能把它们弄成 binary 模式。把串流关掉再设法以 binary 模式 reopen 它们,可
能会导致不可预期的结果。

在这两种模式有不同行为的系统上,一定有办法让它们变成 binary 串流,但是你得
去查查该系统的文件。

--
Marshall Cline
--
Marshall P. Cline, Ph.D. / Paradigm Shift Inc / PO Box 5108 / Potsdam NY 13676
cline@sun.soe.clarkson.edu / 315-353-6100 / FAX: 315-353-6110

 

作者Blog:http://blog.csdn.net/vcmfc/

 

 

 

C++语言常见问题解
出处 http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-Chinese/

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

C++语言常见问题解
== 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];
for (unsigned r = 0; r < nrows; ++r)
matrix[r] = new Fred[ ncols[r] ];

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

//释放就是配置的反动作:
for (r = nrows; r > 0; --r)
delete [] matrix[r-1];
delete [] matrix;
}

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

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

可以。STL 有一个 vector template 提供这种行为。请参考“链接库”一节的 STL
项目。

不行。内建的数组型态必须在编译期就指定它的长度了。

可以,内建的数组可以在执行期才指定第一个索引的范围。譬如说,和上一则 FAQ
相较,如果你只需要第一个维度大小能够变动,你可以 new 一个数组的数组(而不
是数组指针的数组 "an array of pointers to arrays"):

const unsigned ncols = 100;
//'ncols' 不是执行期才决定的变量 (用来代表数组的行数)

class Fred { ... };

void manipulateArray(unsigned nrows)
//'nrows' 是执行期才决定的变量 (用来代表数组的列数)
{
Fred (*matrix)[ncols] = new Fred[nrows][ncols];

//用 matrix[i][j] 来处理

//deletion 是对象配置的逆运算:
delete [] matrix;
}

如果你不光是需要在执行期改变数组的第一个维度的话,就不能这样做了。

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

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

确定该类别的建构子都是 "private:" 的,并定义个 "friend" 或 "static" 函数,
来传回一个指向由 "new" 建造出来的对象(把建构子设成 "protected:",如果你想
要有衍生类别的话)。

class Fred { //只允许 Fred 动态地配置出来
public:
static Fred* create() { return new Fred(); }
static Fred* create(int i) { return new Fred(i); }
static Fred* create(const Fred& fred) { return new Fred(fred); }
private:
Fred();
Fred(int i);
Fred(const Fred& fred);
virtual ~Fred();
};

main()
{
Fred* p = Fred::create(5);
...
delete p;
}


===============================
■□ 第10节:除错与错误处理
===============================

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

丢出一个例外(throw an exception)。

建构子没有传回值,所以不可能采用它传回的错误码。因此,侦测建构子错误最好的
方法,就是丢出一个例外。

在 C++ 编译器尚未提供例外处理之前,我们可先把对象置于「半熟」的状态(譬如
:设个内部的状态位),用个查询子("inspector")来检查该位,就可让用户
查看该对象是否还活着。也可以用另一个成员函数来检查该位,若该对象没存活
下来,就做个「没动作」(或是更狠的像是 "abort()" )的程序。但这实在很丑陋。

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

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

对象里面的每个资料成员,都该自己收拾残局。

如果建构子丢出一个例外的话,该对象的解构子就“不会”执行。如果你的对象得回
复些曾做过的事情(像是配置内存、开启档案、锁定 semaphore),该对象内的资
料成员就“必须”记住这个「必须恢复的东西」。

举例来说:不要单单的把配置到的内存放入 "Fred*" 资料成员,而要放入一个「
聪明的指针」(smart pointer) 资料成员中;当该“聪明指针”死掉的话,它的解构
子就会删去 Fred 对象。

【译注】「聪明的指针」(smart pointer) 在 Q4 中有提到一点。


=============================
■□ 第11节:Const 正确性
=============================

Q45:什么是 "const correctness"?

好问题。

「常数正确性」乃使用 "const" 关键词,以确保常数对象不会被更动到。譬如:若
"f()" 函数接收一个 "String",且 "f()" 想确保 "String" 不会被改变,你可以:

* 传值呼叫 (pass by value): void f( String s ) { /*...*/ }
* 透过常数参考 (reference): void f(const String& s ) { /*...*/ }
* 透过常数指针 (pointer) : void f(const String* sptr) { /*...*/ }
* 但不能用非常数参考 : void f( String& s ) { /*...*/ }
* 也不能用非常数指针 : void f( String* sptr) { /*...*/ }

在接收 "const String&" 参数的函数里面,想更动到 "s" 的话,会产生个编译期的
错误;没有牺牲任何执行期的空间及速度。

宣告 "const" 参数也是另一种型别安全方法,就像一个常数字符串,它会“丧失”各
种可能会变更其内容的行为动作。如果你发现型别安全性质让你的系统正确地运作
(这是真的;特别是大型的系统),你会发现「常数正确性」亦如是。

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

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

越越越早越好。

延后补以常数正确性,会导致雪球效应:每次你在「这儿」用了 "const",你就得在
「那儿」加上四个以上的 "const"。

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

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

一个只检测(而不更动)其对象的成员函数。

class Fred {
public:
void f() const;
}; // ^^^^^--- 暗示说 "fred.f()" 不会改变到 "fred"

此乃意指:「抽象层次」的(用户可见的)对象状态不被改变(而不是许诺:该对象
的「每一个位内容」都不会被动到)。C++ 编译器不会对你许诺「每一个位」这
种事情,因为不是常数的别名(alias)就可能会修改对象的状态(把 "const" 指针
黏上某个对象,并不能担保该对象不被改变;它只能担保该对象不会「被该指针的动
作」所改变)。

【译注】请逐字细读上面这句话。

"const" 成员函数常被称作「查询子」(inspector),不是 "const" 的成员函数则
称为「更动子」(mutator)。

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

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

使用 "mutable" 或是 "const_cast"。
【译注】这是很新的 ANSI C++ RTTI (RunTime Type Information) 规定,Borland
C++ 4.0 就率先提供了 const_cast 运操作数。

少数的查询子需要对资料成员做些无害的改变(譬如:"Set" 对象可能想快取它上一
回所查到的东西,以加速下一次的查询)。此改变「无害」是指:此改变不会由对象
的外部接口察觉出来(否则,该运作行为就该叫做更动子,而非查询子了)。

这类情况下,会被更动的资料成员就该被标示成 "mutable"(把 "mutable" 关键词
放在该资料成员宣告处前面;也就是和你放 "const" 一样的地方),这会告诉编译
器:此数据成员允许 const 成员函数改变之。若你不能用 "mutable" 的话,可以用
"const_cast" 把 "this" 的「常数性」给转型掉。譬如,在 "Set::lookup() const"
里,你可以说:

Set* self = const_cast(this);

这行执行之后,"self" 的位内容就和 "this" 一样(譬如:"self==this"),但
是 "self" 是一个 "Set*" 而非 "const Set*" 了,所以你就可以用 "self" 去修改
"this" 指针所指向的对象。

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

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

理论上,是;实际上,否。

就算编译器没真正做好 "const_cast",欲避免 "const" 成员函数被呼叫时,会造成
缓存器快取区被清空的唯一方法,乃确保没有任何「非常数」的指针指向该对象。这
种情况很难得会发生(当对象在 const 成员函数被启用的范围内被建立出来;当所
有非 const 的成员函数在对象建立间启用,和 const 成员函数的启用被静态系结住
;当所有的启用也都是 "inline";当建构子本身就是 "inline";和当建构子所呼叫
的任何成员函数都是 inline 时)。

【译注】这一段话很难翻得好(好啦好啦!我功力不足... :-< ),所以附上原文:
Even if a compiler outlawed "const_cast", the only way to avoid flushing
the register cache across a "const" member function call would be to
ensure that there are no non-const pointers that alias the object. This
can only happen in rare cases (when the object is constructed in the scope
of the const member fn invocation, and when all the non-const member
function invocations between the object's construction and the const
member fn invocation are statically bound, and when every one of these
invocations is also "inline"d, and when the constructor itself is "inline"d,
and when any member fns the constructor calls are inline).


=====================
■□ 第12节:继承
=====================

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

是的。

「继承」是抽象化资料型态(abstract data type, ADT)与 OOP 的一大分野。

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

Q51:何时该用继承?

做为一个「特异化」(specialization) 的机制。

人类以两种角度来抽象化事物:「部份」(part-of) 和「种类」(kind-of)。福特汽
车“是一种”(is-a-kind-of-a) 车子,福特汽车“有”(has-a) 引擎、轮胎……等
等零件。「部份」的层次随着 ADT 的流行,已成为软件系统的一份子了;而「继承
」则添入了“另一个”重要的软件分解角度。

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

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

用 ": public" 语法:

class Car : public Vehicle {
//^^^^^^^^---- ": public" 读作「是一种」("is-a-kind-of-a")
//...
};

我们以几种方式来描述上面的关系:

* Car 是「一种」("a kind of a") Vehicle
* Car 乃「衍生自」("derived from") Vehicle
* Car 是个「特异化的」("a specialized") Vehicle
* Car 是 Vehicle 的「子类别」("subclass")
* Vehicle 是 Car 的「基底类别」("base class")
* Vehicle 是 Car 的「父类别」("superclass") (这不是 C++ 界常用的说法)
【译注】"superclass" 是 Smalltalk 语言的关键词。

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

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

可以。

衍生类别是该基底类别的特异化版本(衍生者「是一种」("a-kind-of") 基底)。这
种向上的转换是绝对安全的,而且常常会发生(如果我指向一个汽车 Car,实际上我
是指向一个车子 Vehicle):

void f(Vehicle* v);
void g(Car* c) { f(c); } //绝对很安全;不需要转型

注意:在这里我们假设的是 "public" 的继承;后面会再提到「另一种」"private/
protected" 的继承。

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

 


作者Blog:http://blog.csdn.net/vcmfc/

 


C++语言常见问题解
出处 http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-Chinese/

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

C++语言常见问题解
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[]"),这问题就可以在编译期被挑出来,而非在执行的时候。

==========================
● 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 处的存取函数。

================================
● 12D:继承--建构子与解构子
================================

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

在基底类别 Base 的建构子执行过程中,该对象还不是属于衍生 Derived 的,所以
如果 "Base::Base()" 呼叫了虚拟函数 "virt()",则 "Base::virt()" 会被呼叫,
即使真的有 "Derived::virt()"。

类似的道理,当 Base 的解构子执行时,该对象不再是个 Derived 了,所以当
Base::~Base() 呼叫 "virt()",则 "Base::virt()" 会被执行,而非覆盖后的版本
"Derived::virt()"。

当你想象到:如果 "Derived::virt()" 碰得到 Derived 类别的对象成员,会造成什
么样的灾难,你很快就会看出这规则的明智之处。

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

Q67:衍生类别的解构子应该外显地呼叫基底的解构子吗?

不要,绝对不要外显地呼叫解构子(「绝对不要」指的是「几乎完全不要」)。

衍生类别的解构子(不管你是否明显定义过)会“自动”去呼叫成员对象的﹑以及基
底类别之子对象的解构子。成员对象会以它们在类别中出现的相反顺序解构,接下来
是基底类别的子对象,以它们出现在类别基底列表的相反顺序解构之。

只有在极为特殊的情况下,你才应外显地呼叫解构子,像是:解构一个由「新放入的
new 运操作数」配置的对象。

===========================================
● 12E:继承--Private 与 protected 继承
===========================================

Q68:该怎么表达出「私有继承」(private inheritance)?

用 ": private" 来代替 ": public." 譬如:

class Foo : private Bar {
//...
};

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

Q69:「私有继承」和「成份」(composition) 有多类似?

私有继承是「成份」(has-a) 的一种语法变形。

譬如:「汽车有引擎」("car has-a engine") 关系可用成份来表达:

class Engine {
public:
Engine(int numCylinders);
void start(); //starts this Engine
};

class Car {
public:
Car() : e_(8) { } //initializes this Car with 8 cylinders
void start() { e_.start(); } //start this Car by starting its engine
private:
Engine e_;
};

同样的 "has-a" 关系也可用私有继承来表达:

class Car : private Engine {
public:
Car() : Engine(8) { } //initializes this Car with 8 cylinders
Engine::start; //start this Car by starting its engine
};

这两种型式的成份有几分相似性:
* 这两种情况之下,Car 只含有一个 Engine 成员对象。
* 两种情况都不能让(外界)使用者由 Car* 转换成 Engine* 。

也有几个不同点:
* 如果你想要让每个 Car 都含有数个 Engine 的话,就得用第一个型式。
* 第二个型式可能会导致不必要的多重继承(multiple inheritance)。
* 第二个型式允许 Car 的成员从 Car* 转换成 Engine* 。
* 第二个型式可存取到基底类别的 "protected" 成员。
* 第二个型式允许 Car 覆盖掉 Engine 的虚拟函数。

注意:私有继承通常是用来获得基底类别 "protected:" 成员的存取权力,但这通常
只是个短程的解决方案。

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

Q70:我比较该用哪一种:成份还是私有继承?

成份。

正常情形下,你不希望存取到太多其它类别的内部,但私有继承会给你这些额外的权
力(与责任)。不过私有继承丼/p>

C++语言常见问题解
出处 http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-Chinese/

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

C++语言常见问题解
== 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 老手的习惯不同,但
是「新的」不见得就是「不好的」。

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

Q85:哪一种原始档命名惯例最好? "foo.C"? "foo.cc"? "foo.cpp"?

如果你已有个惯例,就用它吧。如果没有,看看你的编译器,看它用的是哪一种。典
型的答案是:".C", ".cc", ".cpp", 或 ".cxx"(很自然的,".C" 扩展名是假设该
档案系统会区分出 ".C" ".c" 大小写)。

在 Paradigm Shift 公司,我们在 Makefiles 里用 ".C",即使是在不区分大小写的
档案系统下(在有区分的系统中,我们用一个编译器选项:「假设 .c 档案都是 C++
的程序」;譬如:IBM CSet++ 用 "-Tdp",Zortech C++ 用 "-cpp",Borland C++用
"-P",等等)。

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

Q86:哪一种标头档命名惯例最好? "foo.H"? "foo.hh"? "foo.hpp"?

如果你已有个惯例,就用它吧。如果没有,而且你的编辑器不必去区分 C 和 C++ 檔
案的话,只要用 ".h" 就行了,否则就用编辑器所要的,像 ".H"﹑".hh" 或是
".hpp"。

在 Paradigm Shift 公司,我们用 ".h" 做为 C 和 C++ 的源文件(然后,我们就
不再建那些纯粹的 C 标头档案)。

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

Q87: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",且
"+=" 必须重新配置/拷贝字符串内存的话,一开始就知道它的最后长度,可能会
比较好)。


==============================================
■□ 第15节:Smalltalk 程序者学习 C++ 之钥
==============================================

Q88:为什么 C++ 的 FAQ 有一节讨论 Smalltalk?这是用来攻击 Smalltalk 的吗?

世界上「主要的」两个 OOPLs 是 C++ 与 Smalltalk。由于这个流行的 OOPL 已有第
二大的使用者总数量,许多新的 C++ 程序者是由 Smalltalk 背景跳过来的。这一节
会回答以下问题:
* 这两个语言的差别?
* 从 Smalltalk 跳到 C++ 的程序者,要知道些什么,才能精通 C++?

这一节 *!*不会*!* 回答这些问题:
* 哪一种语言「最好」?
* 为什么 Smalltalk「很烂」?
* 为什么 C++「很烂」?

这可不是对 Smalltalk 恐怖份子挑衅,让他们趁我熟睡时戳我的轮胎(在我很难得
有空休息的这段时间内 :-) 。

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

Q89:C++ 和 Smalltalk 的差别在哪?

最重要的不同是:

* 静态型别或动态型别?
* 继承只能用于产生子型别上?
* 数值语意还是参考语意 (value vs reference semantics)?

头两个差异会在这一节中解释,第三点则是下一节的讨论主题。

如果你是 Smalltalk 程序者,现在想学 C++,底下三则 FAQs 最好仔细研读。

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

Q90:什么是「静态型别」?它和 Smalltalk 有多相似/不像?

静态型别(static typing)是说:编译器会“静态地”(于编译时期)检验各运算
的型态安全性,而不是产生执行时才会去检查的程序代码。例如,在静态型别之下,会
去侦测比对函数自变量的型态签名,不正确的配对会被编译器挑出错误来,而非在执行
时才被挑出。

OO 的程序里,最常见的「型态不符」错误是:欲对某对象激活个成员函数,但该物
件并未准备好要处理该运算动作。譬如,如果 "Fred" 类别有成员函数 "f()" 但没
有 "g()",且 "fred" 是 "Fred" 类别的案例,那么 "fred.f()" 就是合法的,
"fred.g()" 则是非法的。C++(静态地)在编译期捕捉型别错误,Smalltalk 则(动
态地)在执行期捕捉。(技术上,C++ 很像 Pascal--“半”静态型别--因为指
标转型与 union 都能用来破坏型别系统;这提醒了我们:你用指针转型与 union 的
频率,只能像你用 "goto" 那样。)

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

Q91:「静态型别」与「动态型别」哪一种比较适合 C++?

若你想最有效率使用 C++,请把她当成静态型别语言来用。

C++ 极富弹性,你可以(藉由指针转型﹑union 或 #define)让她「长得」像
Smalltalk。但是不要这样做。这提醒了我们:少用 #define。

有些场合,指针转型和 union 是必要的,甚至是很好的做法,但须谨慎为之。指针
转型等于是叫编译器完全信赖你。错误的指针转型可能会毁坏堆积﹑在别的对象记忆
体中乱搞﹑呼叫不存在的运作行为﹑造成一般性错误(general failure)。这是很
糟糕的事。如果你避免用与这些相关的东西,你的 C++ 程序会更安全﹑更快,因为
能在编译期就检测的东西,就不必留到执行期再做。

就算你喜欢动态型别,也请避免在 C++ 里使用,或者请考虑换另一个将型态检查延
迟到执行期才做的语言。C++ 将型态检验 100% 都放在编译时期;她没有任何执行期
型态检验的内建机制。如果你把 C++ 当成一个动态型别的 OOPL 来用,你的命运将
操之汝手。

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

Q92:怎样分辨某个 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),而非透过继承。

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

Q93:在 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++ 编译器利用公共继承
额外附的语意信息,以提供静态型别。

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

 


作者Blog:http://blog.csdn.net/vcmfc/

 

 

 

C++语言常见问题解
出处 http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-Chinese/

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

C++语言常见问题解
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:" 里)特别的建构子,以
自衍生类别中接收一个 "Array*"; StretchableArray 的建构子会用
"new StretchableArray" 把它传给那个特别的建构子。

优点:
* 容易做出 StretchableStack(大部份的程序都继承下来了)。
* 使用者可把 StretchableStack 当成“是一种”Stack 来传递。

缺点:
* 多增加额外的间接存取层,才能碰到 Array。
* 多增加额外的自由内存配置负担(new 与 delete)。
* 多增加额外的动态系结负担(原因请见下一则 FAQ)。

换句话说,在“我们”这一边,很轻松就成功做出 StretchableStack,但所有用户
却都为此付出代价。不幸的,额外负荷不仅在 StretchableStack 会有,连 Stack
也会。

请看下下一则 FAQ,看看使用者会「付出」多少代价。也请读读下一则 FAQ 以后的
几则(不看其它的,你将得不到平衡的报导)。

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

Q99:虚拟数据和动态数据有何差别?

最容易分辨出来的方法,就是看看颇为类似的「虚拟函数」。虚拟成员函数是指:在
所有子类别中,它的宣告(型态签名)部份必须保持不变,但是定义(本体)的部份
可以被覆盖(override)。继承下来的成员函数可被覆盖,是子类别的静态性质
(static property);它不随任何对象之生命期而动态地改变,同一个子类别的不同
对象,也不可能会有不同的成员函数的定义。

现在请回头重读前面这一段,但稍作些代换:
* 「成员函数」 --> 「成员对象」
* 「型态签名」 --> 「型别」
* 「本体」 --> 「真正所属的类别」
这样子,你就看到虚拟数据的定义。

从另一个角度来看,就是把「各个对象」的成员函数与「动态」成员函数区分开来。
「各个对象」成员函数是指:在任何对象案例中,该成员函数可能会有所不同,可能
会塞入函数指针来实作出来;这个指针可以是 "const",因为它在对象生命期中不会
变更。「动态」成员函数是指:该成员函数会随时间动态地改变;也可能以函数指针
来做,但该指针不会是 const 的。

推而广之,我们得到三种不同的资料成员概念:
* 虚拟数据:成员对象的定义(真正所属的类别)可被子类别覆盖,只要它的宣告
(型别)维持不变,且此覆盖是子类别的静态性质。
* 各对象的资料:任何类别的对象在初始化时,可以案例化不同型式(型别)的成
员对象(通常是一个 "wrapper" 包起来的对象),且该成员对象真正所属的类别
,是把它包起来的那个对象之静态性质。
* 动态数据:成员对象真正所属的类别,可随时间动态地改变。

它们看起来都差不多,是因为 C++ 都不「直接支持」它们,只是「能做得出来」而
已;在这种情形下,仿真它们的机制也都一样:指向(可能是抽象的)基底类别的指
标。在直接提供这些 "first class" 抽象化机制的语言中,这三者间的差别十分明
显,它们各有不同的语法。

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

Q100:我该正常地用指针来配置资料成员,还是该用「成份」(composition)?

成份。

正常情况下,你的成员资料应该被「包含」在合成的对象里(但也不总是如此;
"wrapper" 对象就是你会想用指针/参考的好例子;N-to-1-uses-a 的关系也需要某
种指针/参考之类的东西)。

有三个理由说明,完全被包含的成员对象(「成份」)的效率,比自由配置对象的指
标还要好:

* 额外的间接层,每当你想存取成员对象时。
* 额外的动态配置("new" 于建构子中,"delete" 于解构子中)。
* 额外的动态系结(底下会解释)。

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

Q101:动态配置成员对象有三个效率因素,它们的相对代价是多少?

这三个效率因素,上一则 FAQ 有列举出来:
* 以它本身而言,额外的间接层影响不大。
* 动态配置可能是个效率问题(当有许多配置动作时,典型的 malloc 会拖慢速度
;OO 软件会被动态配置拖垮,除非你事先就留意到它了)。
* 用指针而非对象的话,会带来额外的动态系结。每当 C++ 编译器能知道某对象「
真正的」类别,该虚拟函数呼叫就能“静态”地系结住,能够被 inline。Inline
可能有无限大的 (但你可能只会相信有半打的 :-) 最佳化机会,像是 procedural
integration﹑缓存器生命期等等事项。三种情形之下,C++ 编译器能知道对象真
正的类别:区域变量﹑整体/静态变量﹑完全被包含的成员对象。

完全被包含的成员对象,可达到很大的最佳化效果,这是「成员对象的指针」所不可
能办到的。这也就是为什么采用参考语意的语言,会「与生俱来」就效率不彰的原因
了。

注意:请读读下面三则 FAQs 以得到平衡的观点!

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

Q102:"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 以看看另一种说法!

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

Q103:看起来我不应该用参考语意了,是吗?

错。

参考语意是个好东西。我们不能拋弃指针,我们只要不让软件的指针变成一个大老鼠
窝就行了。在 C++ 里,你可以选择该用参考语意(指针/参考)还是数值语意(物
件真正包含其它对象)的时机。在大型系统中,两者应该取得平衡。然而如果你全都
用指针来做的话,速度会大大的降低。

接近问题层次的对象,会比较高阶的对象还要大。这些针对「问题空间」抽象化的个
体本身,通常比它们内部的「数值」更为重要。参考语意应该用于问题空间的对象上

注意:问题空间的对象,通常会比解题空间的更为高阶抽象化,所以相对地问题空间
的对象通常会有较少的交谈性。因此 C++ 给我们一个“理想的”解决法:我们用参
考语意,来对付那些需要独立的个体识别 (identity) 者,或是大到不适合直接拷贝
的对象;其它情形则可选择数值语意。因此,使用频率较高的就用数值语意,因为(
只有)在不造成伤害的场合下,我们才去增加弹性;必要时,我们还是选择效率!

还有其它关于实际 OO 设计方面的问题。想精通 OO/C++ 得花时间,以及高素质的训
练。若你想有个强大的工具,你必须投资下去。

<<<< 还不要停下来! 请一并读读下一则 FAQ!! >>>>

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

Q104:参考语意效率不高,那么我是否应该用传值呼叫?

不。

前面的 FAQ 是讨论“成员对象”(member object) 的,而不是函数参数。一般说来
,位于继承阶层里的对象,应该用参考或指针来传递,而非传值,因为惟有如此你才
能得到(你想要的)动态系结(传值呼叫和继承不能安全混用,因为如果把大大的子
类别对象当成基底的对象来传值的话,它会被“切掉”)。

除非有足以令人信服的反方理由,否则成员对象应该用数值,而参数该用参考传递。
前几则 FAQs 提到一些「足以信服的理由」,以支持“成员对象该用参考”一事了。

--
Marshall Cline
--
Marshall P. Cline, Ph.D. / Paradigm Shift Inc / PO Box 5108 / Potsdam NY 13676
cline@sun.soe.clarkson.edu / 315-353-6100 / FAX: 315-353-6110

 


作者Blog:http://blog.csdn.net/vcmfc/

 

 

 


C++语言常见问题解
出处 http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-Chinese/

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

C++语言常见问题解
== 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).
* 完全被包含的子对象中也都没有虚拟函数.

如果 C++ 类别有任何基底类别(或是任何被完全包含的子对象中有基底类别)的话
,技术上来说,存取该资料没有可移植性的,因为语言没规定在继承之下的类别配置是
什么样子。不过经验上,所有 C++ 编译器的做法都一样:基底类别对象先出现(在
多重继承之下,则由左到右排列之),子对象次之。

还有,如果类别(或是任何基底类别)含有任何虚拟函数,你时常可以(但不是一直
都可以)假设有一个 "void*" 出现在对象第一个虚拟函数之所在,或是在该对象的
第一个 word 那里。同样的,语言对它也没规定到,但这似乎是「大家」都采取的做
法。

如果该类别有任何虚拟基底类别,情况会更复杂而且更没有可移植性。常见的做法是:
让对象最后才包含基底类别之对象 (V)(不管 "V" 在继承阶层中在哪儿出现),物
件的其它部份则以正常的次序出现。每个有 V 这个虚拟基底类别的衍生类别,实际
上都有个“指针”指向最后一个对象的 V 的部份。

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

Q110:为什么我总觉得 C++ 让我「离机器更远了」,不像 C 那样?

因为事实上正是如此。

做为一个 OOPL,C++ 让你以该问题的领域来思考,让你以问题领域的语言来设计程
式,而非以解题的领域来着手。

一个 C 最强的地方是:它没有「隐藏的机制」:你看到的就是你得到的,你可以一
边阅读 C 的程序,一边「看到」每个系统时脉。C++ 则不然; C 的老手(像从前的
我们)对这种特性常会有矛盾的心理(或是说「敌视」),但是很快的他们会发现:
C++ 提供了抽象化的层次及经济的表现能力,大大降低维护成本,又不会损及执行效
率。

很自然的,用任何语言都会写出坏程序;C++ 并不会确保任何高品质﹑可重用性﹑抽
象化,或是任何「正字标记」的品质因子。C++ 不会让差劲的程序者写不出差劲的程
式;她只是协助明智的发展者做出高人一等的软件。


===================================
■□ 第18节:指向成员函数的指针
===================================

Q111:「指向成员函数的指针」和「指到函数的指针」的型态有差别吗?

是的。

考虑底下的函数:

int f(char a, float b);

如果它是普通的函数,它的型态是: int (*) (char,float);
如果它是 Fred 类别的运作行为,它的型态是: int (Fred::*)(char,float);

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

Q112:怎样把指向成员函数的指针传给 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)。

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

Q113:当我想以成员函数做为中断服务例程 (ISR) 时,为什么编译器产生(型态不
符)的错误?

这是前两个问题的特例,所以请先看看前两则解答。

非静态的成员函数,都有一个隐藏的参数,对应到 'this' 指针,该 'this' 指针会
指向该对象的案例资料 (instance data),可是系统中断的硬件/韧体并未提供这个
'this' 参数。你得用「正常的」函数(不是类别的成员)或是静态成员函数来做为
中断服务例程才行。

一个可行的解法是:用一个静态成员做为中断服务例程,让它能自己到某处去找案例
/成员的配对,以供中断呼叫之用。这么一来,当中断产生时,正常的 method 就会
被激活,不过以技术观点来看,你得先呼叫一个中介函数。

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

Q114:为什么我取不出 C++ 函数的地址?

这可由前一则 FAQ 推论过来。

详细的解答:在 C++ 里,成员函数有一个隐含的参数,指向该对象本身(成员函数
内的 "this" 指针)。正常的 C 函数与成员函数的呼叫惯例可视为不同,所以它们
指针的型态(指向成员函数 vs 指向函数)既不同也不兼容。C++ 引进一个新的指针
型态:指向成员的指针,要提供一个对象才能激活之(见 ARM ["Annotated
Reference Manual"] 5.5)。

注意:不要去把指向成员函数的指针强制转型成指向函数的指针;这样做的结果是未
定义的,且下场可能会很惨。譬如,指向成员函数的指针,“不必然”会包含某正常
函数的机器地址(看 ARM, 8.1.2c, p.158)。如前例所提,如果你有个指向正常 C
函数的指针的话,请用上层的(非成员的)函数,或是用 "static" 成员函数(类别
成员函数)。

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

Q115:怎样宣告指向成员函数的指针数组?

用 "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);


====================================
■□ 第19节:容器类别与 template
====================================

Q116:怎样自一个连结串行/杂凑表等等里面,插入/存取/改变元素?

我将以最简单的「插入连结串行」为例。想把元素插入串行的头尾很容易,但只限
于这些功能的话,会使链接库过于低能(太低能的链接库比没有更糟)。

完整的解答会让 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"].

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

Q117:「样版」(template)的用意是什么?

Template 本意是个压饼模子,它把饼干都压成差不多一个样子(虽然饼干的原料不
尽相同,但它们都有相同的基本外形)。同理,class template 是个样版模子,用
来描述如何将一系列的对象类别弄成同一个基本型式;而 function template 则是
用以描述一系列看起来差不多的函数。

Class template 常用于制造型别安全的容器(即使这仅止于「如何使用它」而已)。

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

Q118:"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" 实体化之后的案例。)

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

Q119:"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>"
// (编译器会把 ">>" 看成单一的元素)

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

Q120:什么是「参数化型别」(parameterized type)?

另一种 "class template" 的说法。

「参数化型别」是一种型别,它被另一个型别或数值所参数化(parameterized)了。
像 List 是一个型别 ("List") ,它被另一个型别 ("int") 所参数化。

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

 


作者Blog:http://blog.csdn.net/vcmfc/

 

 

C++语言常见问题解
出处 http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-Chinese/

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

C++语言常见问题解
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 时,执行期的例外处理系统如何得知哪一个区域对象该被解
构?


=======================================
■□ 第22节:其它的技术和环境的事项
=======================================
● 22A:其它的技术事项
========================

Q129:为什么有 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"
等等你使用的扩展名)。

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

Q130:"struct" 和 "class" 关键词差别在哪?

struct 的成员和基底类别, 都是预设为 public 的,而 class 则预设为 private。
注意:你应该“明显地”把基底类别设为 public、private 或是 protected,而不
要依赖默认值。

除此之外,两者的功能是相等的。

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

Q131:为什么不能以函数的传回值来多载(overload)它?

如果你同时宣告了 "char f()" 及 "float f()" ,编译器会给你个错误讯息,因为
呼叫 "f()" 会造成仿真两可的情况。

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

Q132:什么是「持续性」?什么是「持续性对象」?

一个持续性对象 (persistent object),在创造它的程序执行结束后,仍可存活下来
。它甚至可存活于不同的父程序,存活于磁盘系统、操作系统、甚至于操作系统所处
的硬件上。

持续性对象的困难在于:如何有效地在次储存体中,存放它们的运作行为(method)
及资料位(以及所有成员对象的资料和运作行为,及它们所有的成员对象、基底类
别……等等)。这一切都得自己来做的话,可不是件容易的事。在 C++中,你就得自
己来。C++/OO 的数据库系统,会替你把这些机制都隐藏起来。

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

Q133:为什么浮点数 (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 位的假数)。

==========================
● 22B:其它环境下的琐事
==========================

Q134:有任何 TeX 或 LaTeX 的宏,能处理 "C++" 的留白效果(spacing)吗?

有的,底下列出两个:

/def/CC{C/raise.22ex/hbox{{/footnotesize +}}/raise.22ex/hbox{/footnotesize +}}

/def/CC{{C/hspace{-.05em}/raisebox{.4ex}{/tiny/bf ++}}}

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

Q135:在哪儿可拿到 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

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

Q136:该到哪里取得 "tgrind" 这个 C++/C/etc 的原始码美编工具?

"tgrind" 读入 C++ 源文件,并输出能让 Unix 打印机印出美观文件的东西。它常
会伴随在 TeX 和 LaTeX 的套件里;请找找这个目录:
"...tex82/contrib/van/tgrind" 。 由 Jerry Leichter 所做更新的版本,可在
venus.ycc.yale.edu in [.TGRIND] 里找到。

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

Q137:有给 GNU emacs 编辑器用的 C++-mode 吗?有的话,该怎么拿?

Yes,有一个给 GNU emacs 用的 C++-mode。

最新﹑最好的 C++-mode(以及 c-mode)版本是 cc-mode.el 檔,是 Detlef &
Clamen 版本的延伸。Emacs 里头有一个了,较新的则在 elisp 里面。

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

Q138:我要到哪儿得到和操作系统相关的 FAQs( 譬如:BC++﹑DOS﹑Windows 等等
)?

请参考:
* comp.os.msdos.programmer
* comp.windows.ms.programmer
* comp.unix.programmer

[如果您有 BC++、VC++ 的 email address,或是 Semantic C++ 的臭虫清单或可供
讨论的 mailing list,请告诉我该如何加入,我会在这儿提出的。]

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

Q139:为什么我的 DOS C++ 程序说 "Sorry: floating point code not linked"
“抱歉,浮点运算程序代码未连结进来”?

编译器会试着节省执行档的大小,所以除非必要,否则不引入浮点数→字符串格式转换
的子程序,可是有时候它会猜错,就会产生上述的错误讯息了。解决法:(1) 使用
而不要用 ,或是 (2) 在您程序的某个地方,置入如下的函
数(但是不要真的去呼叫它!):

static void dummyfloat(float *x) { float y; dummyfloat(&y); }

请参考关于 stream I/O 的 FAQ项目,有提到更多使用 vs
的理由。

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

Q140:为什么当我没执行 BC45 IDE 的话,BC++ 做出来的 Windows 应用程序就不能
用?

用 BC++ 写 Windows 应用程序,如果当 BC45 IDE 正在执行时,你的程序很正常;
待会儿当 BC45 IDE 关掉了,而你的程序却在建立窗口时产生了个 exception 的话
,就把底下这行程序加到你的应用程序类别 ("YourApp::InitMainWindow()") 里头
的 InitMainWindow() 内:

EnableBWCC(TRUE);

【译注】这是因为你用 BC++ 写的应用程序,可能会自动用到 bwcc*.dll,刚好
BC++ 的 IDE 也会用到它,所以两者并存的话,BWCC 已先被 IDE 加载了。
若是 IDE 未执行,则 BWCC 未被加载,你就得用上面那一行程序来通知
OWL 去加载它。

== comp.lang.c++ FAQ 结束 ========================= 

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页