C++语言常见问题解答3.docx

上传人:b****7 文档编号:23775656 上传时间:2023-05-20 格式:DOCX 页数:20 大小:30.01KB
下载 相关 举报
C++语言常见问题解答3.docx_第1页
第1页 / 共20页
C++语言常见问题解答3.docx_第2页
第2页 / 共20页
C++语言常见问题解答3.docx_第3页
第3页 / 共20页
C++语言常见问题解答3.docx_第4页
第4页 / 共20页
C++语言常见问题解答3.docx_第5页
第5页 / 共20页
点击查看更多>>
下载资源
资源描述

C++语言常见问题解答3.docx

《C++语言常见问题解答3.docx》由会员分享,可在线阅读,更多相关《C++语言常见问题解答3.docx(20页珍藏版)》请在冰豆网上搜索。

C++语言常见问题解答3.docx

C++语言常见问题解答3

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

2007-8-10    作者:

      编辑:

眼镜丢了  点击进入论坛

第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;

          file:

//...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++ 编译器利用公共继承

额外附的语意资讯,以提供静态型别。

 

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)。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1