Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1442015
  • 博文数量: 463
  • 博客积分: 10540
  • 博客等级: 上将
  • 技术积分: 5450
  • 用 户 组: 普通用户
  • 注册时间: 2006-11-12 08:30
文章分类

全部博文(463)

文章存档

2014年(2)

2012年(14)

2011年(42)

2010年(18)

2009年(78)

2008年(35)

2007年(182)

2006年(92)

我的朋友

分类: C/C++

2009-10-13 11:14:08

5.变量(11条规则)

【规则5-1-1】局部变量在引用之前要进行出初始化或要有明确的值。

【规则5-1-2】如果指针变量知道被初始化为什么地址,则初始化为该地址,否则初始化为NULL

【规则5-1-3】所有的外部变量声明前都应加上extern关键字。

【规则5-1-4】尽量不要使用一个bit位控制程序流程或标识特定状态。最好使用多位或枚举类型标识状态。

【规则5-1-5】如果定义数组时全部初始化,则不用给出数组长度。

例如:

int array[]{1,2,3,4,5}

在需要使用数组长度时,用sizeof(array)/sizeof(array[0])计算得出。

【规则5-1-6】不同文件的全局变量没有固定的初始化顺序,注意使用#include包括的文件都算作同一文件。

【规则5-1-7】尽量避免强制类型转换;如果不得不使用,则尽量使用显式方式。

【规则5-1-8】不要强制指针指向尺寸不同的目标。

例如:

int Functionconst char* pChar {

       int *pInt=(const int*)pChar;/*危险操作*/

       return(*pInt);

}

【规则5-1-9】尽量少使用无符号类型,其在混合表达式中可能隐式转换造成错误。

【规则5-1-10】尽量少的使用浮点类型,因为浮点数据标识不精确,而且运算速度慢。

【规则5-1-11】尽量少用union类型,因为成员共用内存空间,处理不当容易出错。

6.表达式和基本语句(17条规则+3条建议)

6.1 运算符的优先级(1条规则)

【规则6-1-1】避免使用默认的优先级。如果代码行中的运算符比较多,为了防止产生歧义并提高可读性,应当用括号明确表达式的计算顺序。

例如:

value = (high << 8) | low

    if ((a | b) && (a & c) )

6.2 复合表达式(4条规则)

a=b=c=0这样的表达式称为复合表达式。允许复合表达式存在的理由是:

1)书写简洁;

2)可以提高编译效率;

3)但要防止滥用复合表达式。

【规则6-2-1】不要编写太复杂的复合表达式。

例如:i=a>= b && c复合表达式过于复杂

【规则6-2-2】不要使用多用途的复合表达式。

例如:d=(a=b+c)+r ; //该表达式既求a 值又求d 值。

应该拆分为两个独立的语句:

a=b+c;

d=a+r;

【规则6-2-3】不要把程序中的复合表达式与真正的数学表达式混淆。

例如: if( a是数学表达式而不是复合表达式并不表示 if( (a而是成了令人费解的 if( (a

【规则6-2-4“++”“—”,当对语句中的变量使用递增或递减运算符时,该变量不应在语句中出现一次以上,因为求值的顺序取决于编译器。编写代码时不要对顺序作假设,也不要编写在某一机器上能够如期运行但没有明确定义行为的代码。

例如:

int i = 0, a[5];

a = i++;   // a[0]赋值还是给a[1]赋值?

6.3 if 语句布尔表达式(7条规则)

if 语句是C 语言中最简单、最常用的语句,然而很多程序员用隐含错误的方式写if 语句。

【规则6-3-1】如果布尔表达式比较长且与一个常值进行比较时,一般把常值放在前面更清楚。

例如:

  if( 0!=(a+b)%6 ) {

    }

【规则6-3-2】有多个if语句嵌套时,要层层对齐,每一层的真假分支使用括号括起来并对齐。

例如:

if( x>y ) {  //左括号不另起一行

}

Else {  //左括号不另起一行

if ( y>z) {

}

Else {

}

}

【规则6-3-3】布尔表达式中有多个逻辑判断条件时,只要其中一个条件不满足则该表达式的值就是;注意在else分支中,不能假定某个逻辑表达式为而进行处理,导致错误。

例如:

int a=10;

int b=0;

int c=0;

if 0!==a && 0!=b && 0!=c {

}

Else {

x=y/c;

}

【规则6-3-4】布尔表达式中有多个逻辑判断条件时,只要其中一个条件满足则该表达式的值就是;注意不能假定某个逻辑表达式为而进行处理,导致错误。

例如:

int a=10;

int b=0;

int c=0;

if( 0!==a || 0!=b || 0!=c {

x=y/c;

}

【规则6-3-5】不可将布尔变量直接与TRUEFALSE 或者10 进行比较。

根据布尔类型的语义,零值为(记为FALSE),任何非零值都是(记为TRUE)。TRUE 的值究竟是什么并没有统一的标准。例如Visual C++ TRUE 定义为1,而Visual Basic 则将TRUE 定义为-1

假设布尔变量名字为flag,它与零值比较的标准if 语句如下:

if( flag )   /* 表示flag 为真*/

if( !flag ) /* 表示flag 为假*/

其它的用法都属于不良风格,例如:

if( flag == TRUE )

if( flag == 1 )

if( flag == FALSE )

if( flag == 0 )

【规则6-3-6】不可将浮点变量用“==”=”与其它变量或数字比较。

千万要留意,无论是float 还是double 类型的变量,都有精度限制。所以一定要避免将浮点变量用“==”=”与数字比较,应该设法转化成“>=”“<=”的形式。假设浮点变量为x,应当将

if( x == 0.0 )   // 隐含错误的比较

转化为

if( (x>=-EPSINON ) && ( x<=EPSINON) )

其中EPSINON 是允许的误差(即精度)。

【规则6-3-7】应当将指针变量用“==”=”NULL 比较。

指针变量的零值是(记为NULL)。尽管NULL 的值与0 相同,但是两者意义不同。假设指针变量的名字为p,它与零值比较的标准if 语句如下:

if( p== NULL )   // p NULL 显式比较,强调p 是指针变量

if( p!= NULL )

不要写成

if( p==0 )   // 容易让人误解p 是整型变量

if( p!=0 )

或者

if( p )     // 容易让人误解p 是布尔变量

if( !p )

6.4 循环语句(1条规则+3条建议)

C语言的循环语句中,for 语句的使用频率最高,while 语句其次,do 语句很少用。本节重点论述循环体的效率。提高循环体效率的基本办法是降低循环体的复杂性。

【规则6-4-1】不可在循环体内修改循环变量,以防止循环失去控制。

【建议6-4-1】在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU 跨切循环层的次数。如下表的对比:

低效率:长循环在最外层

高效率:长循环在最内层

for ( row=0; row<100; row++ ) {

for ( col=0; col<5; col++ ) {

sum = sum + a[row][col];

}

}

for ( col=0; col<5; col++ ) {

for ( row=0; row<100; row++ ) {

sum = sum + a[row][col];

}

}

【建议6-4-2】如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。下面示例a的程序比示例b程序多执行了N-1 次逻辑判断。并且由于前者总要进行逻辑判断,打断了循环流水线作业,使得编译器不能对循环进行优化处理,降低了效率。如果N非常大,最好采用示例b的写法,可以提高效率。如果N非常小,两者效率差别并不明显,采用示例a的写法比较好,因为程序更加简洁。如下表的对比:

a、效率低但程序简洁

b、效率高但程序不简洁

for( i=0; i

if ( condition )

DoSomething();

else

DoOtherthing();

}

if( condition ) {

for( i=0; i

         DoSomething();

}

Else {

    for(i=0; i

         DoOtherthing();

}

【建议6-4-3】建议for 语句的循环控制变量的取值采用半开半闭区间写法。

示例a中的x 值属于半开半闭区间“0 =< x < N”,起点到终点的间隔为N,循环次数为N

示例b中的x 值属于闭区间“0 =< x <= N-1”,起点到终点的间隔为N-1,循环次数为N

相比之下,示例a的写法更加直观,尽管两者的功能是相同的。

a、循环变量属于半开半闭区间

b、循环变量属于闭区间

for( x=0; x

...

}

for( x=0; x<=N-1; x++ ) {

...

}

6.5 switch 语句(2条规则)

【规则6-6-1】每个case 语句的结尾不要忘了加break语句,否则将导致多个分支重叠(除非有意使多个分支重叠)。

【规则6-6-2】不要忘记最后的default 分支。即使程序真的不需要default 处理,也应该保留语句default : break;

6.6 goto 语句(1条规则)

【规则6-7-1】慎用goto 语句。虽然在某些情况使用方便,但是会造成程序可读性下降。

7.函数设计(17条规则+10条建议)

7.1注释规则(1条规则)

【规则7-1-1】一般在函数头添加函数功能、参数说明、返回值等注释信息。可以选择在函数末尾添加该函数在单元测试中曾经出现的问题。

一个函数的注释信息如下例:

/**********************************************************************************

*Function(功能)calculate The area of rectangle *

*parameter(参数)the Length and Width of rectangle *

*outout(返回值):the area of rectangle *

********************************************************** ************************/

int GetValue(int iLength, int iWidth) {

return iArea;

}

/*

Error:

    1描述在单元测试中出现的错误

    2…

*/

 

7.2 函数的使用(1条规则)

【规则7-2-1】函数的扇入数目尽量多,扇出数目不宜太多,一般不超过10,以保证程序的高内聚、低藕和。

7.3 参数的规则(4条规则+2条建议)

【规则7-3-1】参数的书写要完整,不要贪图省事只写参数的类型而省略参数的名字。如果函数没有参数,则用void 填充。(使用函数原型)。

例如:

void SetValue(int width, int height); // 良好的风格

void SetValue(int, int);           // 不良的风格

float GetValue(void);             // 良好的风格

float GetValue();               // 不良的风格

【规则7-3-2】参数命名要恰当,顺序要符合习惯用法。

例如:

编写字符串拷贝函数StringCopy,它有两个参数。如果把参数名字起为str1str2

例如:

void StringCopy(char *str1, char *str2);

很难搞清楚究竟是把str1 拷贝到str2 中,还是相反。可以把参数名字起得更有意义,例如strSource strDestination。这样从名字上就可以看出应该把strSource 拷贝到strDestination中。另外,参数的顺序要遵循库函数的风格。一般地,应将目的参数放在前面,源参数放在后面。

【规则7-3-3】如果参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改。

例如:

void StringCopy(char *strDestinationconst char *strSource);

【规则7-3-4】对于基本数据类型参数应该传递值(除非函数要修改传入的参数),这样安全、简单。对于复合数据类型应传递指针(地址),以提高效率。

【建议7-3-1】避免函数有太多的参数,参数个数尽量控制在5个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。

【建议7-3-2】尽量不要使用类型和数目不确定的参数。

7.4 返回值的规则(6条规则)

【规则7-4-1】不要省略返回值的类型。如果函数没有返回值,那么应声明为void 类型。

【规则7-4-2】函数名字与返回值类型在语义上不可冲突。

【规则7-4-3】函数返回正常值和错误标志要有明确的说明。

【规则7-4-4】给以指针传递方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。

函数返回值采用值传递方式,由于函数会把返回值复制到外部临时的存储单元中,加const修饰没有任何价值。

【规则7-4-5】返回指针类型的函数应该使用NULL表示失败。

【规则7-4-6】当函数返回指针时,应用注释描述指针的有效期。

7.5 函数内部实现的规则(2条规则)

不同功能的函数其内部实现各不相同,看起来似乎无法就内部实现达成一致的观点。但根据经验,我们可以在函数体的入口处出口处从严把关,从而提高函数的质量。

【规则7-5-1】在函数体的入口处,对参数的有效性进行检查。

【规则7-5-2】尽量保持函数只有一个出口,在函数体的出口处,对return 语句的正确性和效率进       行检查。

注意事项如下:

1return 语句不可返回指向栈内存指针,因为该内存在函数体结束时被自动销毁。

例如:  

  char * Func(void) {

  char str[] = “hello student”; // str 的内存位于栈上

  …

  return str;             // 将导致错误    

}

2)要搞清楚返回的究竟是还是指针

7.6 其它建议(6条建议)

【建议7-6-1】函数的功能要单一,不要设计功能过于复杂的函数。

【建议7-6-2】经常使用的重复代码用函数来代替。

【建议7-6-3】函数体的规模要小,尽量控制在50 行代码之内。

【建议7-6-4】尽量避免函数带有记忆功能。相同的输入应当产生相同的输出。函数的static 局部变量是函数的记忆存储器。建议尽量少用static 局部变量,除非必需。

【建议7-6-5】不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内的变量的有效性。         例如全局变量、文件句柄等。

【建议7-6-6】用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况。

7.7 使用断言(2条规则+2条建议)

程序一般分为Debug 版本和Release 版本,Debug 版本用于内部调试,Release 版本发行给用户使用。

断言assert 是仅在Debug 中版本起作用的宏,它用于检查不应该发生的情况。在程序运行过程中,如果assert 的参数为假,那么程序就会中止(一般还会出现提示对话,说明在什么地方引发了assert)。

assert 不是一个仓促拼凑起来的宏。为了不在程序的Debug 版本和Release 版本引起差别,assert 不应该产生任何副作用。所以assert 不是函数,而是宏。程序员可以把assert 看成一个在任何系统状态下都可以安全使用的无害测试手段。如果程序在assert处终止了,并不是说明含有该assert 的函数有错误,而是调用者出了差错,assert 可以帮助我们找到发生错误的原因。

【规则7-7-1】使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。

【规则7-7-2】在函数的入口处,使用断言检查参数的有效性(合法性)。

【建议7-7-1】在编写函数时,要进行反复的考查,并且自问:我打算做哪些假定?一旦确定了的假定,就要使用断言对假定进行检查。

【建议7-7-2】一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风格可能会隐瞒错误。当进行防错设计时,如果不可能发生的事情的确发生了,则要使用断言进行报警。

8.内存管理(5条规则)

8.1 内存使用注意的问题(5条规则)

内存分配方式有三种:

1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。

2)在栈上分配。在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

3)从堆上分配,亦称动态内存分配。程序在运行时用malloccalloc申请任意大小的内存,程序员自己负责在何时用free释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

【规则8-1-1】用malloccalloc 申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL 的内存。

【规则8-1-2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

【规则8-1-3】避免数组或指针的下标越界,特别要当心发生1”或者1”操作。

【规则8-1-4】动态内存的申请与释放必须配对,防止内存泄漏。

【规则8-1-5】用free释放内存之后,应立即将指针设置为NULL,防止产生野指针

9.其他规范及建议(27条建议)

9.1 提高程序的效率(6条建议)

程序的时间效率是指运行速度,空间效率是指程序占用内存或者外存的状况。全局效率是指站在整个系统的角度上考虑的效率,局部效率是指站在模块或函数角度上考虑的效率。

【建议9-1-1】不要一味地追求程序的效率,应当在满足正确性、可靠性、健壮性、可读性等质量因素的前提下,设法提高程序的效率。

【建议9-1-2】以提高程序的全局效率为主,提高局部效率为辅。

【建议9-1-3】在优化程序的效率时,应当先找出限制效率的瓶颈,不要在无关紧要之处优化。

【建议9-1-4】先优化数据结构和算法,再优化执行代码。

【建议9-1-5】有时候时间效率和空间效率可能对立,此时应当分析哪个更重要,做出适当的折衷。例如多花费一些内存来提高性能等。

【建议9-1-6】不要追求紧凑的代码,因为紧凑的代码并不能产生高效的机器码。

9.2 编译问题(2条建议)

【建议9-2-1】把编译器的选择项设置为最严格状态。在调试代码时应该将编译器的所有警告信息都打开,并且编译不包括任何警告信息。把问题尽量暴露在编译时而不是运行时,尽量减少解决的代价。

【建议9-2-2】对于大型软件尽量减少编译时间。例如调试时关闭优化选项,尽量减少文件之间的依赖程度。

9.3 兼容性问题(8条建议)

【建议9-3-1】尽量不要使用与具体硬件或软件环境关系密切的变量。

【建议9-3-2】遵守ANSI国际标准,将不符合标准的代码与其它代码分开并进行标记,将依赖特定硬件或操作系统的代码分开。

【建议9-3-3】不要假设各数据类型的尺寸。

【建议9-3-4】注意不能设定数据溢出处理方式。

【建议9-3-5】不能假设表达式的运算顺序。

【建议9-3-6】不要假设参数的计算顺序。

【建议9-3-7】注意数据文件的兼容性,例如:字节排列顺序,数据对齐方式,行尾标志等。

【建议9-3-8】尽量使用标准库函数, 不要发明已经存在的库函数。

9.4性能问题(4条建议)

【建议9-4-1】不要使用移位代替乘除操作,一般编译器会作优化。

【建议9-4-2】不要使用关键字register,一般编译器会作优化。

【建议9-4-3】避免在循环体内定义变量。

【建议9-4-4】前缀++--的效率高于后缀的++--

9.5 其他一些有益的建议(7条建议)

【建议9-5-1】当心那些视觉上不易分辨的操作符发生书写错误。例如经常会把==误写成,像“||”“&&”“<=”“>=”这类符号也很容易发生丢失错误。然而编译器却不一定能自动指出这类错误。

【建议9-5-2】当心变量的初值、缺省值错误,或者精度不够。

【建议9-5-3】当心数据类型转换发生错误。尽量使用显式的数据类型转换,避免让编译器轻悄悄地进行隐式的数据类型转换。

【建议9-5-4】当心变量发生上溢或下溢,以及数组的下标越界。

【建议9-5-5】当心忘记编写错误处理程序,当心错误处理程序本身有误。

【建议9-5-6】在工程中编写程序时,避免编写技巧性很高的代码。

【建议9-5-7】如果可能的话,使用代码规则检查工具进行代码检查。

阅读(795) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~