资源描述
C编码规范
109条规则+49条建议
C语言编码规范
目 录
1.文件结构(4条规则+6条建议) 1
1.1 版权和版本的声明 1
1.2 头文件的结构 2
1.3 定义文件的结构 3
1.4头文件和定义文件使用(4条规则+6条建议) 3
2.程序版式(34条规则+2条建议) 4
2.1 空行(4条规则) 4
2.2 代码行(5条规则+1条建议) 4
2.3 代码行内的空格(6条规则+1条建议) 5
2.4 对齐(4条规则) 6
2.5 长行拆分(2条规则) 7
2.6 修饰符的位置(1条规则) 8
2.7 注释(12条规则) 8
3.标识符命名(15条规则+1条建议) 8
4.常量(7条规则) 10
4.1 const 与#define 的比较(2条规则) 10
4.2 常量定义(5条规则) 10
5.变量(11条规则) 11
6.表达式和基本语句(17条规则+3条建议) 11
6.1 运算符的优先级(1条规则) 11
6.2 复合表达式(4条规则) 12
6.3 if 语句布尔表达式(7条规则) 12
6.4 循环语句(1条规则+3条建议) 15
6.5 switch 语句(2条规则) 16
6.6 goto 语句(1条规则) 16
7.函数设计(16条规则+10条建议) 16
7.1注释规则(1条规则) 16
7.2 函数的使用(1条规则) 16
7.3 参数的规则(4条规则+2条建议) 17
7.4 返回值的规则(6条规则) 17
7.5 函数内部实现的规则(2条规则) 17
7.6 其它建议(6条建议) 18
7.7 使用断言(2条规则+2条建议) 18
8.内存管理(5条规则) 18
8.1 内存使用注意的问题(5条规则) 18
9.其他规范及建议(27条建议) 19
9.1 提高程序的效率(6条建议) 19
9.2 编译问题(2条建议) 19
9.3 兼容性问题(8条建议) 19
9.4性能问题(4条建议) 19
9.5 其他一些有益的建议(7条建议) 20
C语言编码规范
1.文件结构(4条规则+6条建议)
C程序文件通常分为两类文件:
一类文件用于保存程序的声明(declaration),称为头文件。头文件以“.h”为后缀。
另一类文件用于保存程序的实现(implementation),称为定义(definition)文件。定义文件以“.c”为后缀。对于简单的C语言程序,一般在把头文件和程序定义文件放在一起,只有一个.c定义文件即可。而对于复杂的程序,则多采用头文件包含的形式并通过多个定义文件实现。
1.1 版权和版本的声明
版权和版本的声明一般应该位于头文件和定义文件的开头(参见示例1-1),主要内容包括:
(1) 版权信息;
(2) 文件名称、文件标识、摘要;
(3) 当前版本号、作者/修改者、修改日期、修改描述等;
(4) 版本历史信息、原作者、完成日期等。
/*
* Copyright (c) 2004,北大青鸟培训中心
* All rights reserved.
*
* 文件名称:filename.h
* 文件标识:根据软件工程设置
* 摘要:简要描述本文件的作用和内容等
*/
//下面其它的声明代码
…
//下面是原作者、版本、完成、日期和当前版本的信息
/* 当前版本:1.0.1
* 作者:修改者名字,修改日期:2005年4月5日
* 修改的地方描述:
*/
/*
* 取代版本:1.0.0
* 原作者:原作者名字,完成日期:2004年12月31日
*/
示例1-1 版权和版本的声明
版本标识:采用<主版本号>.<次版本号>.<修订号> 来命名自己产品的编号。一般这样约定,如果次版本号是偶数(如0、2、4等),代表正式版本,如果次版本号是奇数(如1、3、5等),代表开发过程中的测试版本。修订号则相当于Build号,用来标识一些小的改动。
1.2 头文件的结构
头文件由三部分内容组成:
(1) 文件开头处的版权和版本声明(参见示例1-1);
(2) 预处理块;
(3) 声明函数原型和声明数据结构或变量等。
假设头文件名称为filename.h,头文件的结构参见示例1-2。
/*
* Copyright (c) 2004,北大青鸟培训中心
* All rights reserved.
*
* 文件名称:filename.h
* 文件标识:根据软件工程设置
* 摘要:简要描述本文件的作用和内容等
*/
#include <stdio.h>
#include <stdlib.h>
#include <graphics.h> // 引用头文件
struct studentstruct
{
int no;
char name[20];
char sex;
float score;
};
void GetValue()
{
}
void SetValue(int no)
{
}
//后面同示例 1-1。
示例1-2 C头文件结构
1.3 定义文件的结构
定义文件有三部分内容:
(1) 定义文件开头处的版权和版本声明(参见示例1-1);
(2) 对一些头文件的引用;
(3) 程序的实现体(包括数据和代码)。
假设定义文件的名称为filename.c,定义文件的结构参见示例1-3
/* 版权和版本声明见示例1-1,此处省略。*/
#inlcude “filename.h”
#ifndef GRAPHICS_H // 防止graphics.h 被重复引用
#define GRAPHICS_H
/* 函数的实现体*/
void GetValue()
{
…
}
/* 函数的实现体*/
void SetValue(int no)
{
…
}
void main()
{
…
}
示例1-3 C定义文件的结构
1.4头文件和定义文件使用(4条规则+6条建议)
【规则1-2-1】在复杂的工程文件中,为了防止头文件被重复引用,应使用ifndef/define/endif 结构产生预处理块。
【规则1-2-2】用#include <filename.h> 格式来引用标准库的头文件(编译器将从标准库目录开始搜索)。
【规则1-2-3】用#include “filename.h” 格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)。
【规则1-2-4】只引用必需的头文件,不要为了防止忘记包含头文件而在每个文件开始添加很多的头文件。
【建议1-2-1】不要在头文件中定义常量或变量,注意头文件只是用来声明。
【建议1-2-2】不提倡使用全局变量,尽量不要在头文件中出现像“extern int width;”这类声明。
【建议1-2-3】将非系统的函数库放在一个单独的目录下引用。
【建议1-2-4】头文件应按功能组织在一起,即对单独子系统的声明应放在单独的头文件中。此外,当代码从一个平台移植到另一个平台时有可能发生更改的声明应位于单独的头文件中,并进行相应的注释。
【建议1-2-5】避免使用与函数库中专用头文件名相同的头文件名。语句 #include "math.h" 如果在当前目录中找不到所期望文件的话,会包括标准库 math 头文件。
【建议1-2-6】包含头文件时一般不使用绝对路径名。
2.程序版式(35条规则+2条建议)
2.1 空行(4条规则)
空行起着分隔程序段落的作用,空行得体将使程序的布局更加清晰。空行不会浪费内存,所以不要舍不得用空行。
【规则2-1-1】在函数内部局部变量定义结束之后处理语句之前要加空行。
【规则2-1-2】在每个函数定义结束之后都要加空行。参见示例2-1(a)。
【规则2-1-3】函数返回语句和其他语句之间使用空行分开。
【规则2-1-4】在一个函数体内,逻辑上密切相关的语句之间不加空行,其它地方应加空行分隔。参见示例2-1(b)。
// 空行
void Function1( )
{
…
}
// 空行
void Function2( )
{
…
}
// 空行
void Function3( )
{
…
}
// 空行
while( condition )
{
statement1;
// 空行
if ( condition )
{
statement2;
}
else
{
statement3;
}
// 空行
statement4;
}
示例2-1(a) 函数之间的空行 示例2-1(b) 函数内部的空行
2.2 代码行(5条规则+1条建议)
【规则2-2-1】一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便写注释。
【规则2-2-2】if、for、while、do 等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}表明是一个语句块。
【规则2-2-3】一对花括号要单独各占一行。但是在do-while、struct和union及其后有‘;’的除外,要同在一行。
例如:
do
{
…
}while(i>0);
【规则2-2-4】switch语句中的每个case语句各占一行,当某个case语句不需要break语句最好加注释声明。
【规则2-2-5】并列的语句行应该按照字母顺序排序,如变量定义和switch中的case语句等。
【建议2-2-1】尽可能在定义变量的同时初始化该变量(就近原则),如果变量的引用处和其定义处相隔较远,变量的初始化很容易被忘记。如果引用了未被初始化的变量,可能会导致程序错误。本建议可以减少隐患。
例如:
int width=20; /* 定义并初绐化width*/
int height=20; /* 定义并初绐化height*/
int depth=20; /* 定义并初绐化depth*/
风格良好的代码行
风格不良的代码行
int width; /* 宽度*/
int height; /* 高度*/
int depth; /* 深度*/
int width, height, depth; /* 宽度高度深度*/
x=a+b;
y=c+d;
z=e+f;
x=a+b;y=c+d;z=e+f;
if( width < height )
{
dosomething();
}
if( width < height ) dosomething();
for( initialization; condition; update)
{
dosomething();
}
// 空行
other();
for( initialization; condition; update )
dosomething();
other();
2.3 代码行内的空格(6条规则+1条建议)
【规则2-3-1】关键字之后要留空格。象const、case 等关键字之后至少要留一个空格,否则无法辨析关键字。象if、for、while 等关键字和紧跟的左括号‘(’之后应留一个空格,右括号前也对应要留一个空格,以突出关键字。例如:if( a==b )
【规则2-3-2】函数名之后不要留空格,紧跟左括号‘(’,以与关键字区别。例如:void calc(void);
【规则2-3-3】“,”之后要留空格,如Function(x, y, z)。如果‘;’不是一行的结束符号,其后要留空格,如for( initialization; condition; update )。
【规则2-3-4】不要在单目运算符(如“!”、“~”、“++”、“--”、“&”)和其操作对象间加空格。
例如:!foo,++i,(long)getValue
【规则2-3-5】赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+=”、“>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。
【规则2-3-6】象“[]”、“.”、“->”这类操作符前后不加空格。
例如:big.bar,pFile->bar,big[bar]
【建议2-3-1】对于表达式较长的for语句和if语句,为了紧凑起见可以适当地去掉一些空格.
例如:for( i=0; i<10; i++ )和if( (a<=b) && (c<=d) )
风格良好的空格
风格不良空格
void Func1(int x, int y, int z);
void Func1 (int x,int y,int z);
if( year >= 2000 )
if( (a>=b) && (c<=d) )
if(year>=2000)
if(a>=b&&c<=d)
for( i=0; i<10; i++ )
for(i=0;i<10;i++)
for (i = 0; i < 10; i ++)
x = a < b ? a : b;
x=a<b?a:b;
int *x = &y;
int * x = & y;
array[5] = 0;
a.Function();
b->Function();
array [ 5 ] = 0;
a.Function();
b -> Function();
2.4 对齐(4条规则)
【规则2-4-1】程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。【规则2-4-2】水平缩进每次使用四个空格即可(定义一个tab键为四个空格。有的要求缩进两个空格)。
【规则2-4-3】同属于一个语句块的代码对齐。
【规则2-4-4】{ }之内的代码块在‘{’右边一个tab键处左对齐。
风格良好的对齐
风格不良的对齐
void Function(int x)
{
program code
}
void Function(int x){
program code
}
if( condition )
{
program code
}
else
{
program code
}
if(condition){
program code
}
else {
program code
}
for( initialization; condition; update )
{
program code
}
for(initialization; condition; update){
…program code
}
while( condition )
{
program code
}
while(condition){
…program code
}
如果出现嵌套的{},则使用缩进对齐,如:
{
{
}
}
2.5 长行拆分(2条规则)
【规则2-5-1】代码行最大长度宜控制在70至80个字符以内。代码行不宜过长,否则不便于阅读,也不便于打印。
【规则2-5-2】长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。
if( (very_longer_variable1 >= very_longer_variable12 )
&& (very_longer_variable3 <= very_longer_variable14)
&& (very_longer_variable5 <= very_longer_variable16))
{
dosomething();
}
virtual CMatrix CMultiplyMatrix (CMatrix leftMatrix,
CMatrix rightMatrix);
for( very_long_initialization;
very_long_condition;
very_long_update)
{
dosomething();
}
示例2-5 长行的拆分
2.6 修饰符的位置(1条规则)
【规则2-6-1】将修饰符 * 和 &紧靠变量名,以免引起误解。
例如:char *name;
int *x, y; /* 此处y 不会被误解为指针*/
2.7 注释(12条规则)
C 语言的注释符为“/*… */”和“//”。注释通常用于:
(1)版本、版权声明;
(2)函数接口说明,包括参数类型和意义、函数类型和意义等;
(3)重要的数据类型声明、变量、算法、处理、段落等提示。
“//”为行注释。
【规则2-7-1】注释是对代码作用的“提示”,而不是文档。注释的频度要合适,一般要求占程序总行数的1/5~1/4。
【规则2-7-2】边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
【规则2-7-3】注释应准确、易懂,防止注释有二义性。错误的注释不但无益反而有害。
【规则2-7-4】尽量避免在注释中使用缩写,特别是不常用的缩写。根据维护程序的对象确定使用中文还是使用英文。
【规则2-7-5】注释的位置应与被描述代码相邻,可以放在代码的上方或右方,一般不宜放在下方。
【规则2-7-6】当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。
【规则2-7-7】尽量不要在语句指令中添加注释。
【规则2-7-8】注释不具备约束使用者行为的能力。
【规则2-7-9】给一行代码添加注释最好使用“//”,比较清楚。
【规则2-7-10】不要使用/**/注释掉大量代码,而要使用#if0条件编译语句
例如:
#if 0
if( debugLevel>1 )
{
...
}
#endif
【规则2-7-11】行末注释最好对齐。
【规则2-7-12】应对包含的头文件进行行末注释。
3.标识符命名(15条规则+1条建议)
共性规则是被大多数程序员采纳的,我们应当在遵循这些共性规则。
命名两个基本原则:
1.含义清晰,不易混淆;
2.不与其它模块、函数的命名空间相冲突。
【规则3-1-1】标识符要清楚、准确、简单而且尽量可发音的英文名字。
例如:int returnStatus;
不要把currentValue 写成nowValue 。
【规则3-1-2】标识符的长度应当符合“min-length && max-information”(最短并包含信息最多)原则。单字符的名字也是有用的,常见的如i、j、k、m、n、x、y、z 等,它们通常可用作函数内的局部变量。
【规则3-1-3】命名规则尽量与所采用的操作系统或开发工具的风格保持一致。
例如Windows应用程序的标识符通常采用“大小写”混排的方式,如printStudent;而Unix 应用程序的标识符通常采用“小写加下划线”的方式,如print_student。别把这两类风 格混在一起用。
【规则3-1-4】尽量选择通用词汇并保持整个软件风格一致。
例如:使用get、read、fetch 、retrieve都能表达“取出”的意思,一旦软件采用哪一 个则应贯穿始终。
【规则3-1-5】程序中不要出现仅靠大小写区分的相似的标识符。
例如:int x, X; /* 变量x 与X 容易混淆*/
void foo(int y); /* 函数foo 与FOO 容易混淆*/
void FOO(float y);
【规则3-1-6】程序中不要出现标识符完全相同的局部变量和全局变量,尽管可能两者的作用域不同而不会发生语法错误,但会使人误解。
【规则3-1-7】变量的名字应当使用“名词”或者“形容词+名词”。
例如: float value;
float newValue;
【规则3-1-8】用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。
例如: int MinValue;
int MaxValue;
int MinValue(void);
int MaxValue(void);
【规则3-1-9】变量和参数首字母小写,其后每个英文单词的第一个字母大写,其它小写。
例如:int recWidth;
【规则3-1-10】标识布尔型的变量或函数名称一般使用is作为前缀。
例如:void isFull();
【规则3-1-11】常量全用大写字母,用下划线分割单词。
const int MAX_LENGTH = 100;
【规则3-1-12】静态变量加前缀s_(表示static)。
static int s_initValue; /* 静态变量*/
【规则3-1-13】如果需要定义全局变量,则变量加前缀g_(表示global)。
例如:int g_howStudent; /* 全局变量*/
【规则3-1-14】函数名用大写字母开头的单词组合而成。由多个单词组成的标识符每个单词首字母大写。其它小写。
例如:InputStudInfo(); //全局函数
【规则3-1-15】一般错误包裹函数名全部大写。
例如:
FILE *pFile=fopen("readme.txt","rw+");
if ( pFile==NUL )
{
//错误处理:打印错误信息等。
abort();
}
可以定义成下面的包裹函数
FILE *( char const* fileName,char const *mode )
{
FILE *pFile=fopen(fileName,mode);
if ( pFile==NUL )
{
//错误处理:打印错误信息等。
abort();
}
return pFile;//正常则返回相应的文件指针
}
以后调用的话,则可以使用下面的简洁方式:
FILE *pFile=FOPEN("readme.txt","rw++");
【建议3-1-1】尽量避免名字中出现数字编号,如value1、value2 等,除非逻辑上的确需要编号。
4.常量(7条规则)
常量是一种标识符,它的值在运行期间恒定不变。C 语言用#define 来定义常量。除了#define之外还可以用const 来定义常量。
4.1 const 与#define 的比较(2条规则)
C 语言可以用const 来定义常量,也可以用#define 来定义常量。但是前者比后者有更多的优点:
(1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换过程中可能会产生意料不到的错误。
(2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。
【规则4-1-1】尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串。
例如: #define MAX 100 // C 语言的宏常量
const float PI = 3.14159; // C 语言的const 常量
【规则4-1-2】尽量使用const定义常量替代宏定义常量。
4.2 常量定义(5条规则)
【规则4-2-1】需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。
【规则4-2-2】如果某一常量与其它常量密切相关,则应在定义中包含这种关系,而不应给出一些孤立的值。
例如: const float RADIUS = 100;
const float DIAMETER = RADIUS * 2;
【规则4-2-3】enum中的枚举常量应以大写字母开头或全部大写。
【规则4-2-4】如果宏值多于一项,一定使用括号。
例如:#define ERROR_DATA_LENGTH 10+1
应该这样定义:
#define ERROR_DATA_LENGTH (10+1)
这样使用malloc(5*ERROR_DATA_LENGTH)时,得到是5*(10+1)=55; 而上面的定义则得到5*10+1=51。
【规则4-2-5】函数宏的每个参数都要括起来。
例如:#define WEEKS_TO_DAYS(w) (w*7)
应该写成:#define WEEKS_TO_DAYS(w) ((w)*7)
这样在翻译totalDays=WEEKS_TO_DAYS(1+2)时,才能够正确地翻译成:(1+2)*7;否则将错误地翻译成1+2*7。
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 Function(const 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<d && c+f<=g+h ; // 复合表达式过于复杂
【规则6-2-2】不要使用多用途的复合表达式。
例如:d=(a=b+c)+r ; //该表达式既求a 值又求d 值。
应该拆分为两个独立的语句:
a=b+c;
d=a+r;
【规则6-2-3】不要把程序中的复合表达式与“真正的数学表达式”混淆。
例如: if( a<b<c ) //a<b<c 是数学表达式而不是复合表达式
并不表示 if( (a<b) && (b<c) )
而是成了令人费解的 if( (a<b)<c )
【规则6-2-4】“++”和“—”,当对语句中的变量使用递增或递减运算符时,该变量不应在语句中出现一次以上,因为求值的顺序取决于编译器。编写代码时不要对顺序作假设,也不要编写在某一机器上能够如期运行但没有明确定义行为的代码。
例如:int i = 0, a[5];
a[i] = 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】不可将布尔变量直接与TRUE、FALSE 或者1、0 进行比较。
根据布尔类型的语义,零值为“假”(记为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<N; i++ )
{
if ( condition )
DoSomething();
else
DoOtherthing();
}
if( condition )
{
for( i=0; i<N; i++ )
DoSomething();
}
else
{
展开阅读全文