effective_c  _second_edition(中文版)(可编辑)_第1页
effective_c  _second_edition(中文版)(可编辑)_第2页
effective_c  _second_edition(中文版)(可编辑)_第3页
effective_c  _second_edition(中文版)(可编辑)_第4页
effective_c  _second_edition(中文版)(可编辑)_第5页
已阅读5页,还剩112页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

EFFECTIVE_C_SECOND_EDITION中文版关于本电子书制作本书的目的是为了方便大家的阅读感谢LOSTMOUSE为我们翻译了这么好的C名著希望大家多多支持他并希望他能继续给我们带来更多更好的文章为了加强本书的完整性特将侯老师翻译的前言和导读也加了近来其余均为LOSTMOUSE翻译制作PROPHETMSNLZPHDHOTMAILCOM欢迎志同道合的朋友互相交流来源HTTPCOMBAT60320060529善待周围的人以感恩的心对待生活中的任何人和事译者简介LOSTMOUSE简介认识CSDN已经很久了但开始写技术文章不过是不久前的事开辟这个专栏更是出于偶然因为工作方面的原因不能保证自己一直会有大块的时间来维护这个专栏但我会尽力至于LOSTMOUSE本人并非卡通中那只聪明可爱的小老鼠相反却是一只将至而立却还在迷惘的老猫眼神固然明亮坚定内心深处却还不知道下一个目标在哪儿猫的天性只是等待吗作为一个程序员很喜欢程序人生的说法写一段程序和亲历一段人生何其相似只是LOSTMOUSE过去的程序里滥用了太多的IFSWITCHBREAK甚至FOR我期望很快自己写出的程序更完美更高效相片中是我和2岁的儿子斗斗没出现的另一位主角是我的妻子妞妞正是有了你们我才觉得我所做的一切都有了意义欢迎程序人生路上的每一位朋友和我联系NETTEDFISH21CNCOMNETTEDFISHYAHOOCOM前言这本书是多年来我对专业程式员所做的C教学课程下的一个自然产物我发现大部份学生在一个星期的密集训练之后即可适应这个语言的基本架构但要他们将这些基础架构以有效的方式组合运用我实在不感乐观於是我开始尝试组织出一些简短明确容易记忆的准则做为C高实效性程式开发过程之用那都是经验丰富的C程式员几乎总是会奉行或几乎肯定要避免的一些事情我最初的兴趣在於整理出一些可被某种LINTLIKE程式施行的规则最后我甚至领导一个计划研究某种可将C原始码中违反使用者指定条件之处检验出来的工具你可以在EFFECTIVEC网站上找到此研究的一份概要报告不幸的是在我尚未完成其完整原型之前这个研究计划便结束了幸运的是目前市面上已有这类C检验工具商品而且不只一个虽然我最初的兴趣是在研究可被某种工具自动实施的程式设计准则但我很快了解到那个研究方向的局限性优秀的C程式员所奉行的准则多数都难以公式化要不就是虽然它们有许多重要的例外情况却被程式员盲目地奉行不渝这使我念头一转某些东西虽然不比电脑程式精准但仍能比一本泛泛的C教科书更集中火力更打到重点这个念头的结果就是你手上这本书一本内含50个有效建议如何改善你的C程式技术和你的设计思维的书在这本书中你会发现一些忠告告诉你应该做些什麼为什麼如此告诉你不应该做些什麼又为什麼如此基本而言当然WHYS比WHATS更重要但检阅一列列准则也确实比强记一本或两本教科书更轻松更方便得多和大部份的C书籍不同我的组织方式并非以语言特性做为依据也就是说我并不在某处集中讨论CONSTRUCTORS建构式在另一处集中讨论VIRTUALFUNCTIONS虚拟函式又在第三个地方集中讨论INHERITANCE继承机制不不是这样本书的每一个讨论主题都剪裁合度地以一个个准则陈列出来至於我对某特定语言性质的探讨散布面积可能涵盖整本书这种作法的优点就是比较容易反映出特意挑选C做为开发工具的那些软体系统的复杂度在那些系统之中光只了解个别语言特性是不够的例如有经验的C程式员知道了解INLINE函式和了解VIRTUALDESTRUCTORS并不一定表示你了解INLINEVIRTUALDESTRUCTORS身经百战的开发人员都认知到理解C各个特性之间的互动关系才是有效使用这个语言的最重要关键本书组织反映出这一基本事实这种作法的缺点是你恐怕必须前后交叉参考而非只看一个地方才能发现我所说的某个C架构的全貌为了将不方便性降至最低我在书中各处放了许多交叉索引书后并有一份涵盖全部范围的索引译注为了协助读者更容易掌握EFFECTIVEC和MOREEFFECTIVEC二书我以EFFECTIVECCD为本为两书的中文版额外加上两书之间的交叉索引此乃原书所无如果文中出现像条款M5这样的参考指示M便是代表MOREEFFECTIVEC筹划第二版期间我改写此书的雄心一再被恐惧所取代成千上万的程式员热情拥抱EFFECTIVEC第一版我不希望破坏吸引他们的任何东西但是自从我写了第一版之后六年过去了C有了变化C程式库有了变化见条款49我对C的了解也有了变化乃至於C的用途也有了变化许许多多的变化对我而言重要的是我必须修订EFFECTIVEC以反映那些变化我尝试一页一页地修改内容但是书籍和软体十分类似局部加强是不够的唯一的机会就是系统化地重写本书就是重写后的结果EFFECTIVEC20版熟悉第一版的读者可能有兴趣知道书中的每一个条款都经过重新检验然而我相信第一版的结构至今仍是流畅的所以整本书的结构并没有改变50个条款中我保留了48个其中某些标题稍有变化附随的讨论内容亦复如此退休下来被取代的两个条款是32和49不过原条款32的许多资讯被我移到如今焕然一新的条款1中我将条款41和42的次序做了对调因为这样比较能够适当呈现它们修订后的内容最后我把上一版继承体系图所采用的箭头方向颠倒过来以符合目前几乎已经一致的习惯从DERIVEDCLASSES指往BASECLASSES我的MOREEFFECTIVEC一书也采用相同习惯本书最后列有该书摘要本书提供的准则离钜细糜遗的程度还很远但是完成一个好的准则一个几乎可於任何时间应用於任何程式的准则动手远比动嘴困难得多如果你知道其他准则可以协助撰写有效的C程式我非常乐意听到你告诉我它们的故事此外说不定你会觉得本书的某些条款不适合成为一般性忠告或许你认为另有比较好的方法来完成书中所说的任务或许你认为某些条款在技术讨论方面不够清楚不够完全抑或有误导之嫌我衷心盼望你也能够让我知道你的这些想法DONALDKNUTH译注经典书籍THEARTOFCOMPUTERPROGRAMMINGVOLUMEIIIIII的作者长久以来为挑出其书错误的热心读者准备有一份小小的报酬这个故事传为美谈追求完美的精神令人佩服看过那麼多仓促上市错误垒垒的C书籍后我更是特别强烈地希望踵随KNUTH的风范因此如果有人挑出本书的任何错误并告诉我不论是技术文法错别字或任何其他东西我将在本书新刷的时候把第一位挑出错误的读者大名加到致谢名单中请将你的建议你的见解你的批评以及喔真糟你的臭虫报告寄至SCOTTMEYERSCOPUBLISHERCORPORATEANDPROFESSIONALPUBLISHINGADDISONWESLEYLONGMANINC1JACOBWAYREADINGMA01867USA或者传送电子邮件到ECAWLCOM我维护有本书第一刷以来的修订记录其中包括错误更正文字修润以及技术更新你可以从EFFECTIVEC网站取得这份记录如果你希望拥有这份资料但无法上网请寄申请函到上述地址我会邮寄一份给你SCOTTDOUGLASMEYERSSTAFFORDOREGONJULY1997导读学会一个程式语言是一回事儿学会如何以此语言设计并实作出有效的程式又是一回事儿C尤其如此因为它很不寻常地涵盖了罕见的威力和丰富的表现力不但建立在一个全功能的传统语言C之上更提供极为广泛的物件导向OBJECTORIENTED性质以及对TEMPLATES和EXCEPTIONS异常状态的支援假以适当运用C是个可以让你感受愉悦的夥伴各种不同的设计方式包括物件导向型式和传统型式都可以直接在这个语言中表现并有效地实作出来你可以定义新的资料型别它们和语言内建的型别表面上无分轩轾实质上则更具弹性明智地选用一些谨慎设计的CLASSES自动完成记忆体管理别名ALIASING处理初始化动作与清理动作型别转换以及软体开发的其他难题与祸根可以使程式设计更容易更直观更有效更少错误是的要写出有效的C程式并不会太困难如果你知道怎麼做的话如果没有什麼训练与素养就冒然使用C会导至做出来的码不易理解不易维护不易扩充缺乏效率而且容易出错关键在於找出C可能绊倒你的状况有哪些然后学习如何避开它们这正是本书的目的我假设你已经认识C并对它有某种程度的使用经验我提供一些准则让你更有效地使用这个语言使你的软体容易理解容易维护容易扩充效率高而且行为如所预期我提出的忠告分为两大类一般性的设计策略以及特殊的比较难得一见的语言性质设计方面的讨论集中在如何对不同的方法俾得以C达成某个目标做取舍如何在INHERITANCE继承和TEMPLATES范本之间做选择在TEMPLATES和GENERICPOINTERS泛型指标之间在PUBLICINHERITANCE公开继承和PRIVATEINHERITANCE私有继承之间在PRIVATEINHERITANCE和LAYERING分层技术之间在FUNCTIONOVERLOADING函式多载化和PARAMETERDEFAULTING参数预设值之间在VIRTUALFUNCTION虚拟函式和NONVIRTUALFUNCTIONS非虚拟函式之间在PASSBYVALUE传值和PASSBYREFERENCE传址之间一开始就做出正确的决定是很重要的因为不正确的选择或许不会一下子就浮现影响但是在开发过程的后期矫正它往往很困难很花时间很混乱很令人沮丧事倍功半成本很高在你确切知道你要做什麼之后把它做对恐怕也不是件太容易的事什麼是ASSIGNMENT运算子的适当传回型别当OPERATORNEW无法找出足够的记忆体它该有怎样的行为DESTRUCTOR何时应该被宣告为VIRTUAL你应该写一个MEMBERINITIALIZATIONLIST成员初值列吗在如斯细节中努力也颇具有决定性因为如果不这样常会导至意料之外或神秘难解的程式行为更糟的是这类脱轨行为可能不会立即浮现这些恐怖的码或能通过品管检验却仍然藏匿著许多未侦测出来的臭虫不定时炸弹正等待引爆这不是本得一页页读下去才有感觉的书籍你甚至不需要依序读它所有素材被我分为50个条款每一个都相当独立不过条款之间会彼此参考所以阅读本书的一种方法是先从感兴趣的条款开始然后遵循其参考指示进一步读下去所有条款被我分为七大类如果你对某类主题特别感兴趣例如记忆体管理或物件导向设计可以从相关章节开始一路读下去或是跳跃前进不过最后你会发现本书的所有内容对於高实效的C程式设计而言都十分基础而重要所以几乎每个条款最后都会和其他条款互有牵连这并不是一本C参考工具书也不是一本让你从头学习C的书例如虽然我热切告诉你一些有关撰写自己的OPERATORNEW的注意事项条款710但是我假设你可以从其他地方获知OPERATORNEW必须传回一个VOID其第一引数的型别必须是SIZE_T许多C语言书可以带给你这样的资讯这本书的目的是要强调那些其他书籍往往浅浅带过如果有的话的C程式设计概念其他书籍描述的是C语言的各个成份本书则告诉你如何将那些成份组合起来完成一个有效的程式其他书籍告诉你如何让程式顺利编译本书则告诉你如何避开编译器不会告诉你的一些问题和大部份语言一样C有著丰富的传统在程式员之间口耳相传形成这个语言的伟大传承的一部份我企图在这本书中以容易阅读的型式记录一些长久累积而来的智慧然而在此同时我必须告诉你本书仅限於正统的可移植的C语言只有明列於ISOANSI标准见条款M35中的性质才会被本书采用本书之中移植性是个关键考量如果你想要寻找因编译器而异的特殊技法本书不适合你但是啊呀标准规格所描述的C与社区软体商店所卖的编译器S的表现多少有点出入所以当我指出某个新的语言特性颇有用处时我也会告诉你如何在缺乏那些特性的情况下产出有效的软体毕竟在确知未来即将如何如何之际却忽略那些美丽远景而尽做些低下的劳力工作容我坦言是相当愚蠢的但是反过来看你也不能在最新最伟大的C编译器S降临世界之前空自等待而束手无策呀你必须和你手上可用的工具一起打拼而本书正打算帮助你这麼做注意我说编译器S复数不同的编译器对标准C的满足程度各不相同所以我鼓励你至少以两种编译器S来开发程式这麼做可以帮助你避免不经意仰赖某个编译器专属的语言延伸性质或是误用某个编译器对标准规格的错误阐示这也可以帮助你避免使用过度先进的编译器特殊技术例如独家厂商才做得出来的某种语言新特性如此特性往往实作不够精良臭虫多要不就是表现迟缓或两者兼具而且C社群往往对这些特性缺乏使用经验无法给你应用上的忠告雷霆万钧之势固然令人兴奋但当你的目标是要产出可靠的码恐怕还是步步为营并且能够与人合作得好你在本书中找不到C的必杀秘笈也看不到通往C完美软体的唯一真理50个条款中的每一个带给你的都只是准则包括如何完成较好的设计如何避免常见的问题如何到达更好的效率但任何条款都不可能放之四海皆准软体的定义和实作是极为复杂的工作常会受到硬体作业系统以及应用软体的束缚所以我能够做的最好事情就是提供一些准则让你可以依循产生出比较好的程式如果任何时候你都奉行每一个条款应该不太可能掉进最常见的一些C陷阱不过准则毕竟只是准则可能存在例外情况那正是为什麼每个条款都带有一堆解释的原因这些解释是本书最重要的资产唯有彻底了解一个条款背后的基本原理你才能合理决定此条款是否适用於手上的专案或你正艰苦奋斗的难题上本书的最佳用途就是增进你对C行为的了解知道它为什麼有那样的表现以及如何将其行为转化为你的利益盲目运用本书所列的条款并不适当不过话说回来你或许不应该在缺乏好理由的情况任意违反任何一个条款这样性质的书籍中专用术语的解释并非重点所在那样的工作顶好是留给语言界的律师去做然而有少量C辞汇是每个人都应该要懂的以下术语一再出现所以有必要确定你我之间对它们有共同的认知所谓宣告DECLARATION用来将一个OBJECTFUNCTIONCLASS或TEMPLATE的型别名称告诉编译器宣告式并不带有细目资讯下面统统都是宣告EXTERNINTXOBJECTDECLARATIONINTNUMDIGITSINTNUMBERFUNCTIONDECLARATIONCLASSCLOCKCLASSDECLARATIONTEMPLATECLASSSMARTPOINTERTEMPLATEDECLARATION所谓定义DEFINITION用来将细目资讯提供给编译器对OBJECT而言其定义式是编译器为它配置记忆体的地点对FUNCTION或FUNCTIONTEMPLATE而言其定义式提供函式本体FUNCTIONBODY对CLASS或CLASSTEMPLATE而言其定义式必须列出该CLASS或TEMPLATE的所有MEMBERSINTX这是物件的定义式INTNUMDIGITSINTNUMBER这是函式的定义式此函式传回其参数的数位DIGITS个数INTDIGITSSOFAR1IFNUMBER0NUMBERNUMBERDIGITSSOFARWHILENUMBER10DIGITSSOFARRETURNDIGITSSOFARCLASSCLOCK这是CLASS的定义式PUBLICCLOCKCLOCKINTHOURCONSTINTMINUTECONSTINTSECONDCONSTTEMPLATECLASSSMARTPOINTER这是TEMPLATE的定义式PUBLICSMARTPOINTERTP0SMARTPOINTERTOPERATORCONSTTOPERATORCONST上述程式码把我们带往所谓的CONSTRUCTORSDEFAULTCONSTRUCTOR意指可以不需任何引数就被唤起者这样的一个CONSTRUCTOR如果不是没有任何参数就是每个参数都有预设值通常当你需要定义物件阵列时就会需要一个DEFAULTCONSTRUCTORCLASSAPUBLICADEFAULTCONSTRUCTORAARRAYA10呼叫CONSTRUCTORS10次CLASSBPUBLICBINTX0DEFAULTCONSTRUCTORBARRAYB10呼叫CONSTRUCTORS10次每次都给引数0CLASSCPUBLICCINTX这不是一个DEFAULTCONSTRUCTORCARRAYC10错误或许有时候你会发现某个CLASS的DEFAULTCONSTRUCTOR有预设参数值你的编译器却拒不接受其物件阵列例如某些编译器拒绝接受上述ARRAYB的定义即使它其实符合C标准这是存在於C标准规格书和实际编译器行为之间的一个矛盾例子截至目前我所知道的每一个编译器都有一些这类不相容缺点在编译器厂商追上C语言标准之前请保持你的弹性并安慰自己也许不久后的某一天C编译器的表现就可以和C标准规格书所描述的一致了附带一提如果你想要产生一个物件阵列但该物件型别没有提供DEFAULTCONSTRUCTOR通常的作法是定义一个指标阵列取而代之然后利用NEW一一将每个指标初始化CPTRARRAY10没有呼叫任何CONSTRUCTORSPTRARRAY0NEWC22配置并建构一个C物件PTRARRAY1NEWC4同上这个作法在任何场合几乎都够用了如果不够你或许得使用条款14所说的更高层次也因此更不为人知的“PLACEMENTNEW“方法回到术语来所谓COPYCONSTRUCTOR系以某物件做为另一同型物件的初值CLASSSTRINGPUBLICSTRINGDEFAULTCONSTRUCTORSTRINGCONSTSTRINGRHSCOPYCONSTRUCTORPRIVATECHARDATASTRINGS1呼叫DEFAULTCONSTRUCTORSTRINGS2S1呼叫COPYCONSTRUCTORSTRINGS3S2呼叫COPYCONSTRUCTOR或许COPYCONSTRUCTOR最重要的用途就是用来定义何谓以BYVALUE方式传递和传回物件例如考虑以下效率不佳的作法以一个函式串接两个STRING物件CONSTSTRINGOPERATORSTRINGS1STRINGS2STRINGTEMPDELETETEMPDATATEMPDATANEWCHARSTRLENS1DATASTRLENS2DATA1STRCPYTEMPDATAS1DATASTRCATTEMPDATAS2DATARETURNTEMPSTRINGA“HELLO“STRINGB“WORLD“STRINGCABCSTRING“HELLOWORLD“其中OPERATOR需要两个STRING物件做为参数并传回一个STRING物件做为运算结果不论参数或运算结果都是以BYVALUE方式传递所以在OPERATOR进行过程中会有一个COPYCONSTRUCTOR被唤起用以将A当做S1的初值再有一个COPYCONSTRUCTOR被唤起用以将B当做S2的初值再有一个COPYCONSTRUCTOR被唤起用以将TEMP当做C的初值事实上只要编译器决定产生中介的暂时性物件就会需要一些COPYCONSTRUCTOR呼叫动作见条款M19重点是PASSBYVALUE便是呼叫COPYCONSTRUCTOR的同义词顺带一提你不能够真的像上述那样实作STRINGS的OPERATOR传回一个CONSTSTRINGOBJECT是正确的见条款21和23但是你应该以BYREFERENCE方式见条款22传递那两个参数其实如果你有外援并不需要为STRINGS撰写OPERATOR事实上你的确有外援因为C标准程式库条款49就内含有一个STRING型别带有一个OPERATOR做的事情几乎就是上述OPERATOR的行为本书中我并用STRING和STRING两者注意前者名称以大写开头后者否但方式不同如果我只是需要一般字串不在意它是怎麼做出来的那麼我便使用标准程式库提供的STRING这也是你应该选择的行为然而如果我打算剖析C的行为并因而需要某些实作码来示范或验证我便使用非标准的那个STRINGCLASS身为一个程式员只要必须用到字串就应该尽可能使用标准的STRING型别那种开发自己的字串类别以象徵具备C某种成熟功力的日子已经过去了不过你还是有必要了解开发一个像STRING那样的CLASSES所需知道的课题对示范或验证目的而且可说只对此种目的而言STRING很是方便无论如何除非你有很好的理由否则都不应该再使用旧式的CHARBASED字串具有良好定义的STRING型别如今已能够在每一方面比CHARS更具优势并且更好包括其执行效率见条款49和条款M29M30接下来两个需要掌握的术语是INITIALIZATION初始化和ASSIGNMENT指派物件的初始化行为发生在它初次获得一个值的时候对於带有CONSTRUCTORS之CLASSES或STRUCTS初始化总是经由唤起某个CONSTRUCTOR达成这和物件的ASSIGNMENT动作不同后者发生於已初始化之物件被指派新值的时候STRINGS1INITIALIZATION初始化STRINGS2“HELLO“INITIALIZATION初始化STRINGS3S2INITIALIZATION初始化S1S3ASSIGNMENT指派纯粹从操作观点看INITIALIZATION和ASSIGNMENT之间的差异在於前者由CONSTRUCTOR执行后者由OPERATOR执行换句话说这两个动作对应不同的函式动作C严格区分此二者原因是上述两个函式所考虑的事情不同CONSTRUCTORS通常必须检验其引数的有效性VALIDITY而大部份ASSIGNMENT运算子不必如此因为其引数必然是合法的因为已被建构完成另一方面ASSIGNMENT动作的标的物并非是尚未建构完成的物件而是可能已经拥有配置得来的资源在新资源可被指派过去之前旧资源通常必须先行释放这里所谓的资源通常是指记忆体在ASSIGNMENT运算子为一个新值配置记忆体之前必须先释放旧值的记忆体下面是STRING的CONSTRUCTOR和ASSIGNMENT运算子的可能作法以下是一个可能的STRINGCONSTRUCTORSTRINGSTRINGCONSTCHARVALUEIFVALUE如果指标VALUE不是NULLDATANEWCHARSTRLENVALUE1STRCPYDATAVALUEELSE处理NULL指标此一接受一个CONSTCHAR引数的STRINGCONSTRUCTOR有能力处理传进来的指标为NULL的情况标准的STRING可没如此宽容企图以一个NULL指标产生一个STRING其结果未有定义不过以一个空的CHARBASED字串例如“产生一个STRING物件倒是安全的DATANEWCHAR1以下是一个可能的STRINGASSIGNMENT运算子STRINGSTRINGOPERATORCONSTSTRINGRHSIFTHISRHSRETURNTHIS见条款17DELETEDATA删除释放旧有的记忆体DATA配置新的记忆体NEWCHARSTRLENRHSDATA1STRCPYDATARHSDATARETURNTHIS见条款15注意CONSTRUCTOR必须检验其参数的有效性并确保MEMBERDATA都被适当地初始化例如一个CHAR指标必须被适当地加上NULL结束字元亦请注意ASSIGNMENT运算子认定其参数是合法的反倒是它会侦测诸如自己指派给自己这样的病态情况见条款17或是集中心力确保配置新记忆体之前先释放旧有记忆体这两个函式的差异象徵物件初始化INITIALIZATION和物件指派ASSIGNMENT两者的差异顺带一提如果DELETE这样的表示法对你而言很陌生条款5和条款M8应该能够消除你的任何相关疑惑我要讨论的最后一个术语是CLIENT客户CLIENT代表任何使用你所写的码的人当我在本书提及CLIENTS我指的便是任何观察你的码并企图理解它们的人我也是指阅读你的CLASS定义并企图决定是否可以继承它们的人我同时也是指那些审查你的设计并希望洞察其中原理的人你或许还不习惯去想到你的CLIENTS但是我会尽量说服你设法让他们的生活愉快一些毕竟你也是他人所开发的软体的CLIENT难道你不希望那些人让你的生活愉快一些吗此外也许有一天你会发现你必须使用自己所写的码译注指那些CLASSES或LIBRARIES那时候你的CLIENT就是你自己我在本书用了两个你可能不甚熟悉的C性质它们都是晚近才加入C标准之中第一个是BOOL型别其值若非TRUE就是FALSE两者都是关键字语言内建的相对关系运算子如的传回型别都是BOOLIFFORWHILEDO等述句的条件判断式的传回型别也是BOOL如果你的编译器尚未实作出BOOL型别你可以利用TYPEDEF模拟BOOL再以两个CONST物件模拟TRUE和FALSETYPEDEFINTBOOLCONSTBOOLFALSE0CONSTBOOLTRUE1这种手法相容於传统的CC语意使用这种模拟作法的程式在移植到一个支援BOOL型别的编译器平台后行为并不会改变如果你想知道另一种BOOL模拟法包括其优缺点讨论请参考MOREEFFECTIVEC的导读部份第二个新特性其实有四样东西分别是STATIC_CASTCONST_CASTDYNAMIC_CASTREINTERPRET_CAST四个转型运算子传统的C转型动作如下TYPEEXPRESSION将EXPRESSION转为TYPE型别新的转型动作则是这样STATIC_CASTEXPRESSION将EXPRESSION转为TYPE型别CONST_CASTEXPRESSIONDYNAMIC_CASTEXPRESSIONREINTERPRET_CASTEXPRESSION这些不同的转型运算子有不同的作用用来将物件或指标的常数性CONSTNESS转型掉我将在条款21验CONST_CAST证这个主题用来执行安全的向下转型动作SAFEDOWNCASTING这是条款39DYNAMIC_CAST的主题的转型结果取决於编译器例如在函式指标型别之间做转型动作你REINTERPRET_CAST大概不常需要用到REINTERPRET_CAST本书完全没有用到它是个杂物袋没有其他适当的转型运算子可用时就用这个它最STATIC_CAST接近传统的C转型动作传统的C转型动作仍然合法但是新的转型运算子比较受欢迎它们更容易在程式码中被识别出来不论是对人类或是对诸如GREP等工具而言而且愈是缩小范围地指定各种转型运算子的目标编译器愈有可能诊断出错误的运用例如只有CONST_CAST才可以用来将某物的常数性CONSTNESS转换掉如果你尝试使用其他转型运算子来转换物件或指标的常数性一定会踢到铁板欲知这些新式转型动作的更多资讯请看条款M2或查阅较新的C语言书籍M代表MOREEFFECTIVEC是我的另一本书本书最后附有一份该书摘要本书的程式范例中我设法为OBJECTSCLASSESFUNCTIONS取一些有意义的名称许多书籍在选用识别名称时都喜欢恪守一句箴言简短是智慧的灵魂但是我不我喜欢一切都交待得清清楚楚我努力打破传统坚不使用那种隐秘而不易为人识破天机的名称但偶尔我会被诱惑所屈服使用两个我最欢迎的参数名称其意义可能并不浅显易懂特别是如果你从未在任何编译器开发团队待过的话这两个参数名称是LHS和RHS分别意味“LEFTHANDSIDE“左端和“RIGHTHANDSIDE“右端我以它们做为二元运算子各函式的参数名称尤其是OPERATOR和算术运算子如OPERATOR举个例子如果A和B代表两个分数RATIONALNUMBERS物件而如果分数可经由一个NONMEMBERFUNCTIONOPERATOR相乘那麼算式AB等於这款形式的函式呼叫OPERATORAB我将宣告OPERATOR如下一如你在条款23所见CONSTRATIONALOPERATORCONSTRATIONALLHSCONSTRATIONALRHS如你所见左运算元A成为函式中的LHS右运算元B成为函式中的RHS我也利用缩写字来为指标命名规则如下指向型别T之物件的指标我称为PT意思是“POINTERTOT“下面是几则例子STRINGPSPSPTRTOSTRINGCLASSAIRPLANEAIRPLANEPAPAPTRTOAIRPLANECLASSBANKACCOUNTBANKACCOUNTPBAPBAPTRTOBANKACCOUNT对於REFERENCES我亦采用类似习惯也就是说RS大约就是一个REFERENCETOSTRINGRA则可能是一个REFERENCETOAIRPLANE当我谈到MEMBERFUNCTIONS偶而我会使用MF这个名称为避免任何混淆任何时候我在书中提到C程式设计时我说的是ISOANSI版的C语言而不是旧式的没那麼STRONGLYTYPED强型式的古典C语言第一章从C转向C对每个人来说习惯C需要一些时间对于已经熟悉C的程序员来说这个过程尤其令人苦恼因为C是C的子集所有的C的技术都可以继续使用但很多用起来又不太合适例如C程序员会认为指针的指针看起来很古怪他们会问为什么不用指针的引用来代替呢C是一种简单的语言它真正提供的只有有宏指针结构数组和函数不管什么问题C都靠宏指针结构数组和函数来解决而C不是这样宏指针结构数组和函数当然还存在此外还有私有和保护型成员函数重载缺省参数构造和析构函数自定义操作符内联函数引用友元模板异常名字空间等等用C比用C具有更宽广的空间因为设计时有更多的选择可以考虑在面对这么多的选择时许多C程序员墨守成规坚持他们的老习惯一般来说这也不是什么很大的罪过但某些C的习惯有悖于C的精神本质他们都在下面的条款进行了阐述条款1尽量用CONST和INLINE而不用DEFINE这个条款最好称为尽量用编译器而不用预处理因为DEFINE经常被认为好象不是语言本身的一部分这是问题之一再看下面的语句DEFINEASPECT_RATIO1653编译器会永远也看不到ASPECT_RATIO这个符号名因为在源码进入编译器之前它会被预处理程序去掉于是ASPECT_RATIO不会加入到符号列表中如果涉及到这个常量的代码在编译时报错就会很令人费解因为报错信息指的是1653而不是ASPECT_RATIO如果ASPECT_RATIO不是在你自己写的头文件中定义的你就会奇怪1653是从哪里来的甚至会花时间跟踪下去这个问题也会出现在符号调试器中因为同样地你所写的符号名不会出现在符号列表中解决这个问题的方案很简单不用预处理宏定义一个常量CONSTDOUBLEASPECT_RATIO1653这种方法很有效但有两个特殊情况要注意首先定义指针常量时会有点不同因为常量定义一般是放在头文件中许多源文件会包含它除了指针所指的类型要定义成CONST外重要的是指针也经常要定义成CONST例如要在头文件中定义一个基于CHAR的字符串常量你要写两次CONSTCONSTCHARCONSTAUTHORNAME“SCOTTMEYERS“关于CONST的含义和用法特别是和指针相关联的问题参见条款21另外定义某个类CLASS的常量一般也很方便只有一点点不同要把常量限制在类中首先要使它成为类的成员为了保证常量最多只有一份拷贝还要把它定义为静态成员CLASSGAMEPLAYERPRIVATESTATICCONSTINTNUM_TURNS5CONSTANTECLARATIONINTSCORESNUM_TURNSUSEOFCONSTANT还有一点正如你看到的上面的语句是NUM_TURNS的声明而不是定义所以你还必须在类的实现代码文件中定义类的静态成员CONSTINTGAMEPLAYERNUM_TURNSMANDATORYDEFINITIONGOESINCLASSIMPLFILE你不必过于担心这种小事如果你忘了定义链接器会提醒你旧一点的编译器会不接受这种语法因为它认为类的静态成员在声明时定义初始值是非法的而且类内只允许初始化整数类型如INTBOOLCHAR等还只能是常量在上面的语法不能使用的情况下可以在定义时赋初值CLASSENGINEERINGCONSTANTSTHISGOESINTHECLASSPRIVATEHEADERFILESTATICCONSTDOUBLEFUDGE_FACTORTHISGOESINTHECLASSIMPLEMENTATIONFILECONSTDOUBLEENGINEERINGCONSTANTSFUDGE_FACTOR135大多数情况下你只要做这么多唯一例外的是当你的类在编译时需要用到这个类的常量的情况例如上面GAMEPLAYERSCORES数组的声明编译过程中编译器一定要知道数组的大小所以为了弥补那些不正确地禁止类内进行整型类常量初始化的编译器的不足可以采用称之为借用ENUM的方法来解决这种技术很好地利用了当需要INT类型时可以使用枚举类型的原则所以GAMEPLAYER也可以象这样来定义CLASSGAMEPLAYERPRIVATEENUMNUM_TURNS5“THEENUMHACK“MAKESNUM_TURNSASYMBOLICNAMEFOR5INTSCORESNUM_TURNSFINE除非你正在用老的编译器即写于1995年之前你不必借用ENUM当然知道有这种方法还是值得的因为这种可以追溯到很久以前的时代的代码可是不常见的哟回到预处理的话题上来另一个普遍的DEFINE指令的用法是用它来实现那些看起来象函数而又不会导致函数调用的宏典型的例子是计算两个对象的最大值DEFINEABABAB这个语句有很多缺陷光想想都让人头疼甚至比在高峰时间到高速公路去开车还让人痛苦无论什么时候你写了象这样的宏你必须记住在写宏体时对每个参数都要加上括号否则别人调用你的宏时如果用了表达式就会造成很大的麻烦但是即使你象这样做了还会有象下面这样奇怪的事发生INTA5B0ABA的值增加了2次AB10A的值只增加了1次这种情况下内部发生些什么取决于它比较的是什么值幸运的是你不必再忍受这样愚笨的语句了你可以用普通函数实现宏的效率再加上可预计的行为和类型安全这就是内联函数见条款33INLINEINTINTAINTBRETURNABAB不过这和上面的宏不大一样因为这个版本的只能处理INT类型但模板可以很轻巧地解决这个问题TEMPLATEINLINECONSTTCONSTTACONSTTBRETURNABAB这个模板产生了一整套函数每个函数拿两个可以转换成同种类型的对象进行比较然后返回较大的常量对象的引用因为不知道T的类型返回时传递引用可以提高效率见条款22顺便说一句在你打算用模板写象这样有用的通用函数时先检查一下标准库见条款49看看他们是不是已经存在比如说上面说的你会惊喜地发现你可以后人乘凉是C标准库的一部分有了CONST和INLINE你对预处理的需要减少了但也不能完全没有它抛弃INCLUDE的日子还很远IFDEFIFNDEF在控制编译的过程中还扮演重要角色预处理还不能退休但你一定要计划给它经常放长假条款2尽量用而不用是的SCANF和PRINTF很轻巧很高效你也早就知道怎么用它们这我承认但尽管他们很有用事实上SCANF和PRINTF及其系列还可以做些改进尤其是他们不是类型安全的而且没有扩展性因为类型安全和扩展性是C的基石所以你也要服从这一点另外SCANFPRINTF系列函数把要读写的变量和控制读写格式的信息分开来就象古老的FORTRAN那样是该向五十年代说诀别的时候了不必惊奇SCANFPRINTF的这些弱点正是操作符和的强项INTIRATIONALRR是个有理数CINIRCOUTIR上面的代码要通过编译和必须是可以处理RATIONAL类型对象的重载函数可能要通过隐式类型转换如果没有实现这样的函数就会出错处理INT不用这样做因为它是标准用法另外编译器自己可以根据不同的变量类型选择操作符的不同形式所以不必劳你去指定第一个要读写的对象是INT而第二个是RATIONAL另外在传递读和写的对象时采用的语法形式相同所以不必象SCANF那样死记一些规定比如如果没有得到指针必须加上地址符而如果已经得到了指针又要确定不要加上地址符这些完全可以交给C编译器去做编译器没别的什么事好做的而你却不一样最后要注意的是象INT这样的固定类型和象RATIONAL这样的自定义类型在读写时方式是一样的而你用SACNF和PRINTF试试看你所写的表示有理数的类的代码可能象下面这样CLASSRATIONALPUBLICRATIONALINTNUMERATOR0INTDENOMINATOR1PRIVATEINTND分子分母FRIENDOSTREAMOPERATOROSTREAMSCONSTRATIONALOSTREAMOPERATOROSTREAMSCONSTRATIONALRSRNRDRETURNS上面的代码涉及到OPERATOR的一些微妙但很重要的用法这在本书其他地方详细讨论例如上面的OPERATOR不是成员函数条款19解释了为什么而且传递给OPERATOR的不是RATIONAL对象而是定义为CONST的对象的引用参见条款22OPERATOR的声明和实现也类似尽管我不大愿意承认可有些情况下回到那些经过证明而且正确的老路上去还是很有意义的第一有些IOSTREAM的操作实现起来比相应的CSTREAM效率要低所以不同的选择会给你的程序有可能虽然不一定参见条款M16带来很大的不同但请牢记这不是对所有的IOSTREAM而言只是一些特殊的实现参见条款M23第二在标准化的过程中IOSTREAM库在底层做了很多修改参见条款49所以对那些要求最大可移植性的应用程序来说会发现不同的厂商遵循标准的程度也不同第三IOSTREAM库的类有构造函数而里的函数没有在某些涉及到静态对象初始化顺序的时候如果可以确认不会带来隐患用标准C库会更简单实用IOSTREAM库的类和函数所提供的类型安全和可扩展性的价值远远超过你当初的想象所以不要仅仅因为你用惯了而舍弃它毕竟转换到IOSTREAM后你也不会忘掉顺便说一句本条款的标题没有打印错我确实说的是而非从技术上说其实没有这样的东西标准化委员会在简化非C标准头文件时用取代了它他们这样做的原因在条款49进行了解释还必须知道的是如果编译器同时支持和那头文件名的使用会很微妙例如如果使用了INCLUDE得到的是置于名字空间STD见条款28下的IOSTREAM库的元素如果使用INCLUDE得到的是置于全局空间的同样的元素在全局空间获取元素会导致名字冲突而设计名字空间的初衷正是用来避免这种名字冲突的发生还有打字时比少两个字这也是很多人用它的原因条款3尽量用NEW和DELETE而不用MALLOC和FREEMALLOC和FREE及其变体会产生问题的原因在于它们太简单他们不知道构造函数和析构函数假设用两种方法给一个包含10个STRING对象的数组分配空间一个用MALLOC另一个用NEWSTRINGSTRINGARRAY1STATIC_CASTSTRINGMALLOC10SIZEOFSTRINGSTRINGSTRINGARRAY2NEWSTRING10其结果是STRINGARRAY1确实指向的是可以容纳10个STRING对象的足够空间但内存里并没有创建这些对象而且如果你不从这种晦涩的语法怪圈详见条款M4和M8的描述里跳出来的话你没有办法来初始化数组里的对象换句话说STRINGARRAY1其实一点用也没有相反STRINGARRAY2指向的是一个包含10个完全构造好的STRING对象的数组每个对象可以在任何读取STRING的操作里安全使用假设你想了个怪招对STRINGARRAY1数组里的对象进行了初始化那么在你后面的程序里你一定会这么做FREESTRINGARRAY1DELETESTRINGARRAY2参见条款5这里为什么要加上个“调用FREE将会释放STRINGARRAY1指向的内存但内存里的STRING对象不会调用析构函数如果STRING对象象一般情况那样自己已经分配了内存那这些内存将会全部丢失相反当对STRINGARRAY2调用DELETE时数组里的每个对象都会在内存释放前调用析构函数既然NEW和DELETE可以这么有效地与构造函数和析构函数交互选用它们是显然的把NEW和DELETE与MALLOC和FREE混在一起用也是个坏想法对一个用NEW获取来的指针调用FREE或者对一个用MALLOC获取来的指针调用DELETE其后果是不可预测的大家都知道不可预测的意思它可能在开发阶段工作良好在测试阶段工作良好但也可能会最后在你最重要的客户的脸上爆炸NEWDELETE和MALLOCFREE的不兼容性常常会导致一些严重的复杂性问题举个例子里通常有个STRDUP函数它得到一个CHAR字符串然后返回其拷贝CHARSTRDUPCONSTCHARPS返回PS所指的拷贝在有些地方C和C用的是同一个STRDUP版本所以函数内部是用MALLOC分配内存这样的话一些不知情的C程序员会在调用STRDUP后忽视了必须对STRDUP返回的指针进行FREE操作为了防止这一情况有些地方会专门为C重写STRDUP并在函数内部调用了NEW这就要求其调用者记得最后用DELETE你可以想象这会导致多么严重的移植性问题因为代码中STRDUP以不同的形式在不同的地方之间颠来倒去C程序员和C程序员一样对代码重用十分感兴趣大家都知道有大量基于MALLOC和FREE写成的代码构成的C库都非常值得重用在利用这些库时最好是你不用负责去FREE掉由库自己MALLOC的内存并且或者你不用去MALLOC库自己会FREE掉的内存这样就太好了其实在C程序里使用MALLOC和FREE没有错只要保证用MALLOC得到的指针用FREE或者用NEW得到的指针最后用DELETE来操作就可以了千万别马虎地把NEW和FREE或MALLOC和DELETE混起来用那只会自找麻烦既然MALLOC和FREE对构造函数和析构函数一无所知把MALLOCFREE和NEWDELETE混起来用又象嘈杂拥挤的晚会那样难以控制那么你最好就什么时候都一心一意地使用NEW和DELETE吧条款4尽量使用C风格的注释旧的C注释语法在C里还可以用C新发明的行尾注释语法也有其过人之处例如下面这种情形IFABINTTEMPASWAPAANDBABBTEMP假设你出于某种原因要注释掉这个代码块从软件工程的角度看写这段代码的程序员也做得很好他最初的代码里也写了一个注释以解释代码在做什么用C形式的句法来注释掉这个程序块时嵌在里面的最初的注释不受影响但如果选择C风格的注释就会发生严重的错误IFABINTTEMPASWAPAANDBABBTEMP请注意嵌在代码块里的注释是怎么无意间使本来想注释掉整个代码块的注释提前结束的C风格的注释当然还有它存在的价值例如它们在C和C编译器都要处理的头文件中是无法替代的尽管如此只要有可能你最好尽量用C风格的注释值得指出的是有些老的专门为C写的预处理程序不知道处理C风格的注释所以象下面这种情形时事情就不会象预想的那样DEFINELIGHT_SPEEDP3E8MSECINAVACUUM对于不熟悉C的预处理程序来说行尾的注释竟然成为了宏的一部分当然正象条款1所说的那样你无论如何也不会用预处理来定义常量的第二章内存管理C中涉及到的内存的管理问题可以归结为两方面正确地得到它和有效地使用它好的程序员会理解这两个问题为什么要以这样的顺序列出因为执行得再快体积再小的程序如果它不按你所想象地那样去执行那也一点用处都没有正确地得到的意思是正确地调用内存分配和释放程序而有效地使用是指写特定版本的内存分配和释放程序这里正确地得到显得更重要一些然而说到正确性C其实从C继承了一个很严重的头疼病那就是内存泄露隐患虚拟内存是个很好的发明但虚拟内存也是有限的并不是每个人都可以最先抢到它在C中只要用MALLO

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论