1、C/C+ 编码规范( 适用 C51)详解今天人们越来越明白软件设计更多地是一种工程,而不是一种个人艺术。由于大型产品的开发通常由很多的人协同作战,如果不统一编程规范,最终合到一起的程序,其可读性将较差,这不仅给代码的理解带来障碍,增加维护阶段的工作量,同时不规范的代码隐含错误的可能性也比较大。BELL 实验室的研究资料表明,软件错误中 18%左右产生于概要设计阶段, 15%左右产生于详细设计阶段,而编码阶段产生的错误占的比例则接近 50%;分析表明,编码阶段产生的错误当中,语法错误大概占 20%左右,而由于未严格检查软件逻辑导致的错误、函数( 模块)之间接口错误及由于代码可理解度低导致优化维护
2、阶段对代码的错误修改引起的错误则占了一半以上。可见,提高软件质量必须降低编码阶段的错误率。如何有效降低编码阶段的错误呢?BELL 实验室的研究人员制定了详细的软件编程规范,并培训每一位程序员,最终的结果把编码阶段的错误降至 10%左右,同时也降低了程序的测试费用,效果相当显著。本文从代码的可维护性(可读、可理解性、可修改性)、代码逻辑与效率、函数(模块)接口、可测试性四个方面阐述了软件编程规范,规范分成规则和建议两种,其中规则部分为强制执行项目,而建议部分则不作强制,可根据习惯取舍。编码规范1.排版风格程序块采用缩进风格编写,缩进为 4 个空格位。排版不混合使用空格和 TAB键。在两个以上的关
3、键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如-) ,后不应加空格。采用这种松散方式编写代码的目的是使代码更加清晰。例如:(1) 逗号、分号只在后面加空格printf(“%d %d %d“ , a, b, c);(2)比较操作符 , 赋值操作符“=“ 、 “+=“,算术操作符“+“、“%“ ,逻辑操作符“a *= 2;a = b 2;(3)“!“、“、“+“、“-“、“ / 内容操作“*“与内容之间flag = !bIsEmpty; / 非操作“!“与内容之间p = / 地址操作 “ / “+“,“-“与内容之间(4
4、)“-“ 、“.“前后不加空格p-id = pId; / “-“指针前后不加空格由于留空格所产生的清晰性是相对的,所以,在已经非常清晰的语句中没有必要再留空格,如最内层的括号内侧(即左括号后面和右括号前面)不要加空格,因为在 C/C+语言中括号已经是最清晰的标志了。另外,在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。最后,即使留空格,也不要连续留两个以上空格(为了保证缩进和排比留空除外 )。函数体的开始,类的定义,结构的定义,if 、for、do 、while 、switch 及 case语句中的程序都应采用缩进方式,憑捄蛻捰禀独占一行并且位于同一列,同时与引用它
5、们的语句左对齐例如下例不符合规范。for ( . ) . / 程序代码if ( . ). / 程序代码void DoExam( void ). / 程序代码应如下书写。for ( . ). / 程序代码if ( . ). / 程序代码void DoExam( void ). / 程序代码功能相对独立的程序块之间或 for、if、do、while 、switch 等语句前后应加一空行。例如以下例子不符合规范。例一:if ( ! ValidNi( ni ) ). / 程序代码nRepssnInd = SsnData index .nRepssnIndex ;nRepssnNi = SsnData
6、index .ni ;例二:char *pContext;int nIndex;long lCounter;pContext = new (CString);if(pContext = NULL)return FALSE;应如下书写例一:if ( ! ValidNi( ni ) ). / 程序代码nRepssnInd = SsnData index .nRepssnIndex ;nRepssnNi = SsnData index .ni ;例二:char *pContext;int nIndex;long lCounter;pContext = new (CString);if(pContex
7、t = NULL)return FALSE;if、while、for、case 、default、do 等语句自占一行。示例:如下例子不符合规范。if(pUserCR = NULL) return;应如下书写:if( pUserCR = NULL )return;若语句较长(多于 80 字符),可分成多行写,划分出的新行要进行适应的缩进,使排版整齐,语句可读。memset(pData-pData + pData-nCount, 0,(m_nMax - pData-nCount) * sizeof(LPVOID);CNoTrackObject* pValue =(CNoTrackObject*)
8、_afxThreadData-GetThreadValue(m_nSlot);for ( i = 0, j = 0 ; ( i 一行最多写一条语句。示例:如下例子不符合规范。rect.length = 0 ; rect.width = 0 ;rect.length = width = 0;都应书写成:rect.length = 0 ;rect.width = 0 ;对结构成员赋值,等号对齐。示例:rect.top = 0;rect.left = 0;rect.right = 300;rect.bottom = 200;#define 的各个字段对齐以下示例不符合规范#define MAX_TA
9、SK_NUMBER 100#define LEFT_X 10#define BOTTOM_Y 400应书写成:#define MAX_TASK_NUMBER 100#define LEFT_X 10#define BOTTOM_Y 400不同类型的操作符混合使用时,使用括号给出优先级。如本来是正确的代码:if( year % 4 = 0 | year % 100 != 0 nRepssnNi = SsnData index .ni ;例子 2nRepssnInd = SsnData index .nRepssnIndex ;nRepssnNi = SsnData index .ni ;/*获得
10、系统指针和网络指针的副本 */应如下书写/*获得系统指针和网络指针的副本 */nRepssnInd = SsnData index .nRepssnIndex ;nRepssnNi = SsnData index .ni ;对于所有的常量,变量,数据结构声明( 包括数组、结构、类、枚举等),如果其命名不是充分自注释的,在声明时都必须加以注释,说明其含义。示例:/* 活动任务的数量 */#define MAX_ACT_TASK_NUMBER 1000#define MAX_ACT_TASK_NUMBER 1000 /*活动任务的数量 */* 带原始用户信息的 SCCP 接口 */enum SCC
11、P_USER_PRIMITIVEN_UNITDATA_IND , /* 向 SCCP 用户报告单元数据已经到达 */N_UNITDATA_REQ , /* SCCP 用户的单元数据发送请求 */ ;头文件、源文件的头部,应进行注释。注释必须列出:文件名、作者、目的、功能、修改日志等。例如:/*文件名:编写者:编写日期:简要描述:修改记录:*/说明:摷蛞 枋鰯一项描述本文件的目的和功能等。撔薷募锹紨是修改日志列表,每条修改记录应包括修改日期、修改者及修改内容简述。函数头部应进行注释,列出 :函数的目的、功能、输入参数、输出参数、修改日志等。形式如下:/*函数名称:简要描述: / 函数目的、功能等
12、的描述输入: / 输入参数说明,包括每个参数的作用、取值说明及参数间关系,输出: / 输出参数的说明, 返回值的说明修改日志:*/对一些复杂的函数,在注释中最好提供典型用法。仔细定义并明确公共变量的含义、作用、取值范围及使用方法。在对变量声明的同时,应对其含义、作用、取值范围及使用方法进行注释说明,同时若有必要还应说明与其它变量的关系。明确公共变量与操作此公共变量的函数或过程的关系,如访问、修改及创建等。示例:/* SCCP 转换时错误代码 */* 全局错误代码,含义如下 */ / 变量作用、含义/* 0 - 成功 1 - GT 表错误 2 -GT 错误 其它值- 未使用 */ / 变量取值范
13、围对指针进行充分的注释说明,对其作用、含义、使用范围、注意事项等说明清楚。在对指针变量、特别是比较复杂的指针变量声明时,应对其含义、作用及使用范围进行注释说明,如有必要,还应说明其使用方法、注意事项等。示例:/* 学生记录列表的头指针 */* 当在此模块中创建该列表时,该头指针必须初始化, */* 这样可以利用 GetListHead()获得这一列表。*/ /指针作用、含义/* 该指针只在本模块使用,其它模块通过调用 GetListHead()获取*/* 当使用时必须保证它非空 */ /使用范围、方法STUDENT_RECORD *pStudentRecHead;对重要代码段的功能、意图进行注
14、释,提供有用的、额外的信息。并在该代码段的结束处加一行注释表示该段代码结束。示例:/* 可选通道的组合 */if (gsmBCIe31-radioChReq = DUAL_HR_RCR)gsmBCIe32-radioChReq = FR_RCR;else if (gsmBCIe31-radioChReq = DUAL_HR_RCR)else if (gsmBCIe31-radioChReq = FR_RCR)/* 本块结束 ( 可选通道组合 ) */在 switch 语句中,对没有 break 语句的 case 分支加上注释说明。示例:switch(SubT30State)case TA0:A
15、T(CHANNEL, “AT+FCLASS=1r“, 0);if(T30Status != 0)return(1);InitFax(); /* 准备发送传真 */AT(CHANNEL, “ATDr“,-1); /*发送 CNG ,接收 CED 和 HDLC 标志*/T1_Flg = 1;iResCode = 0;/* 没有 break; */case TA1:iResCode = GetModemMsg(CHANNEL);break;default:break;维护代码时,要更新相应的注释,删除不再有用的注释。保持代码、注释的一致性,避免产生误解。1.2 命名本文列出 Visual C+的标识
16、符命名规范。标识符缩写形成缩写的几种技术:1) 去掉所有的不在词头的元音字母。如 screen 写成 scrn, primtive 写成 prmv。2) 使用每个单词的头一个或几个字母。如 Channel Activation 写成 ChanActiv,Release Indication 写成 RelInd。3) 使用变量名中每个有典型意义的单词。如 Count of Failure 写成 FailCnt。4) 去掉无用的单词后缀 ing, ed 等。如 Paging Request 写成 PagReq。5) 使用标准的或惯用的缩写形式(包括协议文件中出现的缩写形式 )。如 BSIC(Bas
17、e Station Identification Code)、MAP(Mobile Application Part)。关于缩写的准则:1) 缩写应该保持一致性。如 Channel 不要有时缩写成 Chan,有时缩写成 Ch。Length有时缩写成 Len,有时缩写成 len。2) 在源代码头部加入注解来说明协议相关的、非通用缩写。3) 标识符的长度不超过 32 个字符。变量命名约定参照匈牙利记法,即作用范围域前缀 + 前缀 + 基本类型 + 变量名其中:前缀是可选项,以小写字母表示;基本类型是必选项,以小写字母表示;变量名是必选项,可多个单词(或缩写) 合在一起,每个单词首字母大写。前缀列表
18、如下:前缀 意义 举例g_ Global 全局变量 g_MyVarm_ 类成员变量 或 模块级变量 m_ListBox, m_Sizes_ static 静态变量 s_Counth Handle 句柄 hWndp Pointer 指针 pTheWordlp Long Point 长指针 lpCmda Array 数组 aErr基本类型列表如下:基本类型 意义 举例b Boolean 布尔 bIsOKby Byte 字节 byNumc Char 字符 cMyChari 或 n Intger 整数 nTestNumberu Unsigned integer 无符号整数 uCountul Unsig
19、ned Long 无符号长整数 ulTimew Word 字 wParadw Double Word 双字 dwParal Long 长型 lParaf Float 浮点数 fTotals String 字符串 sTempsz NULL 结束的字符串 szTreesfn Funtion 函数 fnAddenm 枚举型 enmDaysx,y x,y 坐标宏和常量的命名宏和常量的命名规则:单词的字母全部大写,各单词之间用下划线隔开。命名举例:#define MAX_SLOT_NUM 8#define EI_ENCR_INFO 0x07const int MAX_ARRAY结构和结构成员的命名结构名
20、各单词的字母均为大写,单词间用下划线连接。可用或不用 typedef,但是要保持一致,不能有的结构用 typedef,有的又不用。如:typedef struct LOCAL_SPC_TABLE_STRUchar cValid;int nSpcCodeMAX_NET_NUM; LOCAL_SPC_TABLE ;结构成员的命名同变量的命名规则。枚举和枚举成员的命名枚举名各单词的字母均为大写,单词间用下划线隔开。枚举成员的命名规则:单词的字母全部大写,各单词之间用下划线隔开;要求各成员的第一个单词相同。命名举例:typdef enumLAPD_ MDL_ASSIGN_REQ,LAPD_MDL_AS
21、SIGN_IND,LAPD_DL_DATA_REQ,LAPD_DL_DATA_IND,LAPD_DL_UNIT_DATA_REQ,LAPD_DL_UNIT_DATA_IND, LAPD_PRMV_TYPE;类的命名前缀 意义 举例C 类 CMyClassCO COM 类 COMMyObjectClassCF COM class factory CFMyClassFactoryI COM interface class IMyInterfaceCImpl COM implementation class CImplMyInterface函数的命名单词首字母为大写,其余均为小写,单词之间不用下划线
22、。函数名应以一个动词开头,即函数名应类似摱 鼋峁箶。命名举例:void PerformSelfTest(void) ;void ProcChanAct(MSG_CHAN_ACTIV *pMsg, UC MsgLen);1.3 可维护性在逻辑表达式中使用明确的逻辑判断。示例:如下逻辑表达式不规范。1) if ( strlen(strName) )2) for ( index = MAX_SSN_NUMBER; index ; index - )3) while ( p index != 0 ; index - )3) while (p != NULL) i 使用宏定义表达式时,要使用完备的括号。
23、如下的宏定义表达式都存在一定的隐患。#define REC_AREA(a, b) a * b#define REC_AREA(a, b) (a * b)#define REC_AREA(a, b) (a) * (b)正确的定义为:#define REC_AREA(a, b) (a) * (b)宏所定义的多条表达式应放在大括号内。示例:下面的语句只有宏中的第一条表达式被执行。为了说明问题,for 语句的书写稍不符规范。#define INIT_RECT_VALUE( a, b ) a = 0 ; b = 0 ;for ( index = 0 ; index 宏定义不能隐藏重要的细节,避免有 re
24、turn,break 等导致程序流程转向的语句。如下例子是不规范的应用,其中隐藏了程序的执行流程。#define FOR_ALL for(i = 0; i 使用宏时,不允许参数发生变化。下面的例子隐藏了重要的细节,隐含了错误。#define SQUARE (x) * (x).w = SQUARE(+value);这个引用将被展开称:w = (+value) * (+value);其中 value 累加了两次,与设计思想不符。正确的用法是:w = SQUARE(x);x+;当 if、while、 for 等语句的程序块为摽諗时,使用搟敺 拧_while ( *s+ = *t+ ) ;以上代码不符
25、合规范,正确的书写方式为:while( *s+ = *t+ )/* 无循环体 */或while( *s+ = *t+ )结构中元素布局合理,一行只定义一个元素。如下例子不符合规范,typedef struct_UI left, top, right, bottom; RECT;应书写称:typedef struct_UI left; /* 矩形左侧 x 坐标 */_UI top;_UI right;_UI bottom; RECT;枚举值从小到大顺序定义。包含头文件时,使用撓喽月肪稊,不使用摼 月肪稊。如下引用:#include “c:switchincdef.inc“应改为:#include
26、 “incdef.inc“或#include “def.inc“不允许使用复杂的操作符组合等。下面用法不好,iMaxVal = ( (a b ? a : b) c ? (a b ? a : b) : c );应该为:iTemp = ( a b ? a : b);iMaxVal = (iTemp b ? iTemp : b);不要把“+“、“-“操作符与其他如“+=“、“-=“ 等组合在一起形成复杂奇怪的表达式。如下的表达式那以理解。*pStatPoi+ += 1;*+pStatPoi += 1;应分别改为:*pStatPoi += 1;pStatPoi+;和+pStatPoi;*pStatPo
27、i += 1;函数和过程中关系较为紧密的代码尽可能相邻。如初始化代码应放在一起,不应在中间插入实现其它功能的代码。以下代码不符合规范,for (uiUserNo = 0; uiUserNo 每个函数的源程序行数原则上应该少于 200 行。对于消息分流处理函数,完成的功能统一,但由于消息的种类多,可能超过 200 行的限制,不属于违反规定。语句嵌套层次不得超过 5 层。嵌套层次太多,增加了代码的复杂度及测试的难度,容易出错,增加代码维护的难度。用 sizeof 来确定结构、联合或变量占用的空间。这样可提高程序的可读性、可维护性,同时也增加了程序的可移植性。避免相同的代码段在多个地方出现。当某段代
28、码需在不同的地方重复使用时,应根据代码段的规模大小使用函数调用或宏调用的方式代替。这样,对该代码段的修改就可在一处完成,增强代码的可维护性。使用强制类型转换。示例:USER_RECORD *pUser;pUser = (USER_RECORD *) malloc (MAX_USER * sizeof(USER_RECORD);避免使用 goto 语句。避免产生摮绦蚪釘(program knots),在循环语句中,尽量避免 break、goto的使用。如下例子:for( i = 0; i 功能相近的一组常量最好使用枚举来定义。不推荐定义方式:/* 功能寄存器值 */#define ERR_DAT
29、E 1 /* 日期错误 */#define ERR_TIME 2 /* 时间错误 */#define ERR_TASK_NO 3 /* 任务号错误 */推荐按如下方式书写:/*功能寄存器值 */enum ERR_TYPEERR_DATE = 1, /*日期错误 */ERR_TIME = 2, /*时间错误 */ERR_TASK_NO = 3 /* 任务号错误 */每个函数完成单一的功能,不设计多用途面面俱到的函数。多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难。使函数功能明确化,增加程序可读性,亦可方便维护、测试。循环、判断语句的程序块部分用花括号括起来,即使只有一条语句。如:
30、if( bCondition = TRUE )bFlag = YES;建议按以下方式书写:if( bCondition = TRUE )bFlag = YES;这样做的好处是便于代码的修改、增删。一行只声明一个变量。不推荐的书写方式:void DoSomething(void)int Amicrtmrs, nRC;int nCode, nStatus;推荐做法:void DoSomething(void)int nAmicrtmrs; /* ICR 计时器 */int nRC; /* 返回码 */int nCode; /* 访问码 */int nStatus; /* 处理机状态 */使用专门的
31、初始化函数对所有的公共变量进行初始化。使用可移植的数据类型,尽量不要使用与具体硬件或软件环境关系密切的变量。用明确的函数实现不明确的语句功能示例:如下语句的功能不很明显。value = ( a b ) ? a : b ;改为如下就很清晰了。int max( int a, int b )return ( ( a b ) ? a : b ) ;value = max( a, b ) ;或改为如下。#define MAX( a, b ) ( ( ( a ) ( b ) ) ? ( a ) : ( b ) )value = MAX( a, b ) ;1.4. 程序正确性、效率严禁使用未经初始化的变量。
32、引用未经初始化的变量可能会产生不可预知的后果,特别是引用未经初始化的指针经常会导致系统崩溃,需特别注意。声明变量的同时初始化,除了能防止引用未经初始化的变量外,还可能生成更高效的机器代码。定义公共指针的同时对其初始化。这样便于指针的合法性检查,防止应用未经初始化的指针。建议对局部指针也在定义的同时初始化,形成习惯。较大的局部变量(2K 以上)应声明成静态类型(static),避免占用太多的堆栈空间。避免发生堆栈溢出,出现不可预知的软件故障。防止内存操作越界。说明:内存操作主要是指对数组、指针、内存地址等的操作。内存操作越界是软件系统主要错误之一,后果往往非常严重,所以当我们进行这些操作时一定要
33、仔细小心。A.数组越界。char aMyArray10;for( i = 0; i 减少没必要的指针使用,特别是较复杂的指针,如指针的指针、数组的指针,指针的数组,函数的指针等。用指针虽然灵活,但也对程序的稳定性造成一定威胁,主要原因是当要操作一个指针时,此指针可能正指向一个非法的地址。安安全全地使用一个指针并不是一件容易的事情。防止引用已经释放的内存空间。在实际编程过程中,稍不留心就会出现在一个模块中释放了某个内存块(如指针) ,而另一模块在随后的某个时刻又使用了它。要防止这种情况发生。程序中分配的内存、申请的文件句柄,在不用时应及时释放或关闭。分配的内存不释放以及文件句柄不关闭,是较常见的
34、错误,而且稍不注意就有可能发生。这类错误往往会引起很严重后果,且难以定位。注意变量的有效取值范围,防止表达式出现上溢或下溢。示例:unsigned char cIndex = 10;while( cIndex- = 0 ) /将出现下溢当 cIndex 等于 0 时,再减 1 不会小于 0,而是 0xFF,故程序是一个死循环。char chr = 127;chr += 1; /127 为 chr 的边界值,再加 1 将使 chr 上溢到-128,而不是 128。防止精度损失。以下代码将产生精度丢失。#define DELAY_MILLISECONDS 10000char time;time =
35、 DELAY_MILLISECONDS;WaitTime( time );代码的本意是想产生 10 秒钟的延时,然而由于 time 为字符型变量,只取DELAY_MILLISECONDS 的低字节,高位字节将丢失,结果只产生了 16 毫秒的延时。防止操易混淆的作符拼写错误。形式相近的操作符最容易引起误用,如 C/C+中的“=斢霌= 敗 |斢霌|敗 被写为:bRetFlag = ( pMsg - bRetFlag 使用无符号类型定义位域变量。示例:typedef structint bit1 : 1;int bit2 : 1;int bit3 : 1; bit;bit.bit1 = 1;bit.
36、bit2 = 3;bit.bit3 = 6;printf(“%d, %d, %d“, bit.bit1, bit.bit2, bit.bit3 );输出结果为:-1,-1, -2,不是: 1,3,6.switch 语句的程序块中必须有 default 语句。对不期望的情况(包括异常情况 )进行处理,保证程序逻辑严谨。当声明用于分布式环境或不同 CPU 间通信环境的数据结构时,必须考虑机器的字节顺序,使用的位域也要有充分的考虑。比如 Intel CPU 与 68360 CPU,在处理位域及整数时,其在内存存放的撍承驍,正好相反。示例:假如有如下短整数及结构。unsigned short int
37、exam ;typedef struct _EXAM_BIT_STRU /* Intel 68360 */unsigned int A1 : 1 ; /* bit 0 2 */unsigned int A2 : 1 ; /* bit 1 1 */unsigned int A3 : 1 ; /* bit 2 0 */ _EXAM_BIT ;如下是 Intel CPU 生成短整数及位域的方式。内存: 0 1 2 . (从低到高,以字节为单位)exam exam 低字节 exam 高字节内存: 0 bit 1 bit 2 bit . (字节的各撐粩)_EXAM_BIT A1 A2 A3如下是 683
38、60 CPU 生成短整数及位域的方式。内存: 0 1 2 . (从低到高,以字节为单位)exam exam 高字节 exam 低字节内存: 0 bit 1 bit 2 bit . (字节的各撐粩)_EXAM_BIT A3 A2 A1编写可重入函数时,应注意局部变量的使用( 如编写 C/C+语言的可重入函数时,应使用 auto 即缺省态局部变量或寄存器变量) 。可重入性是指函数可以被多个任务进程调用。在多任务操作系统中,函数是否具有可重入性是非常重要的,因为这是多个进程可以共用此函数的必要条件。另外,编译器是否提供可重入函数库,与它所服务的操作系统有关,只有操作系统是多任务时,编译器才有可能提供
39、可重入函数库。如 DOS 下 BC 和 MSC 等就不具备可重入函数库,因为 DOS 是单用户单任务操作系统。编写 C/C+语言的可重入函数时,不应使用 static 局部变量,否则必须经过特殊处理,才能使函数具有可重入性。编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即 P、V操作)等手段对其加以保护。结构中的位域应尽可能相邻。结构中的位域在开始处应对齐撟纸跀或撟謹的边界。这样可减少结构占用的内存空间,减少 CPU 处理位域的时间,提高程序效率。示例:如下结构中的位域布局不合理。(假设例子在 Intel CPU 环境下)typedef struct _EXAMPLE_STRUun
40、signed int nExamOne : 6 ;unsigned int nExamTwo : 3 ; / 此位域跨越字节摻唤訑处。unsigned int nExamThree : 4 ; _EXAMPLE ;应改为如下(按字节对齐 )。typedef struct _EXAMPLE_STRUunsigned int nExamOne : 6 ;unsigned int nFreeOne : 2 ; / 保留 bit 位,使下个位域从字节开始。unsigned int nExamTwo : 3 ; / 此位域从新的字节处开始。unsigned int nExamThree : 4 ; _E
41、XAMPLE ;避免函数中不必要语句,防止程序中的垃圾代码,预留代码应以注释的方式出现。程序中的垃圾代码不仅占用额外的空间,而且还常常影响程序的功能与性能,很可能给程序的测试、维护等造成不必要的麻烦。通过对系统数据结构的划分与组织的改进,以及对程序算法的优化来提高空间效率。这种方式是解决软件空间效率的根本办法。示例:如下记录学生学习成绩的结构不合理。typedef unsigned char _UC ;typedef unsigned int _UI ;typedef struct _STUDENT_SCORE_STRU_UC szName 8 ;_UC cAge ;_UC cSex ;_UC
42、 cClass ;_UC cSubject ;float fScore ; _STUDENT_SCORE ;因为每位学生都有多科学习成绩,故如上结构将占用较大空间。应如下改进(分为两个结构),总的存贮空间将变小,操作也变得更方便。typedef struct _STUDENT_STRU_UC szName 8 ;_UC cAge ;_UC cSex ;_UC cClass ; _STUDENT ;typedef struct _STUDENT_SCORE_STRU_UI iStudentIndex ;_UC cSubject ;float fScore ; _STUDENT_SCORE ;循环
43、体内工作量最小化。应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从而提高程序的时间效率。示例:如下代码效率不高。for ( i= 0 ; i 在多重循环中,应将最忙的循环放在最内层。避免循环体内含判断语句,将与循环变量无关的判断语句移到循环体外。目的是减少判断次数。循环体中的判断语句是否可以移到循环体外,要视程序的具体情况而言,一般情况,与循环变量无关的判断语句可以移到循环体外,而有关的则不可以。尽量用乘法或其它方法代替除法,特别是浮点运算中的除法,在时间效率要求不是特别严格时,要优先保证程序的可读性。说明:浮点运算除法要占用较多 CPU 资源。示例:如下表达式运算可能
44、要占较多 CPU 资源。#define PAI 3.1416fRadius = fCircleLength / ( 2 * PAI ) ;应如下把浮点除法改为浮点乘法。#define PAI_RECIPROCAL ( 1 / 3.1416 ) / 编译器编译时,将生成具体浮点数fRadius = fCircleLength * PAI_RECIPROCAL / 2 ;用“+敚瑩- 敳僮鞔 鎿+=1 敚瑩-=1 敚 岣叱绦蛩俣取_系统输入( 如用户输入)、系统输出(如信息包输出)、系统资源操作(如内存分配、文件及目录操作)、网络操作 (如通信、调用等)、任务之间的操作(如通信、调用等) 时必须进
45、行错误、超时或者异常处理。定义字符串变量的同时将其初始化为空即摂,以避免无限长字符串。在 switch 语句中将经常性的处理放在前面。1.5 接口头文件应采用 #ifndef / #define / #endif 的方式来防止多次被嵌入。示例如下:假设头文件为揇 EF.INC“,则其内容应为:#ifndef _DEF_INC#define _DEF_INC.#endif去掉没有必要的公共变量,编程时应尽量少用公共变量。公共变量是增大模块间耦合的原因之一,故应减少没必要的公共变量以降低模块间的耦合度。应该构造仅有一个模块或函数可以修改、创建,而其余有关模块或函数只访问的公共变量,防止多个不同模块
46、或函数都可以修改、创建同一公共变量的现象。当向公共变量传递数据时,要防止越界现象发生。对公共变量赋值时,若有必要应进行合法性检查,以提高代码的可靠性、稳定性。返回值为指针的函数,不可将局部变量的地址作为返回值。当函数退出时,非 static 局部变量将消失,所以引用返回的指针将可能引起严重后果。下例将不能完成正确的功能。char *GetFilename(int nFileNo)char szFileName20;sprintf( szFileName, “COUNT%d“, nFileNo);return szFileName;尽量不设计多参数函数,将不使用的参数从接口中去掉,降低接口复杂度
47、。减少函数间接口的复杂度。对所调用函数的返回码要仔细、全面地处理。防止把错误传递到后面的处理流程。如有意不检查其返回码,应明确指明。 如:(void)fclose(fp);显示地给出函数的返回值类型。无返回值函数定义为 void。C 、C+ 语言的编译系统默认无显示返回值函数的返回值类型为 int。声明函数原型时给出参数名称和类型,并且与实现此函数时的参数名称、类型保持一致,无参数的函数,用 void 声明。示例:下面声明不正确。int CheckData( ) ;int SetPoint( int, int ) ;int SetPoint( x, y )int x, y;应改为如下声明:in
48、t CheckData( void ) ;int SetPoint( int x, int y ) ;检查接口函数所有输入参数的有效性。可直接检查或使用断言进行检查,尤其是指针参数。只在本模块内使用的函数可不检查。检查函数的所有非参数输入,如数据文件、公共变量等。可直接检查或使用断言进行检查,尤其是指针变量。声明函数原型时,对于数组型参数,不要声明为指针,维护函数接口的清晰性。示例:假设函数 SortInt()完成的功能是对一组整数排序,接受的参数是一整数数组及数组中的元素个数,以下声明不符合规范。void SortInt(int num, int *data);应声明为:void SortInt(int num, int data);1.6 代码可测性模块编写应该有完善的测试方面的考虑。源代码中应该设计了代码测试的内容,如打印宏开关、变量值、函数名称、函数值等。在编写代码之前,应预先设计好程序调试与测试的方法和手段,并设计好各种调测开关及相应测试代码如打印函数等。程序的调试与测试是软件生存周期中很重要的一个阶段,如何对软件进行较全面、高率的测试并尽可能地找出软件中的错误就成为