《C++编程规范.pdf》由会员分享,可在线阅读,更多相关《C++编程规范.pdf(133页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第0章 为什么要用C+(11-15)0.1原因为什么选择C+而不是c?或者更抽象一点,为什么选择面向对象语言,而不是面向过程语言或汇编语言?这是 个很好的问题。有人可能心里知道一些,但说不清楚;有人可能会想到很多,并认为这是一个很泛泛的问题,说来话长。其实答案很简单:如果是一个技术人员在问这个问题,答案是“为了(更好地)复用代码”;如果是一个非技术人员在问(比如你的老板或是什么资本家),回答只需两个字“省钱”,或者让他眼睛发亮的四个字“省很多钱”。话虽不同,其背后的道理却是一样的。软件开发己经有几十年的历史了,每个人都知道这个行业最费人力,因为从开发到测试,再到维护,基本上以人的手工为主。我们
2、还知道,软件开发人员从来都是高薪阶层。所以,软件的成本主要源于人的成本。那么如何降低成本?代码复用成了持续不断的主题。这是因为如果代码能够复用,则相应的开发时间、测试时间,以及分析修改时间都能节省下来,而这些时间都对应于软件人员的高薪。可见,代码复用率越高,成本削减的越多。C+语言,或者说所有面向对象语言,就是针对代码复用设计的。我们可以列举一下面向对象语言的有名的特点:封装:把具体实现封装在类内,而类内类外的代码只靠一些公共接口联系起来,类内实现接口的功能,类外使用接口的功能。目的是什么?类内实现变化了,可以不影响类外代码(复用);类外使用代码变化了,可以不影响类内代码(也是复用)。继承:子
3、类可以继承父类的东西(复用),也可以扩展自己新的特性,这些新特性不会影响父类,也不会影响使用父类的代码(复用),甚至子类可以直接以父类的身份,使用所有父类可使用的代码(还是复用)。多态:父类和子类可以为同一个接口(复用)提供不同的实现,外部代码不需任何改动(复用)就可以拥有不同的特性。0.2语言的发展和代码复用个主流语言的出现,或者说语言发展的 诙质的飞跃,其背后都有代码复用的影子。c 语言取代汇编而流行,源于UNIX操作系统的开发。在这之前的操作系统,基本上是用汇编写成的,而 UNIX的 90%是 C,只 有 10%左右是汇编。带来的好处是,UNIX比其他操作系统更容易移植到不同的机器上,因
4、为不必重写所有代码,90%只需重新编译(当然需要改动,现在看来改动应该还是一件艰巨的工作,但比起用汇编语言重头写要省事多了)。C+较 之 C 在代码复用上的能力更强。一方面,C+试图不加修改地整块 类复用代码,而不像C 那样需要逐行扫描修改(如同UMX移植时);另一方面,C+的复用接口(主要是类接口)更丰富、灵活、安全,而 C 主要依靠函数接U,包容性太窄,而函数间的联系也太弱。我们再看看Java。它的流行不单是因为它也是一种面向对象的语言,还因为它在代码复用上有独到之处。我们基本上可以把一个网页看成一个程序,而浏览网页是将该程序从Internet上下载到本地机器上运行。但这个程序比较特殊,它
5、要求能运行在所有平台上(尽可能)。我们很难用C+来写这样的网页,因 为 C 中编译器只能编译出适应一种平台(CPU)的执行代码。另一种方式是用诸如HTML之类的语言,它们的特点是将源码下载到客户端,再由客户端解释执行。我们确实希望网页一次开发,能(复)用到所有平台,但有时我们不想公开源码(这实际上是不同组织间的代码复用问题,和一个组织内部的复用还不同,因为有商业利益或版权问题)。此时,Java可以帮助我们。它实际上是设计了一个虚拟平台(CPU),所 有 Java语言源码都会编译成可运行在这个虚拟平台 上 一 的二进制执行程序,而客户端的Java解释器负责将这个虚拟平台的程序指令解释成真正平台的
6、指令。可见,我们共享了目标级代码。0.3 代码复用的特点从以上的分析和我们自己的开发经验可以得出代码复用的两个特点。一是代码复用是个由筒到繁、从局部到整体的不断发展的过程。由于软件本身的复杂性,我们不可能一下子把代码做到复用率极高的程度。复用的经验和手段是一个逐渐积累的过程。具体到我们自己的程序,不要指望它们一下子成为代码复用的经典,从复用一个类、一个函数,甚至一两个好的编程风格或想法入手,日积月累,你手边会逐渐形成 个复用代码库,它将是你经验和财富的宝库。如果你所在的部门己经有几个人拥有复用代码库,那么恭喜你的部门,它可以成立一个技术委员会,负责收集和整合不同的复用代码库,这标志着你的部门己
7、具备较高的专业水准和开发较大规模程序的能力。代码复用的另一个特点是:为了复用,牺牲性能、“浪费”系统资源都不在话下。我们从汇编程序、C 程序、C+程序,再 到 Java程序可以看出,复用性越强,性能越差(成倍下降),程序尺寸越大(成倍增加),但我们还是乐此不疲。究其原因,无非是性能、系统资源可以靠突飞猛进的硬件能力弥补,而硬件的成本比起人力资源根本不值一提。由此再顺便提醒大家一句,与其沉醉于程序性能的提高,不如关注代码的可复用性。0.4代码复用对我们的影响回到本书的话题。本书列举了几百条编码原则,其实试图说明两个问题:一是如何防范错误;一是如何更好地发挥C+语言的特点。我自认为代码复用的境界更
8、高,超出本书的范畴,但我还希望大家能思考一下,这几百条原则对代码复用有何帮助。另一方面,大家不妨回忆一下自己开发的C+代码,看看它们有多大程度的可复用性。如果不高,我们能不能问一下自己:“我有充分的理由用C+吗?效率的损失、资源的浪费都值得吗?”我们还可以考虑下今后的项目。第1章 命 名 原 则好的命名原则在软件开发中是很重要的,尤其在可复用代码中更是如此。好的命名应该是直观而容易理解的,在移入新环境或上下文后仍能保持这种清晰的特点,并且不易与其他组件产生名字冲突。原 则 1.1 关于类型名1.1.1 说明注意:缩写字当作普通字处理,即只有首字母大写。1.1.2 例子/类名class T np
9、pCoverage Area_T(II.;/枚举类型名enum pageCode_T(II.);/自定义类型名typedef short Intl6_T;1.1.3 原因防止与变量名冲突(变量名定义请见“原 则 1.2关于变量和函数名”):这种类型名定义方法和变量名会有两处不同,一是名字的第一个字母大小写不同,二是类型名以_T 结尾。使得类型名更加清晰,尤其_T 可以突出表示这是一个类名(T 代表TYPE的意思):_ T 来 源 于 POSIX的类型命名:POSIX统一采用 命名其类名。为沿用其思想而又防止和POSIX软件包冲突(请 参 阅“原 则 1.9命名时避免使用国际组织占用的格式”)而
10、采用_T。区分名字中各单词也可用下划线,但用大写字母会使得名字短些。缩写字当作普通字处理:一是为了防止破坏该原则而造成混淆,请比较GPSReceiver_T和 GpsReceiver_T谁更清楚;二是为了防止和全大写的常量名等混淆(全大写的名字请见”原 则 1.4关于宏、常量和模板名”)。因为namespace是表示一个逻辑组,与 class或 enum的某些用法类似,所以采用同样的命名原则。原 则 1.2 关于变量和函数名变量和函数名中首字母小写,其后每个英文单词的第一个字母大写,其他小写。(同时参阅:“原 则 1.7关于匿名命名空间级标识符的前缀”、“原 则 1.9命名时避免使用国际组织占
11、用的格式”以及函数名的例外“原 则 1.3关于全大写的函数名(建议)”。)注意:缩写字当作普通字处理,即只有首字母大写。1 2.2 例子/变量名int flexPageCount;/函数名(length)class String_T(publ ic:int length(void)coust;/.);比较类型名和变量名GpsCommand_T gpsCommand;原 则1.3关于全大写的函数名(建议)1.3.1 说明有一类函数,它们调用普通函数,只是对普通函数的错误返回做一般化处理。这些函数的名字要和所包含的函数名相同,只是全用大写字母(必要时用下划线分隔名字中的英文单词)。1.3.2 例子
12、/哆嗦的用法FILE*pFile=fopen(abc.txt,rw+”);if(pFile=NULL)(/错误处理:打印错误信息等abort();)ret=fseek(pFile,0,SEEK_END);if(ret!=0)(错误处理:打印错误信息等abort();ret=fwrite(buffer,100,1,pFile);if(ret100)/错误处理:打印错误信息等abort();)简洁的用法inline FILE*F0PEN(char const*filename,char const*mode)(FILE*pFile=fopen(fileName,mode);if(pFile=NUL
13、L)(错误处理:打印错误信息等abort();return pHle;/正常则返回相应的文件句柄)FSEEK()和 FWRITE()依此类推,因而上面哆嗦的代码变成HLE*pFile=FOPEN(abc.txt,rw+);FSEEK(Wile,0,SEEK_END);FWRITE(buffer,100,1,pFile);1.3.3原因阅读简洁,读者的思路不会总是被(非主流的)错误处理代码打断。此类函数与原函数名只有大小写的区别:不会改变原函数名的意思。由于区别小,阅读时有可能误以为就是原函数,从而减少了新加的函数给阅读者造成的额外负担(注意:区别不能太小,否则录入时容易出错)。阅读时即便误以为
14、是原函数也不会出问题,因为它们的功能完全相同,区别只是错误处理方式。效率高:可用inline方式实现这类函数,相比上面“哆嗦的用法”效率一点也不损失。当在程序中要反复做错误检查时,这类函数会节省许多编程时间。原则1.4关于宏、常量和模板名14.1说明这些名字要全部大写;如有多个单词,用下划线分隔。宏指所有用宏形式定义的名字,包括常量类和函数类;常量也包括枚举中的常量成员。1.4.2例子/常量类的宏#define PIE 3.1415926/函数类的宏#define MAX(a,b)(/*.*/)/常量const int LENGTH=1024;/枚举中的常量成员eenumBLUE,RED,WH
15、ITE);/模板名templete(class TYPE_T)class List_T(public:void add(TYPE_T counst&value);/.);1.4.3 原因使得此类名称更加清晰,防止与其他类型的名字(主要是变量名)冲突。函数宏之所以也用此原则是因为它在预编译时被展开(与普通函数有很大的不同),需要阅读的人意识到这一点(这是沿袭了 C 语言的规范)。模板用此原则是因为此种类型名非常近似于“#define。原 则1.5关于指针编标识符名(供参考)1.5.1 说明建议以p 开头或以Ptr结尾。1.5.2 例子/指针变量名char*pName;/函数指针类型的名字type
16、def int(*CallbackFunctionPtr _T)(int parameter);1.5.3 原因使阅读者不用查定义就能意识到这是一个指针:对指针的操作与其他类型变量有很大不同,比 如 对 其 成 员 的 访 问 要 用 而 不 是 特别是可能需要考虑内存回收的问题等,所以应给阅读者和使用者一个较明显的提醒。原 则1.6关于变量名前缀(供参考)1.6.1 说明用下面不同的前缀来修饰变量名以区分不同的作用域:i_类内数据成员(对象级成员);c_类内静态数据成员(类级成员);g_全局变量;匚文件作用域变量(静态变量)。函数内部等局部变量前不用前缀。1.6.2 例子class Mess
17、age_T(/类内静态数据成员static int c_ id;/类内首通数据成员(对象级)int i_id;public:void someFunction(void)/函数内的局部变量int id;/.);/全局变量int g_id;/静态变量(文件作用域)static int f_id;1.6.3 原因减少作用域重叠时(不同作用域中的)变量名冲突问题。比如在上例someFunctionOll变量id、i_id、c_id、f_id、g_id都可见?若无前缀,名字就都相同G中突)了。这样也减少了为避免名字冲突而无规律可循地改变变量名。例如,将 someFunction()中的变量id 改名为
18、m y ld,阅读者可能无法一目了然地知道为何要在Id前加m y,会猜测有无特殊含义等。使得阅读者在读某个作用域代码时更清晰,知道碰到的变量是在哪个作用域中定义的。前缀的含义:i_表示 instance scope;c_表示 class scope;匚表示 file scope:g_表示 global scope o下划线前缀的用法:有些人喜欢用下划线前缀修饰类内私有成员,而另一些人则用它表示全局变量。但遗憾的是它们 都 与 IS O 组织关于下划线前缀用法相冲突(见“原 则 1.9 命名时避免使用国际组织占用的格式”)。常量前缀的处理方法:常量和变量一样,也有作用域和名字冲突问题,因此也适用
19、于此原则。不过由于常量名是全大写,前面加上小写的前缀稍嫌混淆。另一变通方法是将常量前缀改为大写,但造成此条规则复杂化(5 项前缀变9 项),可能给阅读者造成更大的麻烦,建议还是不要这样做。类型前缀:有些组织对于变量前缀定义得更细致,比如用n 代表整数类型(int,short,long等)、用 c 代表字符型等,就如同本书用p 作为指针类型的前缀一样。这些都是可以的。但需要提醒的是,变量命名(以及所有的命名)有两个极端,一个是什么前后缀都不加(精炼但有用信息过少),另一个是加上所有相关的信息(Package名、作用域提示、类型提示等,信息全面但显得哆嗦,且重要信息不突出),合理的命名方式肯定在这
20、两个极端中间的某处,但具体在哪里有赖于大家自己的判断。原 则1.7关于匿名命名空间级标识符的前缀1.7.1 说明给匿名命名空间级标识符一个公共前缀(如所属Package名或Library名,加下划线),用来区别其他提供类似功能的Packet或 Library等。匿名命名空间中的标识符指的是全局或文件级变量名、常量名、宏、类型名、函数名等。前缀格式:全大写字母,(最好)少于3 个字母。1.7.2 例子/H A 出项目中的代码class HA_ CheckPointTable_T.class HA_HashMap_T.UML函 数 库(第三方提供的标准函数库)中的代码class UML_HashM
21、ap_T.1.7.3 原因如果希望代码复用,则匿名命名空间级标识符就需要防止命名冲突。用 Packet名 或 Library名是一个不错的选择。选择全大写是为了醒目,并尽量与其后的真正的名字区别开来(因为真正名字才包含该标识符的意义、途等)。限制其长度是为了防止它干扰对后面真正的名字的理解(要知道该前缀只是为了防止命名冲突,不能喧宾夺主)。原 则 1.8减少匿名命名空间级标识符1.8.1 说明尽量减少匿名命名空间级变量、常量、宏及函数等标识符。可以归类放在某个命名间、类或函数中。1.8.2 例子class CommonDefinilion_T.(public:const float PIE;/
22、.);1.8.3 原因明显减少命名冲突。归类后使用更清晰。缩短生命周期,使之只存在于它应该发挥作用的有限时间内,从而减少麻烦:更好记、更好维护、不会被误用或滥用等。原 则 1.9命名时避免使用国际组织占用的格式1.9.1己知的被占用的格式双下划线开头ISO C+、ANSIC;包含双下划线ISO C+;单下划线开头ISO C+、ANSIC;E 0_9A_Z 开头ANSIC;is a_z 开头ANSIC;to a_z 开头ANSIC;LC一开头ANSIC;SIGLA_Z 开头ANSI C;str a_z 开头ANSI C:mem a_z 开头ANSI C;wcs a_z 开头ANSIC;结尾POS
23、IX;其他国际组织占用的格式。1.9.2 原因减少潜在的命名冲突。防止阅读者误以为是国际组织提供的代码。原 则 1.1 0 名字要本着清楚、简单的原则1.10.1说明名字本身首先要做到清楚,从而帮助(而不是混淆)对代码的理解;其次在清楚的前提下尽量简单,简单本身也是要使阅读者更容易理解。理解之后才能谈到使用,使用之后才有修改和提高。1.10.2 例子/不好理解的名字int shldwncnt;int rs;int num;比较一下int she 11 DownloadCount;int returnstatus;int alarmNumber;1.10.3 定量分析的参考可以将名字长度限制在3
24、 到 25个字符之间,并据此编写或利用现成的工具自动扫描代码,以检查名字是否做到“简单、清楚”。少于3 个字符通常不够清楚(选 择 3 是因为Ihs、rhs之类的名字应该足够清晰,见“原 则 L26关于函数的左值参数和右值参数名”);大于25个字符则(感觉上)嫌不够简单。下限的3 个字符应该不包括公共前缀 如 package名 HA_、变量作用域i_、指针前缀p、公共 后 缀(如类型名中的的_T)以及下划线等,为的是确保名字中真正核心的部分足够清晰:而上限的 25个字符应包含名字中所有字符。原则1.1 1 尽量用可发音的名字1.11.1 例子/不可发音的名字class Ymdhms;/可发音的
25、名字class Timestamp_T;1.11.2 原因可发音的名字更好读、更好懂,也更便于交流。原则1.1 2 尽量用英文命名原因英语是最通用的语言,特别是在程序语言中,其他语言(比如选择汉语拼音)可能造成阅读者理解上的困难。原则1.1 3 尽量选择通用词汇并贯穿始终1.13.1 说明许多单词都能表达一个含义,要选择一个最通用的(大家在编写类似代码时约定俗成的),并且始终保持这一用法。1.13.2 例子比如get、read,fetch,retrieve都能表达“取出”的意思、,在定义一个有“取出”功能的函数时函数名用get或 read较为常见。一旦定下使用哪一个(比如read)就坚持用到底
26、。如果一会儿用read,一会儿又用g e t,读者可能会误以为这两个函数有区别。原则1.1 4 避免用模棱两可、晦涩或不标准的缩写1.14.1 原因好的缩写会明显简化名字,还能清楚地表达出原意。不好的缩写则相反,不如不要,就算因此导致名字长度倍增也在所不惜。此时要记住:(任何情况下都是)录入易,维护难。所谓好与不好,最简单的标准就是看该缩写是否通用,越通用越好。1.14.2 例子class 地h_T;/是 paragraph 的缩写吗原 则1.1 5避免使用会引起误解的词汇例子比如用portList来描述一组po rt,如果其数据结构不是链表就不合适,因为约定俗成List就是指链表,不要小看这
27、一点,它会减少很多头疼的事。原 则1.1 6减少名字中的冗余信息1.1 6.1 例子比如类的成员名不需要包含类名;class Alarm_T.(Severity_T darmseverity(void)const;/用 severity。就足够了);1.16.2 原因不要让阅读者花额外精力来区分哪些是有用信息,那些是冗余信息。原 则1.1 7建议起名尽量通俗,太专一会限制以后的扩展例子比如定义了一个手机短语消息类,其中有个域表示消息来源,用 sourcesubscriberlD做名字就不如用source通用。要预知将来的扩展有时很难,但在初次定义时想一下今后可能的扩展不是坏事。原 则1.1 8
28、名字最好尽可能精确地表达其内容例子没人知道data、infotuff的内容到底是什么。原 则1.1 9避免名字中出现形状况混淆的字母或数字例子/*字母o 和数字。形状类似,避免混用;*实在无法避免,最好总是用小写,这样和数字0 区别*还大一点*/#define F00#define FoO/*同样,字母1和数字1 避免混用;无法避免时*字母1最好总用大写形式L*/const long VALUE=0 x43211;const long VALUE=0 x4321L;原则1.2 0 命名类和成员使得object.method 0M有意义1.20.1 例子t imer.clear();t imer
29、.start();1.20.2 原因这是增加代码可读性、减少冗余信息的一种方法。原则1.2 1 类和对象名应是名词1.21.1 例子class Alarm_T;int length;1.21.2 原因类和对象名用来标明事物,而事物应是名词。原则1.2 2 实现行为的类成员函数名应有尽有动词1.2 2.1 说 明实现行为的类成员函数应是动词,有可能还包含“直接对象”(如例子中的position),此时要把“直接对象”名放在动词之前。1.22.2 例子class Gps_Tvoid positionCalculate(void);void reset(void););1.22.3 原因“直接对象”
30、很有可能是另一个实现类,放在前面可以提醒读者(提供线索)。原则1.2 3 类的存取和查询成员函数名应是名词或形容词1.23.1 说明存取函数是用来读取和修改对象属性的,与属性本身同名显得自然而不同罗嗦。查询函数用于返回对象的信息。非布尔型查询函数应是名词,如 size。布尔型查询函数应是形容词,通常用is作 前 缀,如 isEmpty()。1.23.2 例子class Shape_T(public:/存取函数Color_T color(void)const;/getColor()显得眼嗦void(olor Color_T nst&aColor);/(olorSet()setColor。显得眼嗦
31、/非布尔型查询函数int area(void)const;/布尔型查询函数bool isVisible(void)const;);原 则 1.2 4 变量名应是名词1.24.1 说明类的成员名(变量名、常量名等)应是名闻。(可能带若干形容词作修饰,名词要放在最前面起突出作用。)1.24.2 例子class Gps_T(/类的成员Almanac_T i_almanac;int i_satellitesVisible;);原 则 1.2 5 布尔型的名字要直观1.25.1 说明is通常是一个不错的前缀,好不好可用if语句来检验。1.25.2 例子class Queue_T(public;/布尔型函
32、数名bool isFull(void)const;bool contains(Object_T anObject)Const;);/用 if语句检查,证明is 开头不错if(queue.isFull()./用 if语句检查,此时用is不合适if(queue.contains(thisObject).原 则 1.2 6 关于函数的左值参数和右值参数名1.26.1 说明用 Ihs做左值参数的名字,用 rhs做右值参数的名字。1.26.2 例子/类的拷贝构造函数MyC I ass_T:MyClass_T(MyClass_T const&rhs);/赋值函数int operator=(String c
33、onst&lhs,String const&rhs);1.2 6.3 原因这是被广泛使用的种编程约定。既通用又省事,不必再费神给此类参数起“清楚”、“简单”的名字了。原则1.2 7避免局部名和外层的名字冲突1.2 7.1 例子time.h 定义了 time。函数#include(time.h)int test(void)(/不好的定义,和外层的time。函数同名Time time;/好的定义Time timeStart;);1.27.2 原因这种冲突会给读者带来不必要的混淆、有可能成为真正的bug且非常难查。原则1.2 8用a、an、any区分重名(参数)1.28.1 例子/函数名、参数名和类
34、成员变量名类似void MyClass_T:severity(int aSeverity)i_severity=aSeverity;)1.28.2 原因有效(避免冲突)。名字的含义不变。代价小。原则1.2 9模板类型名应有意义1.29.1 例子 用 T l、T 2不好template classT l,intT2class Vector_T(T1 i_dataT2;);/用 TYPE-T和 SIZE就清楚多了template(class TYPE_T,int SIZE)class Vector_T(TYPE_T i_dataSIZE;;1.29.2 注意注意遵守“原 则1.1关于类型名”的约定
35、(_T)。第2章类型的使用类型是代码中的基本数据单元。正确定义和使用恰当的类型会避免许多问题。原则2.1避免隐式声明类型2.1.1 说明尽量用显式声明。2.1.2 例子main();是 int main(void)的隐式表示法void 6o Const Mdue);/是 void foo(const int aValue)的隐式表示法原则2.2慎用无符号类型2.2.1 说明避免使用无符号类型,除非真的需要。按位访问的数据和设备寄存器通常要用无符号类型。2.2.2 原因混用有符号和无符号类型会导致奇怪的结果,因为其中会发生隐式类型转换。不同的C+标准中有符号和无符号的转换规则不同。原则2.3少用
36、浮点数除非必须2.3.1 例子int baudRale=9600;int symbolsln 15msec;使用了浮点数,能避免吗symbolsln 15msec=(int)(baudRate*0.015);/看来可以symbolsln 15msec=(baudRate*15)/1000;2.3.2 原因浮点数不精确:因为计算机内部是二进制表示法,需要将代码中十进制浮点数转换成二进制。由于字长的限制,经常不得不丢掉最低的几位小数,所以结果是不精确的。浮点数的异常处理复杂(比如上溢、下溢等的处理)。运算速度慢。原则2.4用typedef简化程序中的复杂语法2.4.1 例子 用 typedef简化
37、函数指针typedd int(*CallbackFunchr_T)(int parameter);2.4.2 原因当语法非常复杂时(比如函数指针的定义很难读,尤其当该函数比较复杂时),用 typedef可以大大简化复杂度,使得阅读理解都更容易。2.4.3 定量分析的参考包 含 4 个以上独立元素的语法应被视为复杂语法,如上例中的函数指针定义,不 算 typedef的独立元素数为5o原 则2.5 少用union原因由于union成员公用内存空间,所以容易出错,并且维护困难。使用union通常意味着非面向对象的方法。原 则2.6慎用位操作原因效率低下。可能带来兼容性问题。原 则2.7用enum取
38、代(一组相关的)常量2.7.1例子/为每个成员自动编号enum(BLUE,RED,WHITE);/用一个匿名的枚举把这一组相关常量放在一起enum(EMPTY_ENTRY=-1,NOT_FOUND=-1,MAP_FULL=-2);2.7.2原因易于维护:枚举可以对其成员自动编号,这样增加或减少成员等修改/维护工作就很方便。比#deane或 intconst更安全:因为编译器会检查每个枚举值是否位于取值范围内。在类中使用更方便:如果用常量,要等到在构造函数中初始化后才能使用,(大家可以试一下,这在很多情况下不方便。)而枚举不用。减少匿名命名空间级变量、常量、宏及函数。原 则2.8使用内置bool
39、类型2.8.2说明使用内置bool类型,而不是自己定义或用int代替。2.8.2原因内置bool类型比int更安全强壮。只有true和 false两个值,而不是零和非零。原 则 2.9(尽量)用引用取代指针2.9.1 说明在下述情况下用引用优于指针:被引用的对象永远不可能是空(NULL)o引用某一个对象后决不会再去引用其他对象。(某些Operator 函数的返回值,operator!opemtor+=等。函数的参数传递。但在下述情况下用指针更好:NULL是合法的参数值。其他情况见“原则3.6关于何时用指针传递参数”。2.9.2 原因引用更安全,因为它一定不为空,且定不会再指向其他目标。不需要检
40、查非法值(如 NULL)情况。使用更简洁(“.”比用)。不需要解除引用:与此相反,当所指的内存被释放后,指针应有一个合理的值,般是让它指向NULL。第3章 函 数集中精力写出定义清晰、文档完备、行为良好的函数,会使你的代码易于使用和维护。原 则3.1 一定要做到先定义后使用3.11 说明C+必须这样做(否则编译通不过)。C 程序没有强制要求,但也应该先提供原型,再使用函数。3.12 原因先定义使得编译器能够在编译时就检查和找出错误(而不是等到连接或运行时)。原 则3.2函数原型声明放在一个头文件中原因将同类/相关的(非成员)函数的原型声明集中存放在一个头文件中,有利于引用和修改,因为引用只引用
41、一个熟知的头文件,修改也只在一个地方修改。原 则3.3函数无参数一定要用void标注3.3.1 例子int 6 o oid);/比“intfoo。;”好3.3.2 原因C+和 C 对 function。的解释不同。C+认为是不带参数;C 认为是带任意参数(虽然ANSIC现在凸经废除这一规则,但其他标准和编译器不一定能保证这一点)。显式使用(void)消除了 C+和 C 混编时可能出现的潜在错误。原 则3.4对于内置类型参数应传值(除非函数内部要对其修改)3.4.1 说明内置类型指int、char等(相对于自定义的class、Stuct、unit)。3.4.2 原因传值既安全又简单。内置类型拷贝
42、的代价与传指针或引用相同,因为(绝大多数)内置类型所占内存小于等于指针或引用所占内存。原 则3.5对于非内置类型参数应传递引用(首选)或指针3.5.1 说明非内置类型指的是自定义的类(class)、结构(stuct)和联合(union)。如要防止参数被修改,可用const修饰。引用/指针的选择请参看“原则2.9(尽量)用引用取代指针”。3.5.2 原因不用拷贝:非内置类型的尺寸一般都大于引用和指针类型,尤其还要考虑非内置类型可能包含隐式的数据成员,比如虚函数指针等,所以拷贝的代价高于传递引用和指针。不用构造和析构:如果拷贝对象,还要在传入时调用(拷贝)构造函数,函数退出时还要析构。有利于非内置
43、类型的扩充:对于小对象虽然传值代价也不大,但将来的修改/扩充可能使这 优势丧失,到时再漫山遍野地将函数接口从传值改成传引用/指针就太费劲了。有利于函数支持派生类:若将派生类对象传给以基类为参数的函数(传值方式),就会导致派生类对象被“切割”成基类对象,这样在函数内部实际上用的是一个基类对象的拷贝,而不是最初传入的派生类对象。这多半不是你的本意。这种错误非常难查,因为它是编译器(隐式/自动)做的;编译器也不会报警,因为这是合法的。如果传入的是引用或指针,则不会有对象“切割”现象。函数内部可以将传入的对象视作基类对象使用 因为任何派生类对象都可以作为基类对象。如果传入的对象有虚函数,则恰当的实现版
44、本还会被正确调用,而不局限于传入的参数类型(基类)。原 则3.6关于何时用指针传递参数3.6.1 条件若函数内部须将自己的参数以指针形式传给其他的函数:因为至少你不能确定那些(需要指针的)函数一定不需要传NULL。你若在外面强行用引用,它们就再也无法获得NULL(作为参数值了)。若参数是被new出来的,且将要在函数内被释放;如果用引用,则会出现这样的语句“delete&reference,看起来有点怪(但不是绝对不可接受)。3.6.2 原因总的来说,引用比指针好,但指针也不是一无是处。取舍的一个关关键是:NUIL是否是一个合法的取值。原 则3.7避免使用参或不确定的函数3.7.1 例子MySt
45、ring_Thi(Hello,World);/*虽然MyString_T提供将其对象转换成字符串的成员函*数 operator char const*(),printf()还是打印不出*你想耍的字符串Hello,W orld,因为printf()带的*是可变长的参数,编译器不会将h i隐式转成字符串。*这句话最终会把hi所占内存的内容打出来*/printf(%sn,hi);/*这样就行,因为这句话实际上是调用两次操作符函数*每个函数都只带一个类型己知的参数,此时编译器会调*用 MyString_T的隐式转换函数,将其转成字符串类型,*然后再传给操作符 函数*/couthiend 1;3.7.2
46、 原因参数不确定的函数有隐患:因为参数不确定,编译器就不能检查参数的个数和类型,这会带来很多问题(比如上例中不能做隐式转换等)。C+有很好的解决办法:用重载和链式函数。比 如 printfO可以变成一系列只带一个参数的函数 比 如 operator()函数,每个函数都只接受一种printf()支持的类型,这些函数互为重载(函数名相同)。其结果是,任意 个 pmtf()调用都可以转化为几个重载函数的连续调用。可以看出,转化后更安全,因为每个重载函数的参数个数和类型都固定。同时也更灵活,因为增减个类型只需增减个重载函数,而不像printf()那样需要改函数实现和接口(接口上要相应增减类型指示符,如
47、s、d 等)。这种用法的缺点是效率稍差原 则3.8若不得不使用参数不确定的函数,用提供的方法3.8.1 说明一定要明确这是万不得已的办法(参见“原则3.7避免使用参数不确定的函数”)。3.8.2 例子#include(stdarg.h)void someFunction(char const*pFormat,va_list varArgs);void varFunction(char const*pFormat,.)(va_list varArgs;va_start(varArgs,pFormat);someFunction(pFormat,varArgs);va_end(varArgs);v
48、oid someFunction(char const*pFormat,va_list varArgs)(/first argument determined to be a char*char*pName=va_arg(varArgs,char*);)3.8.3 原因中定义的宏提供了一种安全有效的访问参数链的方法。原 则3.9避免函数的参数过多3.9.1 原因使用麻烦。不易理解和维护。通常表明是个不好的设计。3.9.2 定量分析的参考个函数的参数应该限制在5 个以内。原 则3.1 0尽量保持函数只有唯一出口3.10.1原因单一 H 1 口易于维护:修改代码容易,不易因为忘记修改某处出口而产生
49、问题。易于跟踪调试:可设单一端点跟踪函数(出口)。3.10.2 缺点可读性稍差(比如当有许多嵌套条件语句时)。3.10.3 结论根据情况自己权衡,但尽最大可能保持个出口。原 则3.1 1显示定义返回类型3.11.1 例子/隐含的返回类型是整型,不好Password_T:length(void);/显式声明,好int Password_T:length(void);3.11.2 原因直观,所以容易阅读。提醒自己和阅读者注意正确的返回类型:有可能阅读者没有仔细想,误以为没有返回值,结果造成理解上的偏差。避免让人怀疑是忘了写还是真想用缺省方式。原 则3.12(非void)任何情况都要有返回值3.12
50、.1 说明任何非void函数在任何情况下都要返回某个值。3.12.2 例子int valueGet(int const*pValue)(if(pValue!=NULL)(return*p Value;)/else时会返回什么3.12.3 原因这是一个程序错误,实际上返回值还是有,只是成为随机值。有的编译器会报错。原 则3.1 3若函数返回状态,尝试用枚举作类型原因返回枚举类型可以使编译器对返回值做合法性检查(看看是不是枚举的合法成员)。原 则3.14返回指针类型的函数应该用NULL表示失败3.14.1 原因NULL是唯一合理的表示错误的指针返回值。同时也防止调用者用未初始化或己失效的指针进行随