科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件C++语言常见问题解答(1)

C++语言常见问题解答(1)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

文件不等於书本:这份C++FAQ文件和「FAQ书」是不一样的。“FAQ书” ("C++FAQs",Addison-Wesley,1995)是这篇文件的五倍大。

来源:中国软件网 2008年3月31日

关键字: 解答 问题 C++ C Linux

  • 评论
  • 分享微博
  • 分享邮件

第1节:内容介绍


DOCUMENT:Frequently-Asked-Questionsforcomp.lang.c++
REVISION:Jan31,1996

文件不等於书本:这份C++FAQ文件和「FAQ书」是不一样的。“FAQ书”
("C++FAQs",Addison-Wesley,1995)是这篇文件的五倍大。
底下有更详细的介绍。

AUTHOR:MarshallP.Cline,Ph.D.
ParadigmShift,Inc.
OneParkSt./Norwood,NY13668
voice:315-353-6100
fax:315-353-6110
email:cline@parashift.com

COPYRIGHT:Copyright(C),1991-96MarshallP.Cline,Ph.D.
Permissiontocopyallorpartofthisworkisgranted,
providedthatthecopiesarenotmadeordistributed
forresale(exceptanominalcopyfeemaybecharged),
andprovidedthattheAUTHOR,COPYRIGHT,&NOWARRANTY
sectionsareretainedverbatimandaredisplayed
conspicuously.Ifanyoneneedsotherpermissionsthat
aren''tcoveredbytheabove,pleasecontacttheauthor.

NOWARRANTY:THISWORKISPROVIDEDONAN"ASIS"BASIS.THEAUTHOR
PROVIDESNOWARRANTYWHATSOEVER,EITHEREXPRESSOR
IMPLIED,REGARDINGTHEWORK,INCLUDINGWARRANTIESWITH
RESPECTTOITSMERCHANTABILITYORFITNESSFORANY
PARTICULARPURPOSE.

版权声明:Copyright(C),1991-96MarshallP.Cline,Ph.D.
复制本文件全部或部份的内容,若合乎下述诸项要求,则允许之
:不得为转售之目的而制作或传播任何复制品(但可索取名义上
的传播手续费),并且作者栏、版权声明及责任事项的部份,要
一字不漏地、醒目地显示出来。若您需要此处未提及的授权事项
,请洽本作者。

责任事项:这份文件是供您参考用的。对於本作品可能造成的营利性、或任
何特殊场合适用性之保障,作者概不负责,不论是否曾明白地指
出或暗示。

【译注】上述的「版权声明」与「责任事项」译文,仅供参考,一切请以
原文为准。译者对它们亦概不负责……;->

文件取得方法:旧的FTP地点不能用了。我正在寻找新的地点,
请密切注意下个月的这个地方。
请不要寄信来推荐新的地点(我的信箱会爆满的:)

【译注】台湾使用者,请到以下地点,或其AFSclient取得:
ftp://NCTUCCCA.edu.tw/USENET/FAQ/comp/lang/c++/FAQ*

其他资讯:comp.lang.cFAQ每个月都会出现在该讨论区中,
该文件的维护者是SteveSummit(scs@eskimo.com)。

中文翻译:用"Big5"内码(在台湾最广为使用的16位元中文内码)储存
的中文译文,可用anonymousftp到以下地点取得:
ftp://ftp.cis.nctu.edu.tw/Documents/News/C-faq/c-cppfaq.zip


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


Addison-Wesley已出版了由Cline与Lomow合著的"C++FAQs"(1995,ISBN
0-201-58958-3)一书,许多人对该书和这篇文件的关系感到好奇,一些没看过该书
的人,也担心本文是否会和它相同。此处将回答这些问题。

「FAQ书」(於Oct.94上市)大体上和本文的观念一致,但该书约为本文的5倍
大,还包括了数千则交互参考资讯、参考资料、索引项目,及许多的程式例子。

===
●1B:目录
===

POSTING#1

第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:运算子多载(operatoroverloading)是做什麽的?
Q21:哪些运算子可以/不能被多载?
Q22:怎样做一个"**"「次方」运算子?

第7节:夥伴

Q23:夥伴(friend)是什麽?
Q24:「夥伴」违反了封装性吗?
Q25:夥伴函数的优缺点?
Q26:「夥伴关系无继承及递移性」是什麽意思?
Q27:应该替类别宣告个成员函数,还是夥伴函数?

第8节:输入/输出:和

Q28:该怎样替"classFred"提供输出功能?
Q29:为什麽我该用而不是以前的?
Q30:为什麽我处理输入时,会超过档案的结尾?
Q31:为什麽我的程式执行完第一次回圈後,会对输入的要求不加理睬?
Q32:在DOS及OS/2的binary模式下,要怎样来"reopen"cin及cout?

POSTING#2

第9节:自由记忆体管理

Q33:"deletep"会删去"p"指标,还是它指到的资料,"*p"?
Q34:我能"free()"掉由"new"配置到的、"delete"掉由"malloc()"配置到的
记忆体吗?
Q35:为什麽该用"new"而不是老字号的malloc()?
Q36:为什麽C++不替"new"及"delete"搭配个"realloc()"?
Q37:我该怎样配置/释放阵列?
Q38:万一我忘了将"[]"用在"delete"由"newFred[n]"配置到的阵列,会发生
什麽事?
Q39:成员函数做"deletethis"的动作是合法的(并且是好的)吗?
Q40:我该怎麽用new来配置多维阵列?
Q41:C++能不能做到在执行时期才指定阵列的长度?
Q42:怎样确保某类别的物件都是用"new"建立的,而非区域或整体/静态变数?

第10节:除错与错误处理

Q43:怎样处理建构子的错误?
Q44:如果建构子会丢出例外的话,该怎麽处理它的资源?

第11节:Const正确性

Q45:什麽是"constcorrectness"?
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)hidesBase::f(float)"是什麽意思?

⊙12B:继承--一致性
Q60:我该遮蔽住由基底类别继承来的公共成员函数吗?
Q61:圆形"Circle"是一种椭圆"Ellipse"吗?
Q62:对「圆形是/不是一种椭圆」这两难问题,有没有其他说法?

⊙12C:继承--存取规则
Q63:为什麽衍生的类别无法存取基底的"private"东西?
Q64:"public:"、"private:"、"protected:"的差别是?
Q65:当我改变了内部的东西,怎样避免子类别被破坏?

⊙12D:继承--建构子与解构子
Q66:若基底类别的建构子呼叫一个虚拟函数,为什麽衍生类别覆盖掉的那个虚拟函
数却不会被呼叫到?
Q67:衍生类别的解构子应该外显地呼叫基底的解构子吗?

⊙12E:继承--Private与protected继承
Q68:该怎麽表达出「私有继承」(privateinheritance)?
Q69:「私有继承」和「成份」(composition)有多类似?
Q70:我比较该用哪一种:成份还是私有继承?
Q71:我应该用指标转型方法,把「私有」衍生类别转成它的基底吗?
Q72:保护继承(protectedinheritance)和私有继承有何关连?
Q73:"private"和"protected"的存取规则是什麽?

第13节:抽象化(abstraction)
---
Q74:分离介面与实作是做什麽用的?
Q75:在C++里,我该怎样分离介面与实作(像Modula-2那样)?
Q76:ABC("abstractbaseclass")是什麽?
Q77:「纯虚拟」(purevirtual)成员函数是什麽?
Q78:怎样替整个类别阶层提供列印的功能?
Q79:何时该把解构子弄成virtual?
Q80:虚拟建构子(virtualconstructor)是什麽?

POSTING#3

第14节:程式风格指导

Q81:有任何好的C++程式写作的标准吗?
Q82:程式撰写标准是必要的吗?有它就够了吗?
Q83:我们的组织该以以往C的经验来决定程式撰写标准吗?
Q84:我该在函数中间或是开头来宣告区域变数?
Q85:哪一种原始档命名惯例最好?"foo.C"?"foo.cc"?"foo.cpp"?
Q86:哪一种标头档命名惯例最好?"foo.H"?"foo.hh"?"foo.hpp"?
Q87:C++有没有像lint那样的指导原则?

第15节:Smalltalk程式者学习C++之钥
-
Q88:为什麽C++的FAQ有一节讨论Smalltalk?这是用来攻击Smalltalk的吗?
Q89:C++和Smalltalk的差别在哪?
Q90:什麽是「静态型别」?它和Smalltalk有多相似/不像?
Q91:「静态型别」与「动态型别」哪一种比较适合C++?
Q92:怎样分辨某个C++物件程式库是否属於动态型别的?
Q93:在C++里怎样用继承?它和Smalltalk有何不同?
Q94:Smalltalk/C++不同的继承,在现实里导致的结果是什麽?
Q95:学过「纯种」的OOPL之後才能学C++吗?
Q96:什麽是NIHCL?到哪里拿到它?

第16节:参考与数值语意

Q97:什麽是数值以及参考语意?哪一种在C++里最好?
Q98:「虚拟资料」是什麽?怎麽样/为什麽该在C++里使用它?
Q99:虚拟资料和动态资料有何差别?
Q100:我该正常地用指标来配置资料成员,还是该用「成份」(composition)?
Q101:动态配置成员物件有三个效率因素,它们的相对代价是多少?
Q102:"inlinevirtual"的成员函数真的会被"inline"吗?
Q103:看起来我不应该用参考语意了,是吗?
Q104:参考语意效率不高,那麽我是否应该用传值呼叫?

POSTING#4

第17节:和C连结/和C的关系
----
Q105:怎样从C++中呼叫C的函数"f(int,char,float)"?
Q106:怎样才能建一个C++函数"f(int,char,float)",又能被C呼叫?
Q107:为什麽linker有这种错误讯息:C/C++函数被C/C++函数呼叫到?
Q108:该怎麽把C++类别的物件传给/传自C的函数?
Q109:C的函数能不能存取C++类别的物件资料?
Q110:为什麽我总觉得C++让我「离机器更远了」,不像C那样?

第18节:指向成员函数的指标

Q111:「指向成员函数的指标」和「指到函数的指标」的型态有差别吗?
Q112:怎样把指向成员函数的指标传给signalhandler、Xeventcallback等等?
Q113:当我想以成员函数做为中断服务常式(ISR)时,为什麽编译器产生(型态不
符)的错误?
Q114:为什麽我取不出C++函数的位址?
Q115:怎样宣告指向成员函数的指标阵列?

第19节:容器类别与template
-
Q116:怎样自一个连结串列/杂凑表等等里面,插入/存取/改变元素?
Q117:「样版」(template)的用意是什麽?
Q118:"functiontemplate"的语法/语意是什麽?
Q119:"classtemplate"的语法/语意是什麽?
Q120:什麽是「参数化型别」(parameterizedtype)?
Q121:「泛型」(genericity)是什麽?

第20节:程式库
----
Q122:怎样拿到"STL"?
Q123:怎样ftp到"NumericalRecipes"附的程式?
Q124:为什麽我的执行档会这麽大?

第21节:特定系统的细节

Q125:GNUC++(g++)把小程式造出大大的执行档,为什麽?
Q126:有YACC的C++文法吗?
Q127:什麽是C++1.2?2.0?2.1?3.0?
Q128:如果签名编码标准化了,我能否将不同厂商编译器产生的程式码连结起来?

第22节:其他的技术和环境的事项
----
⊙22A:其他的技术事项
Q129:为什麽有static资料成员的物件类别产生了linker错误?
Q130:"struct"和"class"关键字差别在哪?
Q131:为什麽不能以函数的传回值来多载(overload)它?
Q132:什麽是「持续性」?什麽是「持续性物件」?
Q133:为什麽浮点数(floatingpoint)这麽不精确?为什麽这段程式不会印出0.43?

⊙22B:其他环境下的琐事
Q134:有任何TeX或LaTeX的巨集,能处理"C++"的留白效果(spacing)吗?
Q135:在哪儿可拿到C++2LaTeX这个C++原始码的LaTeX美编工具(pretty
printer)?
Q136:该到哪里取得"tgrind"这个C++/C/etc的原始码美编工具?
Q137:有给GNUemacs编辑器用的C++-mode吗?有的话,该怎麽拿?
Q138:我要到哪儿得到和作业系统相关的FAQs(譬如:BC++、DOS、Windows等等)?
Q139:为什麽我的DOSC++程式说"Sorry:floatingpointcodenotlinked"
“抱歉,浮点运算程式码未连结进来”?
Q140:为什麽当我没执行BC45IDE的话,BC++做出来的Windows应用程式就不能用?

==
●1C:术语及常用的缩写
==

这儿是一些此文件所采用的缩写:

字汇意义
=====
fnfunction,函数(单数型)
fnsfunctions,函数(复数型)
paramparameter,参数
ptrpointer,指标,C/C++的语法元素,宣告法:int*p;
refreference,参考,C++的语法元素,宣告法:int&r;
OOobject-oriented,物件导向
OOPobject-orientedprogramming,物件导向程式设计
OOPLobject-orientedprogramminglanguage,物件导向语言
method运作行为,"memberfunction成员函数"的另一种说法
【译注】"method"是源自Smalltalk的术语,很常用於OO界。


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

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

Comp.lang.c++是讨论C++语言本身最好的地方(譬如:C++程式设计、语法、风格
)。其他讨论区是用来讨论特定的系统(譬如:MSWindows或是UNIX),或是其他
和C++语言不直接相关的主题(譬如:怎样使用你的编译器)。底下列出一些非常热
门的讨论区,并从它们的FAQs中摘录些片断,应该能让您明了它们最常讨论哪些课
题。

comp.os.ms-windows.programmer.tools
此区是用来讨论有关Windows软体发展系统工具的选择及使用。
comp.os.ms-windows.programmer.misc
此乃论及其馀Windows软体发展之事项。
[有个FAQ列表,列出所有comp.os.ms-windows.programmer.*讨论区]
FAQ5.7.1.在DLL中存取C++的物件类别
FAQ6.1.1.以MDI子视窗做出对话框[用OWL]
FAQ6.2.1.把禁能的选项致能起来[用MFC]
FAQ8.1.5.使用windows.h的STRICT符号定义
FAQ10.程式设计参考资料

comp.os.msdos.programmer
许多信件都是关於程式语言产品的(主要是Borland和Microsoft)。
FAQ301.怎样才能读取字元而不[等待]Enter键?
FAQ412.怎样读取、建立、更改及删除磁片标名?
FAQ504.怎样设定COM埠,以用它来传输资料?
FAQ602.C程式怎样才能送控制码给印表机?
FAQ606.怎样才能得知Microsoft滑鼠的位置及按钮状态?
FAQ707.怎样写常驻程式(TSR)工具?
FAQB0.怎样连系[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
FAQ4.5)怎样使用popen()开启行程以读写之?
FAQ4.6)怎样在C程式里sleep()一秒以内?

comp.unix.solaris(包含SunOS4.x和Solaris)
FAQ4)Signal入门
FAQ5)等待子行程Exit

gnu.g++.help
FAQ:到哪里找C++的demangler(反签名编码器)?
FAQ:哪里有Solaris2.x版的gcc/g++位元档?
FAQ:有g++2.x的文件吗?
gnu.g++.bug[g++的臭□列表--请见g++的文件]

comp.lang.c
FAQ1.10:我搞糊涂了。NULL保证一定是0,但是null指标却不是?
FAQ2.3:那麽,在C里头「指标和阵列等价」是什麽意思?
FAQ4.2:[为什麽"printf("%d\n,"i++*i++);"有问题?]
FAQ7.1:怎样写一个接收不定数目引数的函数?[stdarg.h或是varargs.h]
FAQ10.4:怎麽宣告一个指向某种函数的指标阵列,而该函数的传回值为:
指向另一个传回字元指标的函数?

并请参考看看comp.graphics、comp.sources.wanted、comp.programming,以及
comp.object(它的FAQ是个很棒的OOP入门、术语观念概论文件)。请记住:
comp.std.c++是专门讨论和研议中的ANSI/ISOC++标准方案(下文会提)“直接
”相关的事项。

同时到上述信区和comp.lang.c++去问同一个问题,几乎是没必要的(你是知道的
,特定系统信区的读者不用机器语言写程式)。只因你的问题「真的很要紧」,就到
处发问,是个很坏的习惯。如果你在「正确的」信区没得到回音,且认为你非得在这
儿发信不可,请至少考虑一下,将这儿的回信重导回原来那个适当的信区。

在任何信区发问之前,你应当先读读它的FAQ。你想问的可能就在上面,这样就可省
下你发信的时间,以及全世界数以千计的人类读你的信的时间。回答已经是FAQ问题
的人,可能会因为白白浪费时间而烦扰不已;他们也可能会给你错误或不完整的解答
,因为他们也没看过FAQ。

「常见问题解答」文件每天24小时都可由anonymousftp(rtfm.mit.edu的
/pub/usenet/comp.what.ever)或是e-mailserver(寄一则内容为"help"的信到
mail-server@rtfm.mit.edu)来取得。欲知详情,请见"Introductiontothe
*.answersnewsgroups"这份文件,它在news.answers或news.announce.newusers
(这儿还有许多必须一读的文件)中找到。

 

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

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

1.请读读上一个问题,以确定你的问题是针对C++语言本身,而和你的程式设计系
统(譬如:绘图、印表机、设备……)或是编译环境(譬如:「整合环境挂了」
、「怎样消除xxxx警告讯息」、「怎样连结程式库」)完全无关。如果你想知道
为什麽你OWL程式中的虚拟函数CmOk()没被呼叫到,你的问题可能比较适合放
在Windows程式设计的信区。如果你能写个独立的小程式,而它会让编译器产生
和你那个OWL程式同样的错误讯息或行为的话,就可以放到comp.lang.c++了,
其他系统的C++程式员可能帮得上忙。

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

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

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

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

4.列出你用的编译器、编译器版本,以及你使用的系统。我知道我刚刚说过:特定
系统的问题要去特定的信区发问,但和编译器有关的资讯,常常对侦查问题有帮
助(「喔,我记得Acme1.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)资料来办到,以避免其他模组存取
到它。

「多重案例multipleinstances」:典型的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个元素……等等)。你甚至可以做出个“聪明的指标”(smartpointer),以指
向磁碟或其他地方去("x=*p"可dereference[解参用]指标,也就可以在磁碟
中找到p所“指到”的地方,并传回其值)。这可让使用者以切近该问题的方式来
写程式,而非以机器的语言来解题。

【译注】STL(StandardTemplateLibrary)就大量利用到「聪明的指标」功能。

「继承性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)与动态系
结(dynamicbinding)最容易从实例来理解了,所以我提个典型的例子:绘图软体
得处理圆形、方形、矩形、多边形及直线,这些都是「形状shape」。大部份绘图软
体的内部函数都需要个“形状”的参数(相对於某些像是“方形”这种特定的形状)
,譬如:当我们用滑鼠选取某个图形,它就可能被拖曳放到萤幕某处。多型和动态系
结让程式能正确运作,即使编译器只知道该参数是个「形状」,而不知它到底是什麽
形状。我们再假设刚才提到的"pick_and_drag(Shape*)"函数於星期二编译好了,
到了星期三,你打算再加个六边形。听起来很奇怪,但pick_and_drag()仍然能够
处理这个六边形,即使当pick_and_drag()编译时六边形还不存在!(若你明了
C++是怎麽做的,它就再也不惊异了--但它仍然是很方便的!)

 

Q5:谁在用C++?

很多很多的公司及政府部门。相当的多。

统计上来看:当你正在读这份FAQ文字时,就有5个人正成为C++的程式员。

 

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

有的;ANSI(美国的)和ISO(国际的)组织正密切合作。ANSI-C++委员会称为
"X3J16",而ISOC++标准团体称为"WG21"。ANSI/ISOC++的标准过程中包含了
这些人:

AT&T,IBM,DEC,HP,Sun,MS,Borland,Zortech,Apple,OSF等等等等。每次开
会约有70人,他们来自美、英、日、德、瑞典、丹麦、法国……(他们都有「区域
性」的委员会,派遣正式代表并主导「区域性」的会议)。

 

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

也能拿到书面版本:
X3Secretariat
1250EyeStreetNW
Suite200
Washington,DC20005
202-626-5738

你也可以用email:

lbarra@itic.nw.dc.us(LynnBarra)

注明要索取最新的"DraftProposedAmericanNationalStandardforInformation
Systems--ProgrammingLanguageC++",文件编号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"字("typedefstructFredFred"这种技巧仍然能
用,但在C++中是累赘的)。

 

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

像ParadigmShift公司,成功地教授过标准的工业界「短期课程」,将大学一学期
的课压缩到一周40小时。然而真正的精通得由实际经验而来:没有东西能取代时间
。需动手做的指定专题是必要的,因为它们能将你的观念「凝固成形」。

大约要6-12个月才能流利使用C++/OOP,如果身边有高手的话,费时会短些;反之
若没有个“好的”通用型C++物件程式库,则会耗时更久。想成为顾问级的高手,则
约需3年。

有些人却根本办不到。除非你是可造之材,且有强烈的个人驱动力,否则你也做不到
。「孺子可教」最起码的要求是:你必须能「觉今是而昨非」。「驱动力」最起码的
要求是:你愿意多投入时间精力(改变思考的方式〔典□转移paradigmshift〕要
远比学些新的东西来得困难)。


==
■□第4节:C++的基础
==

Q10:什麽是类别(class)?

物件导向系统的基石。

类别是用来定义资料型态(datatype)的,就像C的struct一样。
以资讯科学术语来说,一个型态包含了一组状态(state),以及在状态之间转移的
动作行为(operation)。因此"int"是个「型态」,因为它有一组状态,还有诸如
「加两个整数」、「整数相乘」等等的运作行为。同样的,「类别」提供一组(通常
是公共的)运算,及一组(通常是非公共的)资料栏位,以代表该型态的案例所拥有
的抽象值。以C的角度来看,类别就是其成员(members)皆预设为"private"的
struct。

把"int"想成是个类别,它拥有"operator++"等等的运作行为(method)。

 

Q11:什麽是物件(object)?

一块赋有某种语意的储存空间。

在宣告"inti;"之後,我们称「i是个int型态的物件」。在C++/OOP里,「物
件」通常意指「类别的案例(aninstanceofaclass)」,因此类别定义了数个物
件(案例)的行为。

 

Q12:什麽是参考(reference)?

一个物件的“别名”(alias,另一个名称)。

参考通常用於传址呼叫(pass-by-reference):

voidswap(int&i,int&j)
{
inttmp=i;
i=j;
j=tmp;
}

main()
{
intx,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++经验後,会发现这正是一种「资讯隐藏」的作法,是利
而非弊。好比说,程式员应该以切近该问题的方式来写程式,而非以机器的语言来解
题。

 

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

行内函数(inlinefunction)是个程式码会塞入呼叫者所在之处的函数。就像巨集
一样,行内函数免除了函数呼叫的额外负担,以增进效率,并且(尤其是!)还能让
编译器对它施以最佳化(程序融合"proceduralintegration")。不过和巨集不同
的是:它只会对所有引数求一次的值(在语意上,该“函数呼叫”和正常函数一样,
只是比较快速罢了),以避免某些不易察觉的巨集错误。此外,它还会检测引数的型
态,做必要的型别转换(巨集对你有害;除非绝对必要,否则别再用它了)。

注意:过度使用行内函数会让程式码肥胖,於分页(paging)环境下反而有负面的性
能影响。

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

inlinevoidf(inti,charc){/*...*/}

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

classFred{
public:
voidf(inti,charc){/*...*/}
};

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

classFred{
public:
voidf(inti,charc);
};

inlinevoidFred::f(inti,charc){/*...*/}


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

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

建构子乃用来从零开始建立物件。

建构子就像个「初始化函数」;它把一堆散乱的位元组成一个活生生的物件。最低限
度它会初始化内部用到的栏位,也可能会配置所须的资源(记忆体、档案、semaphore
、socket等等)。

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

 

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

没有办法。

原因是:如果你呼叫另一个建构子,编译器会初始化一个暂时的区域性物件;但并没
有初始化“这个”你想要的物件。你可以用预设参数(defaultparameter),将两
个建构子合并起来,或是在私有的"init()"成员函数中共享它们的程式码。

 

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

解构子乃物件之葬礼。

解构子是用来释放该物件所配置到的资源,譬如:Lock类别可能会锁住一个
semaphore,解构子则用来释放它。最常见的例子是:当建构子用了"new"以後,解
构子用"delete"。

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


==
■□第6节:运算子多载
==

Q20:运算子多载(operatoroverloading)是做什麽的?

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

运算子多载让C/C++的运算子,能对自订的型态(物件类别)赋予自订的意义。它
们形同是函数呼叫的语法糖衣(syntacticsugar):

classFred{
public:
//...
};

#if0
Fredadd(Fred,Fred);//没有运算子多载
Fredmul(Fred,Fred);
#else
Fredoperator+(Fred,Fred);//有运算子多载
Fredoperator*(Fred,Fred);
#endif

Fredf(Freda,Fredb,Fredc)
{
#if0
returnadd(add(mul(a,b),mul(b,c)),mul(c,a));//没有...
#else
returna*b+b*c+c*a;//有...
#endif
}

 

Q21:哪些运算子可以/不能被多载?

大部份都可以被多载。
不能的C运算子有"."和"?:"(和以技术上来说,可算是运算子的"sizeof")。
C++增加了些自己的运算子,其中除了"::"和".*".之外都可以被多载。

底下是个足标(subscript)运算子的例子(它会传回一个参考)。最前面是“不用
”多载的:

classArray{
public:
#if0
int&elem(unsignedi){if(i>99)error();returndata[i];}
#else
int&operator[](unsignedi){if(i>99)error();returndata[i];}
#endif
private:
intdata[100];
};

main()
{
Arraya;

#if0
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()"。因此,夥伴函数可让物件类别
设计者挑选他看得最顺眼的语法,以降低维护成本。

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

 

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

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

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

 

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

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

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

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


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

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

用夥伴函数operator<<:

classFred{
public:
friendostream&operator<<(ostream&o,constFred&fred)
{returno<<fred.i;}
//...
private:
inti;//只为了说明起见而设的
};

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

istream&operator>>(istream&i,Fred&fred);
file://^^^^^-不是"constFred&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常见的错误。

如果你的程式像这样:

inti=0;
while(!cin.eof()){
cin>>x;
++i;
file://workwithx
}

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

inti;
while(cin>>x){
++i;
file://workwithx
}


Q31:为什麽我的程式执行完第一次回圈後,会对输入的要求不加理睬?

因为读取数值的程式,把非数字的字元留在输入缓冲区(inputbuffer)里头了。

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

如果你的程式如下:

charname

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章