庞大资源库的计算机教程网站!
设为首页
加入收藏
总编信箱
投稿或申请专栏请先 [登 陆]
首页 操作系统 程序设计 图形图像 媒体动画 机械电子 WEB开发 数 据 库 办公系列 路由技术 网络原理 网络应用
认证考试 安全技术
首页>程序设计>C++语言>BCB/C++基础>正文
资料搜索
Google搜索
Google
返回上级列表

推荐文章

快速保存网页中所有图片的方法
Windows中让光驱巧妙“隐身”技
防范非法用户入侵Win 2000/XP系
两款比较典型的ASP木马防范方法
有关表格边框的css语法整理
Windows XP中可以被禁用的服务
SQL Server导出导入数据方法
Javascript所有对象的属性的获
网页(HTML)中的特殊字符
与篮球共舞,尽显模式本色
QQ病毒的手工清除方法
Photoshop为极品美女打造性感睫
天衣无缝:IIS与PHP水火也相容
SQL Server存储过程编写和优化

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

 作者:本站收集   日期:2005-8-4 19:31:22
字号选择〖 〗/ 双击滚屏 单击停止   

第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:怎样把指向成员函数的指标传给 signal handler、X event callback 等等? 
Q113:当我想以成员函数做为中断服务常式 (ISR) 时,为什麽编译器产生(型态不 
       符)的错误? 
Q114:为什麽我取不出 C++ 函数的位址? 
Q115:怎样宣告指向成员函数的指标阵列? 
 
第19节:容器类别与 template 
----------------------------- 
Q116:怎样自一个连结串列/杂凑表等等里面,插入/存取/改变元素? 
Q117:「样版」(template)的用意是什麽? 
Q118:"function template" 的语法/语意是什麽? 
Q119:"class template" 的语法/语意是什麽? 
Q120:什麽是「参数化型别」(parameterized type)? 
Q121:「泛型」(genericity)是什麽? 
 
第20节:程式库 
---------------- 
Q122:怎样拿到 "STL"? 
Q123:怎样 ftp 到 "Numerical Recipes" 附的程式? 
Q124:为什麽我的执行档会这麽大? 
 
第21节:特定系统的细节 
------------------------ 
Q125:GNU C++ (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:为什麽浮点数 (floating point) 这麽不精确?为什麽这段程式不会印出 0.43? 
 
⊙22B:其他环境下的琐事 
Q134:有任何 TeX 或 LaTeX 的巨集,能处理 "C++" 的留白效果(spacing)吗? 
Q135:在哪儿可拿到 C++2LaTeX 这个 C++原始码的 LaTeX 美编工具(pretty 
       printer)? 
Q136:该到哪里取得 "tgrind" 这个 C++/C/etc 的原始码美编工具? 
Q137:有给 GNU emacs 编辑器用的 C++-mode 吗?有的话,该怎麽拿? 
Q138:我要到哪儿得到和作业系统相关的 FAQs( 譬如:BC++、DOS、Windows 等等)? 
Q139:为什麽我的 DOS C++ 程式说 "Sorry: floating point code not linked" 
       “抱歉,浮点运算程式码未连结进来”? 
Q140:为什麽当我没执行 BC45 IDE 的话,BC++ 做出来的 Windows 应用程式就不能用? 
 
========================= 
● 1C:术语及常用的缩写 
========================= 
 
这儿是一些此文件所采用的缩写: 
 
     字汇        意义 
     ====        =========== 
     fn          function ,函数(单数型) 
     fns         functions,函数(复数型) 
     param       parameter,参数 
     ptr         pointer,指标,C/C++ 的语法元素,宣告法:  int * p; 
     ref         reference,参考,C++ 的语法元素,宣告法:  int & r; 
     OO          object-oriented,物件导向 
     OOP         object-oriented programming,物件导向程式设计 
     OOPL        object-oriented programming language,物件导向语言 
     method      运作行为,"member function 成员函数" 的另一种说法 
                 【译注】"method" 是源自 Smalltalk 的术语,很常用於 OO 界。 
 
 
======================================================= 
■□ 第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++经验後,会发现这正是一种「资讯隐藏」的作法,是利 
而非弊。好比说,程式员应该以切近该问题的方式来写程式,而非以机器的语言来解 
题。 
 
======================================== 
 
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; 
         } 
 
你应该这样写: 
上一篇:C++语言常见问题解答(3)    下一篇:C++语言常见问题解答(2)  
[发送给好友]  [关闭窗口]  [返回顶部]   转载请注明来源:www.it00.com   
特别声明: 本站除部分特别声明禁止转载的专稿外的其他文章可以自由转载,但请务必注明出处和原始作者。文章版权归文章原始作者所有。对于被本站转载文章的个人和网站,我们表示深深的谢意。如果本站转载的文章有版权问题请联系编辑人员,我们尽快予以更正。
责任编辑: 原点 投稿作者: 本站收集
信息来源: 网络 录入时间: 2005-8-4 19:31:22
关于我们 - 广告服务 - 版权申明 - 网站地图 - 联系方式 - 总编信箱 - 会员投稿