1、C+语言常见问题解答3语言常见问题解答(3)2007-8-10 作者: 编辑:眼镜丢了 点击进入论坛第节:程式风格指导Q81:有任何好的C+程式写作的标准吗?感谢您阅读这份文件,而不是再发明自己的一套。但是请不要在comp.lang.c+里问这问题。几乎所有软体工程师,或多或少都把这种东西看成是大玩具。而且,一些想成为C+程式撰写标准的东西,是由那些不熟悉这语言及方法论的人弄出来的,所以最後它只能成为过去式的标准。这种摆错位置的现象,让大家对程式写作标准产生不信任感。很明显的,在comp.lang.c+问这问题的人,是想使自己更精进,不会因自己的无知而绊倒,然而一些回答却只是让情况更糟而已。Q
2、82:程式撰写标准是必要的吗?有它就够了吗?程式撰写标准不会让不懂OO的人变懂;只有训练及经验才有可能。如果它有用处的话,那就是抑制住那些琐碎无关紧要的程式片段当大机构想把零散的程式设计组织整合起来时,这些片段常常会出现。但事实上你要的不光是这种标准而已。它们提供的架构让新手少去担心一些自由度,但是系统化的方法论会比这些好看的标准做得更好。组织机构需要的是一致性的设计与实行“哲学”,譬如:强型别或弱型别?用指标还是参考介面?streamI/O还是stdio?C+程式该不该呼叫C的?反过来呢?ABC该怎麽用?继承该用为实作的技巧还是特异化的技巧?该用哪一种测试策略?一一去检查吗?该不该为每个资料
3、成员都提供一致的get和set介面?介面该由外往内还是由内往外设计?错误状况该用try/catch/throw还是传回值来处理?等等。我们需要的是详细的“设计”部份的半标准。我推荐一个三段式标准:训练、谘询顾问以及程式库。训练乃提供密集教学,谘询顾问让OO观念深刻化,而非仅仅是被教过而已,高品质的程式库则是提供长程的教学。上述三种培训都有很热门的市场景况。(【译注】无疑的,这是指美、加地区。)接受过上述培训的组织都有如此的忠告:买现成的吧,不要自己硬干(Buy,DontBuild.)。买程式库,买训练课程,买开发工具,买谘询顾问。想靠自学来达到成功的工具厂商及应用系统厂商,都会发现成功很困难。
4、【译注】这一段十分具有参考价值。不过有些背景资料得提供给各位参考。别忘了:作者是美国人,是以该地为背景,且留意一下他所服务的公司是做什麽的.:-)唉!国内有这麽多的专业顾问公司吗?:-少数人会说:程式撰写标准只是理想而已,但在上述的组织机构中,它仍有其必要性。底下的FAQs提供一些基本的指导惯例及风格。Q83:我们的组织该以以往C的经验来决定程式撰写标准吗?No!不论你的C经验有多丰富,不论你有多高深的C能力,好的C程式员并不会让你直接就成为好的C+程式员。从C移到C+并不仅是学习+的语法语意而已,一个组织想达到OOP的境界,却未将OO的精神放进OOP里的话,只是自欺罢了;会计的资产负债表会把
5、他们的愚蠢显现出来。C+程式撰写标准应该由C+专家来调整,不妨先在comp.lang.c+里头问问题(但是不要用codingstandard这种字眼;只要这样子问:这种技巧有何优缺点?)。找个能帮你避开陷阱的高手,上个训练课程,买程式库,看看好的程式库是否合乎你的程式撰写标准。绝对不要光靠自己来制定标准,除非你对它已有某种程度的掌握。没有标准总比有烂标准好,因为不恰当的官方说法会让不够聪明的平民难以追随。现在C+训练课程及程式库,已有十分兴盛的市场。再提一件事:当某个东西炙手可热时,招摇撞骗者亦随之而生;务必三思而後行。也要问一下从某处修过课的人,因为老手不见得也是个好教员。最後,选个懂得指导
6、别人的从业人员,而不是个对此语言方法论只有过时知识的全职教师。【译注】善哉斯言!Q84:我该在函数中间或是开头来宣告区域变数?在第一次用到它的地方附近。物件在宣告的时候就会被初始化(被建构)。如果在初始化物件的地方没有足够的资讯,直到函数中间才有的话,你可以在开头处初始个空值给它,等以後再设定其值;你也可以在函数中间再初始个正确的东西给它。以执行效率来说,一开始就让它有正确的值,会比先建立它,搞一搞它,之後再重建它来得好。以像String这种简单的例子来看,会有350%的速度差距。在你的系统上可能会不同;当然整个系统可能不会降低到300+%,但是“一定”会有不必要的性能衰退现象。常见的反驳是:
7、我们会替物件的每个资料提供set运作行为,则建构时的额外耗费就会分散开来。这比效能负荷更糟,因为你添加了维护的梦靥。替每个资料提供set运作行为就等於对资料不设防:你把内部实作技巧都显露出来了。你隐藏到的只有成员物件的实体“名字”而已,但你用到的List、String和float(举例来说)型态都曝光了。通常维护会比CPU执行时间耗费的资源更多。区域变数应该在靠近它第一次用到之处宣告。很抱歉,这和C老手的习惯不同,但是新的不见得就是不好的。Q85:哪一种原始档命名惯例最好?foo.C?foo.cc?foo.cpp?如果你已有个惯例,就用它吧。如果没有,看看你的编译器,看它用的是哪一种。典型的答
8、案是:.C,.cc,.cpp,或.cxx(很自然的,.C副档名是假设该档案系统会区分出.C.c大小写)。在ParadigmShift公司,我们在Makefiles里用.C,即使是在不区分大小写的档案系统下(在有区分的系统中,我们用一个编译器选项:假设.c档案都是C+的程式;譬如:IBMCSet+用-Tdp,ZortechC+用-cpp,BorlandC+用-P,等等)。Q86:哪一种标头档命名惯例最好?foo.H?foo.hh?foo.hpp?如果你已有个惯例,就用它吧。如果没有,而且你的编辑器不必去区分C和C+档案的话,只要用.h就行了,否则就用编辑器所要的,像.H、.hh或是.hpp。在P
9、aradigmShift公司,我们用.h做为C和C+的原始档案(然後,我们就不再建那些纯粹的C标头档案)。Q87:C+有没有像lint那样的指导原则?Yes,有一些常见的例子是危险的。但是它们都不尽然是坏的,因为有些情况下,再差的例子也得用上去。*Fred类别的设定运算子应该传回*this,当成是Fred&(以允许成串的设定指令)。*有任何虚拟函数的类别,都该有个虚拟解构子。*若一个类别有解构子,设定运算子,拷贝建构子其一的话,通常三者也都全部需要。*Fred类别的拷贝建构子和设定运算子,都该将它们的参数加上const:分别是Fred:Fred(constFred&)和Fred&Fred:op
10、erator=(constFred&)。*类别的子物件一定要用初始化串列(initializationlists)而不要用设定的方式,因为对使用者自订类别而言,会有很大的效率差距(3x!)。*许多设定运算子都应该先测试:我们是不是他们;譬如:Fred&Fred:operator=(constFred&fred)if(this=&fred)return*this;file:/.normalassignmentduties.return*this;有时候没必要测试,但一般说来,这些情况都是:没有必要由使用者提供外显的设定运算子的时候(相对於编译器提供的设定运算子)。*在那些同时定义了+=、+及=的
11、类别中,a+=b和a=a+b通常应该做同样的事;其他类似的内建运算子亦同(譬如:a+=1和+a;pi和*(p+i);等等)。这可使用二元运算子op=之型式来强制做到;譬如:Fredoperator+(constFred&a,constFred&b)Fredans=a;ans+=b;returnans;这样一来,有建构性的二元运算甚至可以不是夥伴。但常用的运算子有时可能会更有效率地实作出来(譬如,如果Fred类别本来就是个String,且+=必须重新配置拷贝字串记忆体的话,一开始就知道它的最後长度,可能会比较好)。=第节:Smalltalk程式者学习C+之钥=Q88:为什麽C+的FAQ有一节讨论
12、Smalltalk?这是用来攻击Smalltalk的吗?世界上主要的两个OOPLs是C+与Smalltalk。由於这个流行的OOPL已有第二大的使用者总数量,许多新的C+程式者是由Smalltalk背景跳过来的。这一节会回答以下问题:*这两个语言的差别?*从Smalltalk跳到C+的程式者,要知道些什麽,才能精通C+?这一节*!*不会*!*回答这些问题:*哪一种语言最好?*为什麽Smalltalk很烂?*为什麽C+很烂?这可不是对Smalltalk恐怖份子挑,让他们趁我熟睡时戳我的轮胎(在我很难得有空休息的这段时间内:-)。Q89:C+和Smalltalk的差别在哪?最重要的不同是:*静态型
13、别或动态型别?*继承只能用於产生子型别上?*数值语意还是参考语意(valuevsreferencesemantics)?头两个差异会在这一节中解释,第三点则是下一节的讨论主题。如果你是Smalltalk程式者,现在想学C+,底下三则FAQs最好仔细研读。Q90:什麽是静态型别?它和Smalltalk有多相似不像?静态型别(statictyping)是说:编译器会“静态地”(於编译时期)检验各运算的型态安全性,而不是产生执行时才会去检查的程式码。例如,在静态型别之下,会去侦测比对函数引数的型态签名,不正确的配对会被编译器挑出错误来,而非在执行时才被挑出。OO的程式里,最常见的型态不符错误是:欲对
14、某物件启动个成员函数,但该物件并未准备好要处理该运算动作。譬如,如果Fred类别有成员函数f()但没有g(),且fred是Fred类别的案例,那麽fred.f()就是合法的,fred.g()则是非法的。C+(静态地)在编译期捕捉型别错误,Smalltalk则(动态地)在执行期捕捉。(技术上,C+很像Pascal“半”静态型别因为指标转型与union都能用来破坏型别系统;这提醒了我们:你用指标转型与union的频率,只能像你用goto那样。)Q91:静态型别与动态型别哪一种比较适合C+?若你想最有效率使用C+,请把她当成静态型别语言来用。C+极富弹性,你可以(藉由指标转型、union或#defi
15、ne)让她长得像Smalltalk。但是不要这样做。这提醒了我们:少用#define。有些场合,指标转型和union是必要的,甚至是很好的做法,但须谨慎为之。指标转型等於是叫编译器完全信赖你。错误的指标转型可能会毁坏堆积、在别的物件记忆体中乱搞、呼叫不存在的运作行为、造成一般性错误(generalfailure)。这是很糟糕的事。如果你避免用与这些相关的东西,你的C+程式会更安全、更快,因为能在编译期就检测的东西,就不必留到执行期再做。就算你喜欢动态型别,也请避免在C+里使用,或者请考虑换另一个将型态检查延迟到执行期才做的语言。C+将型态检验100%都放在编译时期;她没有任何执行期型态检验的内
16、建机制。如果你把C+当成一个动态型别的OOPL来用,你的命运将操之汝手。Q92:怎样分辨某个C+物件程式库是否属於动态型别的?提示#1:当所有东西都衍生自单一的根类别(rootclass),通常叫做Object。提示#2:当容器类别containerclasses,像List、Stack、Set等,都不是template版的。提示#3:当容器类别(List、Stack、Set等)把插入取出的元素,都视为指向Object的指标时。(你可以把Apple放进容器中,但当你取出时,编译器只知道它是衍生自Object,所以你得用指标转型将它转回Apple*;你最好祈祷它真的是个Apple,否则你会脑充血
17、的。)你可用dynamic_cast(於1994年才加入的)来使指标转型安全些,但这种动态测试依旧是“动态”的。这种程式风格是C+动态型别的基本要素,你可以呼叫函数:把这个Object转换成Apple,或是给我个NULL,如果它不是Apple的话,你就得到动态型别了:直到执行时期才知道会发生什麽事。若你用template去实作出容器类别,C+编译器会静态侦测出99%的型态资讯(99%并不是真的;有些人宣称能做到100%,而那些需要持续性(persistence)的人,只能得到低於100%的静态型别检验)。重点是:C+透过template来做到泛型(genericity),而非透过继承。Q93:
18、在C+里怎样用继承?它和Smalltalk有何不同?有些人认为继承是用来重用程式码的。在C+中,这是不对的。说明白点,继承不是为重用程式码而设计的。【译注】这一个分野相当重要。否则,C+使用者就会感染继承发烧症(inheritancefever)。C+继承的目的是用来表现介面一致性(产生子类别),而不是重用程式码。C+中,重用程式码通常是靠成份(composition)而非继承。换句话说,继承主要是用来当作特异化(specialization)的技术,而非实作上的技巧。这是与Smalltalk主要的不同之处,在Smalltalk里只有一种继承的型式(C+有private继承共享程式码,但不承袭
19、其介面,有public继承表现kind-of关系)。Smalltalk语言非常(相对於只是程式的习惯)允许你置放一个override覆盖(它会去呼叫个我看不懂的运作行为),以达到隐藏住继承下来的运作行为的“效果”。更进一步,Smalltalk可让观念界的is-a关系“独立於”子类别阶层之外(子型别不必也是子类别;譬如,你可以让某个东西是一个Stack,却不必继承自Stack类别)。相反的,C+对继承的限制更严:没办法不用到继承就做出“观念上的is-a”关系(有个C+的解决方法:透过ABC来分离介面与实作)。C+编译器利用公共继承额外附的语意资讯,以提供静态型别。Q94:Smalltalk/C+
20、不同的继承,在现实里导致的结果是什麽?Smalltalk让你做出不是子类别的子型别,做出不是子型别的子类别,它可让Smalltalk程式者不必操心该把哪种资料(位元、表现型式、资料结构)放进类别里面(譬如,你可能会把连结串列放到堆叠类别里)。毕竟,如果有人想要个以阵列做出的堆叠,他不必真的从堆叠继承过来;喜欢的话,他可以从阵列类别Array中继承过来,即使ArrayBasedStack并“不是”一种阵列!)在C+中,你不可能不为此操心。只有机制(运作行为的程式码),而非表现法(资料位元)可在子类别中被覆盖掉,所以,通常你“不要”把资料结构放进类别里比较好。这会促成AbstractBaseCla
21、sses(ABCs)的强烈使用需求。我喜欢用ATV和Maseratti之间的差别来比喻。ATV(allterrainvehicle,越野车)很好玩,因为你可以到处逛,任意开到田野、小溪、人行道等地。另一方面,Maseratti让你能高速行驶,但你只能在公路上面开。就算你喜欢自由表现力,偏偏喜欢驶向丛林,但也请不要在C+里这麽做;它不适合。Q95:学过纯种的OOPL之後才能学C+吗?不是(事实上,这样可能反而会害了你)。(注意:Smalltalk是个纯种的OOPL,而C+是个混血的OOPL。)读这之前,请先读读前面关於C+与Smalltalk差别的FAQs。OOPL的纯粹性,并不会让转移到C+更
22、容易些。事实上,典型的动态系结与非子型别的继承,会让Smalltalk程式者更难学会C+。ParadigmShift公司曾教过数千人OO技术,我们注意到:有Smalltalk背景的人来学C+,通常和那些根本没碰过继承的人学起来差不多累。事实上,对动态型别的OOPL(通常是,但不全都是Smalltalk)有高度使用经验的人,可能会“更难”学好,因为想把过去的习惯“遗忘”,会比一开始就学习静态型别来得困难。【译注】作者是以语言学习的角度来看的。事实上,若先有Smalltalk之类的物件导向观念的背景知识,再来学C+就不必再转换paradigm物件导向的中心思维是不会变的,变的只是实行细节而已。Q9
23、6:什麽是NIHCL?到哪里拿到它?NIHCL代表national-institute-of-healths-class-library,美国国家卫生局物件程式库。取得法:anonymousftp到128.231.128.7,档案:pub/nihcl-3.0.tar.Z。NIHCL(有人念作N-I-H-C-L,有人念作nickel)是个由Smalltalk转移过来的C+物件程式库。有些NIHCL用到的动态型别很棒(譬如:persistentobjects,持续性物件),也有些地方动态型别会和C+语言的静态型别相冲突,造成紧张关系。详见前面关於Smalltalk的FAQs。第节:参考与数值语意=
24、Q97:什麽是数值以及参考语意?哪一种在C+里最好?在参考语意(referencesemantics)中,设定是个指标拷贝的动作(也就是“参考”这个词的本意),数值语意(valuesemantics,或copysemantics)的设定则是真正地拷贝其值,而不是做指标拷贝的动作。C+让你选择:用设定运算子来拷贝其值(copy/value语意),或是用指标拷贝方式来拷贝指标(reference语意)。C+让你能覆盖掉(override)设定运算子,让它去做你想要的事,不过系统预设的(而且是最常见的)方式是拷贝其数值。参考语意的优点:弹性、动态系结(在C+里,你只能以传指标或传参考来达到动态系结,
25、而不是用传值的方式)。数值语意的优点:速度。对需要物件(而非指标)的场合来说,速度似乎是很奇怪的特点,但事实上,我们比较常存取物件本身,较不常去拷贝它。所以偶尔的拷贝所付出的代价,(通常)会被拥有真正的物件本身、而非仅是指向物件的指标所带来的效益弥补过去。有三个情况,你会得到真正的物件,而不是指向它的指标:区域变数、整体静态变数、完全被某类别包含在内(fullycontained)的成员物件。这里头最重要的就是最後一个(也就是成份)。後面的FAQs会有更多关於copy-vs-reference语意的资讯,请全部读完,以得到较平衡的观点。前几则会刻意偏向数值语意,所以若你只读前面的,你的观点就会
26、有所偏颇。设定(assignment)还有别的事项(譬如:shallowvsdeepcopy)没在这儿提到。Q98:虚拟资料是什麽?怎麽样为什麽该在C+里使用它?虚拟资料让衍生类别能改变基底类别的物件成员所属的类别。严格说来,C+并不支援虚拟资料,但可以模拟出来。不漂亮,但还能用。欲模拟之,基底类别必须有个指标指向成员物件,衍生类别必须提供一个new到的物件,以让原基底类别的指标所指到。该基底类别也要有一个以上正常的建构子,以提供它们自己的参考(也是透过new),且基底类别的解构子也要delete掉被参考者。举例来说,Stack类别可能有个Array成员物件(采用指标),衍生类别Stretch
27、ableStack可能会把基底类别的成员资料Array覆盖成StretchableArray。想做到的话,StretchableArray必须继承自Array,这样子Stack就会有个Array*。Stack的正常建构子会用newArray来初始化它的Array*,但Stack也会有一个(可能是在protected:里)特别的建构子,以自衍生类别中接收一个Array*;StretchableArray的建构子会用newStretchableArray把它传给那个特别的建构子。优点:*容易做出StretchableStack(大部份的程式都继承下来了)。*使用者可把StretchableStack当成“是一种”Stack来传递。缺点:*多增加额外的间接存取层,才能碰到Array。*多增加额外的自由记忆体配置负担(new与delete)。*多增加额外的动态系结负担(原因请见下一则FAQ)。
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1