《C++编程规范》

     

 

 

            技术文件名称:编码规范_C++

            技术文件编号:

                    本:1.0

          

 

 

 

 

 

                                                 

                                                    

                                                    

                                                        

                                                        

                                                        

                             标准化                     

                                                    

 

 

 

 

 

1.前言

1.1.编码规范目的

  • 增加代码的强壮性、可读性、易维护性。
  • 减少开发人员编程所需的脑力工作。
  • 在项目范围内统一代码风格。
  • 通过人为和自动方式对软件应用质量标准。
  • 使新的开发人员快速适应项目氛围。
  • 支持项目资源的复用。

1.2 编码规范基本内容

  • 排版格式
  • 注释风格
  • 标识符命名
  • 可读性
  • 变量、结构
  • 函数、过程、类
  • 可测性
  • 质量保证
  • 代码编辑、编译,审查

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                2排版

 

2-1:  程序块要采用缩进风格编写,缩进的空格数为4个。 说明:对于由开发工具自动生成的代码可以有不一致。

 

2-2:  相对独立的程序块之间、变量说明之后须加空行。 示例:如下例子不符合规范。

 

if (!valid_ni(ni))

 

{

 

... // program code

 

}

 

repssn_ind = ssn_data[index].repssn_index;

 

repssn_ni = ssn_data[index].ni;

 

 

应如下书写

 

if (!valid_ni(ni))

 

{

 

... // program code

 

}

 

 

repssn_ind = ssn_data[index].repssn_index;

 

repssn_ni = ssn_data[index].ni;

 

2-3: 较长的语句(>80字符)要分成多行书写,长表达式要在低优先级操作符处划分新行, 操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读,一行程序以小于80字符为宜,不要写得过长。

示例:

 

perm_count_msg.head.len = NO7_TO_STAT_PERM_COUNT_LEN

 

+ STAT_SIZE_PER_FRAM * sizeof( _UL );

 

 

act_task_table[frame_id * STAT_TASK_CHECK_NUMBER + index].occupied

 

= stat_poi[index].occupied;

 

 

2-4:  若函数或过程中的参数较长,则要进行适当的划分。

示例:

 

n7stat_str_compare((BYTE *) & stat_object,

(BYTE *) & (act_task_table[taskno].stat_object),

sizeof (_STAT_OBJECT));

 

n7stat_flash_act_duration( stat_item, frame_id *STAT_TASK_CHECK_NUMBER + index, stat_object );

 

2-5:不允许把多个短语句写在一行中,即一行只写一条语句。

示例:如下例子不符合规范。

 

rect.length = 0;   rect.width = 0;

 

 

应如下书写

rect.length = 0;

 

rect.width    = 0;

 

2-6:if、for、do、while、case、switch、default等语句自占一行,且if、for、

do、while等语句的执行语句部分无论多少都要加括号{}。

示例:如下例子不符合规范。

 

if (pUserCR == NULL) return;

应如下书写:

 

if (pUserCR == NULL)

{

return;

}

 

2-7:对齐只使用空格键,不使用TAB键。

说明:以免用不同的编辑器阅读程序时,因 TAB 键所设置的空格数目不同而造成程序布局 不整齐,不要使用 BC 作为编辑器合版本,因为 BC 会自动将 8 个空格变为一个 TAB 键, 因此使用 BC 合入的版本大多会将缩进变乱。

 

2-8:函数或过程的开始、结构的定义及循环、判断等语句中的代码都要采用缩进风格,case语句下的情况处理语句也要遵从语句缩进要求。

 

2-9:程序块的分界符(如C/C++语言的大括号‘{’和‘}’)应各独占一行并且位于同一 列,同时与引用它们的语句左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以 及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式。

 

示例:如下例子不符合规范。

 

for (...) {

 

... // program code

 

}

 

 

 

if (...)

{

... // program code

}

 

 

void example_fun( void )

{

... // program code

}

 

 

应如下书写。

 

for (...)

{

... // program code

}

 

 

if (...)

{

... // program code

}

 

 

 

void example_fun( void )

{

... // program code

}

 

2-10:在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。

说明:采用这种松散方式编写代码的目的是使代码更加清晰。 由于留空格所产生的清晰性是相对的,所以,在已经非常清晰的语句中没有必要再留空格, 如果语句已足够清晰则括号内侧(即左括号后面和右括号前面)不需要加空格,多重括号间 不必加空格,因为在 C/C++语言中括号已经是最清晰的标志了。在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连续留两个以上空格。

 

 

示例:

(1) 逗号、分号只在后面加空格。

 

int a, b, c;

 

 

(2)比较操作符, 赋值操作符"="、  "+=",算术操作符"+"、"%",逻辑操作符"&&"、"&",位域操作符"<<"、"^"等双目操作符的前后加空格。

 

if (current_time >= MAX_TIME_VALUE)

a = b + c;

a *= 2;

a = b ^ 2;

 

 

(3)"!"、"~"、"++"、"--"、"&"(地址运算符)等单目操作符前后不加空格。

*p = 'a'; // 内容操作"*"与内容之间

flag = !isEmpty; // 非操作"!"与内容之间

p = &mem; // 地址操作"&" 与内容之间

i++; // "++","--"与内容之间

 

 

(4)"->"、"."前后不加空格。

p->id = pid; // "->"指针前后不加空格

 

 

(5) if、for、while、switch 等与后面的括号间应加空格,使  if 等关键字更为突出、 明显。

 

if (a >= b && c > d)

 

 

 

3注释

 

3-1:一般情况下,源程序有效注释量必须在20%以上。 说明:注释的原则是有助于对程序的阅读理解,在该加的地方都加了,注释不宜太多也不 能太少,注释语言必须准确、易懂、简洁。

3-2:说明性文件(如头文件.h文件、.inc文件、.def文件、编译说明文件.cfg等)头部应 进行注释,注释必须列出:版权说明、版本号、生成日期、作者、内容、功能、与其它文件的 关系、修改日志等,头文件的注释中还应有函数功能简要说明。

示例:下面这段头文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要 包含在内。

 

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

Copyright (C), 1988-1999, lanmage Tech. Co., Ltd.

File name:   // 文件名

Author:   Version:    Date: // 作者、版本及完成日期

Description: // 用于详细说明此程序文件完成的主要功能,与其他模块

// 或函数的接口,输出值、取值范围、含义及参数间的控

// 制、顺序、独立或依赖等关系

Others:      // 其它内容的说明

Function List:// 主要函数列表,每条记录应包括函数名及功能简要说明

 

1. ....

History: // 修改历史记录列表,每条修改记录应包括修改日期、修改

// 者及修改内容简述

 

1. Date: Author: Modification:

2. ...

 

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

 

 

3-3: 源文件头部应进行注释,列出:版权说明、版本号、生成日期、作者、模块目的/功能、 主要函数及其功能、修改日志等。

示例:下面这段源文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要 包含在内。

/*********************************************************** Copyright (C), 1988-1999, Huawei Tech. Co., Ltd.

FileName: test.cpp

 

Author:  

Version :

Date:

Description:    // 模块描述

Version: // 版本信息

Function List: // 主要函数及其功能

 

1. -------

History: // 历史修改记录

 

<author> <time>   <version > <desc>

 

David 96/10/12    1.0   build this moudle

 

*********************************************************/ 说明:Description 一项描述本文件的内容、功能、内部各部分之间的关系及本文件与 其它文件关系等。History 是修改历史记录列表,每条修改记录应包括修改日期、修改 者及修改内容简述。

 

3-4:函数头部应进行注释,列出:函数的目的/功能、输入参数、输出参数、返回值、调用 关系(函数、表)等。

 

示例:下面这段函数的注释比较标准,当然,并不局限于此格式,但上述信息建议要包含 在内。

 

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

Function:     // 函数名称

Description: // 函数功能、性能等的描述

Input:        // 输入参数说明,包括每个参数的作

// 用、取值说明及参数间关系。

Output:     // 对输出参数的说明。

Return:     // 函数返回值的说明

Others:     // 其它说明

 

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

 

 

3-5:边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。

3-6:注释的内容要清楚、明了,含义准确,防止注释二义性。 说明:错误的注释不但无益反而有害。

3-7:释应与其描述的代码相近,对代码的注释应放在其上方或右方(对单条语句的注释) 相邻位置,不可放在下面,如放于上方则需与其上面的代码用空行隔开。

示例:如下例子不符合规范。 例 1:

/* get replicate sub system index and net indicator */

 

 

repssn_ind = ssn_data[index].repssn_index;

 

repssn_ni = ssn_data[index].ni;

 

 

例 2:

 

repssn_ind = ssn_data[index].repssn_index;

 

repssn_ni = ssn_data[index].ni;

 

/* get replicate sub system index and net indicator */

 

 

应如下书写

 

/* get replicate sub system index and net indicator */

 

repssn_ind = ssn_data[index].repssn_index;

 

repssn_ni = ssn_data[index].ni;

 

 

 

3-8:对于所有有物理含义的变量、常量,如果其命名不是充分自注释的,在声明时都必须加 以注释,说明其物理含义。变量、常量、宏的注释应放在其上方相邻位置或右方。

示例:

 

/* active statistic task number */

#define MAX_ACT_TASK_NUMBER 1000

 

#define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */

 

 

3-9:数据结构声明(包括数组、结构、类、枚举等),如果其命名不是充分自注释的,必须 加以注释。对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每个域的注 释放在此域的右方。

示例:可按如下形式说明枚举/数据/联合结构。

 

/* sccp interface with sccp user primitive message name */

Enum  SCCP_USER_PRIMITIVE

{

N_UNITDATA_IND,  /* sccp notify sccp user unit data come */ N_NOTICE_IND,            /* sccp notify user the network can not */

/* transmission this message */

N_UNITDATA_REQ, /* sccp user's unit data transmission request*/

}

 

3-10: 全局变量要有较详细的注释,包括对其功能、取值范围、哪些函数或过程存取它以及存取时注意事项等的说明。
示例:

 

/* The ErrorCode when SCCP translate */

/* Global Title failure, as follows */ // 变量作用、含义

/* 0 -  SUCCESS 1 -  GT Table error */

/* 2 -  GT error   Others -  no use */ // 变量取值范围

 

/* only     function SCCPTranslate() in */

 

/* this modual can modify it, and   other */

 

/* module can visit it through call */

/* the      function GetGTTransErrorCode() */   // 使用方法

 

BYTE g_GTTranErrorCode;

 

3-11: 注释与所描述内容进行同样的缩排。 说明:可使程序排版整齐,并方便注释的阅读与理解。 示例:如下例子,排版不整齐,阅读稍感不方便。

 

void example_fun( void )

{

/* code one comments */

CodeBlock One

/* code two comments */ CodeBlock Two

}

 

 

应改为如下布局。

 

void example_fun( void )

{

/* code one comments */

 CodeBlock One

 

/* code two comments */

CodeBlock Two

}

 

 

4标识符命名

4-1:标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的 缩写,避免使人产生误解。

说明:较短的单词可通过去掉“元音”形成缩写;较长的单词可取单词的头几个字母形成 缩写;一些单词有大家公认的缩写。

示例:如下单词的缩写能够被大家基本认可。

temp 可缩写为 tmp ;

flag 可缩写为 flg ;

statistic 可缩写为stat;

 increment 可缩写为 inc    ;

 message 可缩写为 msg ;

前缀

说明

例子

m_

类的成员变量(member

Int  m_width

ms_

类的静态成员变量(static member

static int ms_initValue;

s_

静态变量(static

static int s_initValue; 

g_

外部全局变量(global

int g_howManyPeople;

sg_

静态全局变量(static global

 

gg_

进程间共享的共享数据段全局变量(global global

 

 

 

 

前缀

说明

例子

b

布尔型变量(bool, BOOL)

bEnable

ch

字符型变量(char TCHAR)

chName

lpsz

LPSTR、LPCSTR、LPCTSTR

lpszName

n

整型和位域变量(int, UINT,__int32,__int64)

nLength

l

long

lOffset

by

BYTE

 

w

WORD

wPos

dw

DWORD

dwRange

f

浮点型变量(float)

 

d

double

 

p

指针型变量和迭代子(pointer)

pDoc

lp

远指针

 

e

枚举型变量(enumeration)

 

pfn

特别针对指向函数的指针变量和函数对象指针(pointer of function)

 

g

数组(grid)

 

h

handle   Windows对象句柄

hWnd

 

 

4-2:命名规范必须与所使用的系统风格保持一致,并在同一项目中统一,比如采用UNIX的 全小写加下划线的风格或大小写混排的方式,不要使用大小写与下划线混排的方式,用作特殊 标识如标识成员变量或全局变量的m_和g_,其后加上大小写混排的方式是允许的。

示例:  Add_User 不允许,add_user、AddUser、m_AddUser 允许。

 

 

4-3:除非必要,不要用数字或较奇怪的字符来定义标识符。

示例:如下命名,使人产生疑惑。

 

#define _EXAMPLE_0_TEST_

 

#define _EXAMPLE_1_TEST_

 

void set_sls00( BYTE sls);

 

 

应改为有意义的单词命名

 

#define _EXAMPLE_UNIT_TEST_

 

#define _EXAMPLE_ASSERT_TEST_

 

void set_udt_msg_sls( BYTE sls );

 

4-4:在同一软件产品内,应规划好接口部分标识符(变量、结构、函数及常量)的命名,防 止编译、链接时产生冲突。

说明:对接口部分的标识符应该有更严格限制,防止冲突。如可规定接口部分的变量与常 量之前加上“模块”标识等。

 

4-5:用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。

说明:下面是一些在软件中常用的反义词组。

add/remove

begin/end

create/destroy

insert/delete

first/last

get/release

increment/decrement

put/get

up/down

lock/unlock

min/max

next/previous

old/new

open/close

show/hide

示例:

 

int   min_sum;

int   max_sum;

 

int   add_user( BYTE *user_name );

int   delete_user( BYTE *user_name );

 

 

5可读性

 

5-1: 注意运算符的优先级,并用括号明确表达式的操作顺序,避免使用默认优先级。 说明:防止阅读程序时产生误解,防止因默认的优先级与设计思想不符而导致程序出错。 示例:下列语句中的表达式

 

word = (high << 8) | low    (1)

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

if ((a | b) < (c & d))      (3)

 

如果书写为

 

high << 8 | low

a | b && a & c

a | b < c & d

由于

 

high << 8 | low = ( high << 8) | low, a | b && a & c = (a | b) && (a & c), (1)(2)不会出错,但语句不易理解;

a | b < c & d = a | (b < c)  & d,(3)造成了判断条件出错。

 

5-2:源程序中关系较为紧密的代码应尽可能相邻。

说明:便于程序阅读和查找。

示例:以下代码布局不太合理。

 

rect.length = 10;

char_poi = str;

rect.width = 5;

 

若按如下形式书写,可能更清晰一些。

 

rect.length = 10;

rect.width = 5; // 矩形的长与宽关系较密切,放在一起。

 

char_poi = str;

 

5-3:不要使用难懂的技巧性很高的语句,除非很有必要时。 说明:高技巧语句不等于高效率的程序,实际上程序的效率关键在于算法。 示例:如下表达式,考虑不周就可能出问题,也较难理解。

 

* stat_poi ++ += 1;

* ++ stat_poi += 1;

 

 

应分别改为如下。

 

*stat_poi += 1;

stat_poi++;    // 此二语句功能相当于“  * stat_poi ++ += 1; ”

 

++ stat_poi;

*stat_poi += 1; // 此二语句功能相当于“  * ++ stat_poi += 1; ”

 

6、变量、结构

 

6-1:去掉没必要的公共变量。 说明:公共变量是增大模块间耦合的原因之一,故应减少没必要的公共变量以降低模块间 的耦合度。

 

6-2:当向公共变量传递数据时,要十分小心,防止赋与不合理的值或越界等现象发生。 说明:对公共变量赋值时,若有必要应进行合法性检查,以提高代码的可靠性、稳定性。

 

6-3:防止局部变量与公共变量同名。 说明:若使用了较好的命名规则,那么此问题可自动消除。

6-4:严禁使用未经初始化的变量作为右值。

说明:特别是在 C/C++中引用未经赋值的指针,经常会引起系统崩溃。

6-5:构造仅有一个模块或函数可以修改、创建,而其余有关模块或函数只访问的公共变量,防止多个不同模块或函数都可以修改、创建同一公共变量的现象。

说明:降低公共变量耦合度。

 

6-6:结构的功能要单一,是针对一种事务的抽象。

说明:设计结构时应力争使结构代表一种现实事务的抽象,而不是同时代表多种。结构中 的各元素应代表同一事务的不同侧面,而不应把描述没有关系或关系很弱的不同事务的元 素放到同一结构中。

示例:如下结构不太清晰、合理。

 

typedef struct STUDENT_STRU

{

unsigned char name[8]; /* student's name */

unsigned char age; /* student's age */

unsigned char sex; /* student's sex, as follows */

/* 0 - FEMALE; 1 - MALE */

unsigned char

teacher_name[8]; /* the student teacher's name */

unisgned char

teacher_sex; /* his teacher sex */

} STUDENT;

 

 

若改为如下,可能更合理些。

typedef struct TEACHER_STRU

{

unsigned char name[8]; /* teacher name */

unisgned char sex; /* teacher sex, as follows */

/* 0 - FEMALE; 1 - MALE */

} TEACHER;

 

 

typedef struct STUDENT_STRU

{

unsigned char name[8];   /* student's name */

unsigned char age; /* student's age */

unsigned char sex; /* student's sex, as follows */

 

/* 0 - FEMALE; 1 - MALE */

unsigned int   teacher_ind; /* his teacher index */

} STUDENT;

6-7:不同结构间的关系不要过于复杂。 说明:若两个结构间关系较复杂、密切,那么应合为一个结构。

示例:如下两个结构的构造不合理。

typedef struct PERSON_ONE_STRU

{

unsigned char name[8]; unsigned char addr[40]; unsigned char sex; unsigned char city[15];

} PERSON_ONE;

 

 

typedef struct PERSON_TWO_STRU

{

unsigned char name[8];

unsigned char age; unsigned char tel;

} PERSON_TWO;

由于两个结构都是描述同一事物的,那么不如合成一个结构。

 

typedef struct PERSON_STRU

{

unsigned char name[8];

unsigned char age;

unsigned char sex;

unsigned char addr[40];

unsigned char city[15];

 unsigned char tel;

} PERSON;

 

 

7 函数、过程、类

 

7-1:编写可重入函数时,若使用全局变量,则应通过关中断、信号量等手段 对其加以保护。

说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调 用此函数时,很有可能使有关全局变量变为不可知状态。

示例:假设 g_exam 是 int 型全局变量,函数 Squre_Exam 返回 g_exam 平方值。那么如下 函数不具有可重入性。

 

unsigned int example( int para )

{

unsigned int temp;

g_exam = para; // (**)

temp = Square_Exam( );

return temp;

}

 

 

此函数若被多个进程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使 g_exam 赋与另一个不同的 para 值,所以当控制重新回到“temp = Square_Exam( )” 后,计算出的 temp 很可能不是预想中的结果。此函数应如下改进。

 

unsigned int example( int para )

{

unsigned int temp;

[申请信号量操作]   

// 若申请不到“信号量”,说明另外的进程正处于

g_exam = para;    // 给 g_exam赋值并计算其平方过程中(即正在使用此 temp = Square_Exam( ); // 信号),本进程必须等待其释放信号后,                                     

 

[释放信号量操作]           //才可继     

// 续执行。若申请到信号,则可继续执行,但其

// 它进程必须等待本进程释放信号量后,才能再使

// 用本信号。

 

return temp;

 

}

 

 

7-2:防止将函数的参数作为工作变量。 说明:将函数的参数作为工作变量,有可能错误地改变参数内容,所以很危险。对必须改 变的参数,最好先用局部变量代之,最后再将该局部变量的内容赋给该参数。

示例:下函数的实现不太好。

void sum_data( unsigned int num, int *data, int *sum )

{

unsigned int count;

*sum = 0;

for (count = 0; count < num; count++)

{

*sum   += data[count]; // sum 成了工作变量,不太好。

}

}

 

 

若改为如下,则更好些。

 

void sum_data( unsigned int num, int *data, int *sum )

{

unsigned int count ;

int sum_temp;

sum_temp = 0;

for (count = 0; count < num; count ++)

{

sum_temp += data[count];

}

 

 

*sum = sum_temp;

}

 

 

7-3:函数的规模尽量限制在200行以内。

说明:不包括注释和空格行。

7-4:一个函数仅完成一件功能

7-5:为简单功能编写函数。 说明:虽然为仅用一两行就可完成的功能去编函数好象没有必要,但用函数可使功能明确 化,增加程序可读性,亦可方便维护、测试。

示例:如下语句的功能不很明显。

 

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);

 

 

7-6:不要设计多用途面面俱到的函数。

说明:多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难。

7-7:函数的功能应该是可以预测的,也就是只要输入数据相同就应产生同样的输出。

说明:带有内部“存储器”的函数的功能可能是不可预测的,因为它的输出可能取决于内 部存储器(如某标记)的状态。这样的函数既不易于理解又不利于测试和维护。在 C/C++ 语言中,函数的 static 局部变量是函数的内部存储器,有可能使函数的功能不可预测, 然而,当某函数的返回值为指针类型时,则必须是 STATIC 的局部变量的地址作为返回值, 若为 AUTO 类,则返回为错针。

示例:如下函数,其返回值(即功能)是不可预测的。

unsigned int integer_sum( unsigned int base )

{

unsigned int index;

//注意,是 static 类型的。若改为 auto 类型,则函数即变为可预测。

static unsigned int sum = 0;

for (index = 1; index <= base; index++)

{

sum += index;

}

return sum;

}

7-8:使用动宾词组为执行某操作的函数命名。如果是OOP方法,可以只有动词(名词是对 象本身)。

示例:参照如下方式命名函数。

void print_record( unsigned int rec_ind ) ;

int   input_record( void ) ;

unsigned char get_current_color( void ) ;

1)避免使用无意义或含义不清的动词为函数命名。

说明:避免用含义不清的动词如  process、handle 等为函数命名,因为这些动词并没有 说明要具体做什么。

2)函数的返回值要清楚、明了,让使用者不容易忽视错误情况。 说明:函数的每种出错返回值的意义要清晰、明了、准确,防止使用者误用、理解错误或 忽视错误返回码。

7-9:在调用函数填写参数时,应尽量减少没有必要的默认数据类型转换或强制数据类型转 换。

说明:因为数据类型转换或多或少存在危险。

7-10:避免函数中不必要语句,防止程序中的垃圾代码。

说明:程序中的垃圾代码不仅占用额外的空间,而且还常常影响程序的功能与性能,很可 能给程序的测试、维护等造成不必要的麻烦。

7-11:减少函数本身或函数间的递归调用。

说明:递归调用特别是函数间的递归调用(如 A->B->C->A),影响程序的可理解性;递 归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试有一定影响。故 除非为某些算法或功能的实现方便,应减少没必要的递归调用。

7-12:对于提供了返回值的函数,在引用时最好使用其返回值。

7-13:函数规定

1.函数定义必须明确声明返回类型。

2.函数定义必须将形参名列出

3.在C++中,输入参数以值传递的方式传递对象,应该采用const &方式来传递,提高效率。

4.Return语句 不可返回指向“栈内存”的“指针”或者“引用”

 

7-14:类的规范

类名和结构名一般以“L”(Lanmage首字母)开头,后接若干名词 。每个名词首字母大写,其它字母小写,如LPatientList。如果名词本身为缩写,可以全部大写,如LDicomImageIOD。

枚举类型名构造规则同类名。枚举值的名称前应加上枚举类型名的小写缩写(不包含L),如enum LColorType { ctRed, ctBlue }。RAD环境自动产生的类型名不必遵循这里的约定。

1) 类成员定义顺序:Public,protected,Private。每一部分实体的定义顺序:构造函数,析构函数,成员函数,其他。

2) 初始化成员变量时按照变量声明的顺序进行。

 

3) 如果类存在虚函数,析构函数也要定义为虚态。

     例如:

      Class A

{

       public

          A( int i1, int i2 ) : data1(i1),data1(i2);

          virtual~A();//虚态

          virtual int Foo ( void );

       private:

          int data1,data2;

          string *p;

}

4) 拥有指针成员的类必须重新定义拷贝函数和构造函数

   Class A

{

    public

        A( int i1, int i2 ) : data1(i1),data1(i2);

        virtual ~A();

         A(const A &a); //拷贝构造函数

         A & operate =(const A &a);  

         virtual int Foo ( void );

     private:

          int data1,data2;

          string *p;

}

5)构造函数,析构函数不能调用虚函数

6)类的函数注意事项

   (1).至少定义一个构造函数

(2).不要在构造函数里面访问全局变量

(3).基类的析构函数定义为虚函数

(4).避免对字符和数字类型进行重载,禁止在模板类里面重载函数

(5).禁止重定义从父类继承的虚函数的缺省值

 

 


 

 

8可测性

 

8-1: 用断言确认函数的参数。 示例:假设某函数参数中有一个指针,那么使用指针前可对它检查,如下。

 

int exam_fun( unsigned char *str )

 

{

EXAM_ASSERT( str != NULL ); // 用断言检查“假设指针不为空”这个条件

 

 

... //other program code

 

}

8-2: 正式软件产品中应把断言及其它调测代码去掉(即把有关的调测开关关掉)。 说明:加快软件运行速度。

8-3: 在编写代码之前,应预先设计好程序调试与测试的方法和手段,并设计好各种调测开关 及相应测试代码如打印函数等。

说明:程序的调试与测试是软件生存周期中很重要的一个阶段,如何对软件进行较全面、 高率的测试并尽可能地找出软件中的错误就成为很关键的问题。因此在编写源代码之前, 除了要有一套比较完善的测试计划外,还应设计出一系列代码测试手段,为单元测试、集 成测试及系统联调提供方便。

8-4: 编写防错程序,然后在处理错误之后可用断言宣布发生错误。 示例:假如某模块收到通信链路上的消息,则应对消息的合法性进行检查,若消息类别不 是通信协议中规定的,则应进行出错处理,之后可用断言报告,如下例。

#ifdef _EXAM_ASSERT_TEST_ // 若使用断言测试

 

 

/* Notice: this function does not call 'abort' to exit program */

 

void assert_report( char * file_name, unsigned int line_no )

 

{

 

printf( "\n[EXAM]Error Report: %s, line %u\n", file_name, line_no );

}

 

 

#define   ASSERT_REPORT( condition )

if ( condition ) // 若条件成立,则无动作

 

NULL;

else // 否则报告

 

assert_report (    FILE   , __LINE   )

#else // 若不使用断言测试

 

 

#define ASSERT_REPORT( condition )   NULL

 

 

#endif /* end of ASSERT */

 

 

int msg_handle( unsigned char msg_name, unsigned char * msg )

 

{

 

switch( msg_name )

 

{

 

case MSG_ONE:

... // 消息 MSG_ONE 处理

 

return MSG_HANDLE_SUCCESS;

 

 

... // 其它合法消息处理

 

 

default:

... // 消息出错处理

ASSERT_REPORT( FALSE );   // “合法”消息不成立,报告

 

return MSG_HANDLE_ERROR;

 

}

 

}

 

 

 

9 程序效率

 

 

9-1: 循环体内工作量最小化。 说明:应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从 而提高程序的时间效率。

示例:如下代码效率不高。

 

for (ind = 0; ind < MAX_ADD_NUMBER; ind++)

 

{

 

sum += ind;

 

back_sum = sum; /* backup sum */

 

}

 

 

语句“back_sum = sum;”完全可以放在 for 语句之后,如下。

for (ind = 0; ind < MAX_ADD_NUMBER; ind++)

{

sum += ind;

}

back_sum    = sum; /* backup sum */

 

 

 

9-2: 仔细分析有关算法,并进行优化

 

9-3: 仔细考查、分析系统及模块处理输入(如事务、消息等)的方式,并加以改进。

 

9-4: 编程时,要随时留心代码效率;优化代码时,要考虑周全。

 

9-5: 不应花过多的时间拼命地提高调用不很频繁的函数代码效率。

说明:对代码优化可提高效率,但若考虑不周很有可能引起严重后果。

9-6: 要仔细地构造或直接用汇编编写调用频繁或性能要求极高的函数。 说明:只有对编译系统产生机器码的方式以及硬件系统较为熟悉时,才可使用汇编嵌入方 式。嵌入汇编可提高时间及空间效率,但也存在一定风险。

9-7: 在保证程序质量的前提下,通过压缩代码量、去掉不必要代码以及减少不必要的局部和 全局变量,来提高空间效率。

说明:这种方式对提高空间效率可起到一定作用,但往往不能解决根本问题。

9-8: 在多重循环中,应将最忙的循环放在最内层。 说明:减少 CPU 切入循环层的次数。

示例:如下代码效率不高。

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

{

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

{

sum += a[row][col];

}

}

 

 

可以改为如下方式,以提高效率。

 

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

 

{

 

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

 

{

 

sum += a[row][col];

 

}

 

}

9-9: 尽量减少循环嵌套层次。

9-10: 避免循环体内含判断语句,应将循环语句置于判断语句的代码块之中。 说明:目的是减少判断次数。循环体中的判断语句是否可以移到循环体外,要视程序的具 体情况而言,一般情况,与循环变量无关的判断语句可以移到循环体外,而有关的则不可 以。

示例:如下代码效率稍低。

 

for (ind = 0; ind < MAX_RECT_NUMBER; ind++)

 

{

 

if (data_type == RECT_AREA)

 

{

 

area_sum += rect_area[ind];

 

}

 

else

 

{

 

rect_length_sum += rect[ind].length;

 

rect_width_sum += rect[ind].width;

 

}

 

}

因为判断语句与循环变量无关,故可如下改进,以减少判断次数。

 

if (data_type == RECT_AREA)

 

{

 

for (ind = 0; ind < MAX_RECT_NUMBER; ind++)

 

{

 

area_sum += rect_area[ind];

 

}

 

}

 

else

 

{

 

for (ind = 0; ind < MAX_RECT_NUMBER; ind++)

 

{

 

rect_length_sum += rect[ind].length;

 

rect_width_sum += rect[ind].width;

 

}

}

9-11: 尽量用乘法或其它方法代替除法,特别是浮点运算中的除法。 说明:浮点运算除法要占用较多  CPU 资源。

示例:如下表达式运算可能要占较多 CPU 资源。

 

#define PAI 3.1416

 

radius = circle_length / (2 * PAI);

 

 

应如下把浮点除法改为浮点乘法。

#define PAI_RECIPROCAL (1 / 3.1416 ) // 编译器编译时,将生成具体浮点数

 

radius = circle_length * PAI_RECIPROCAL / 2;

  9-12:内存管理

  1. 为动态内存或者数据赋初值。
  2. 用malloc或者new分配内存后应立即检查指针是否为NULL。

例:

char *p = NULL;

p = ( char* )malloc( sizeof( char ) * 100 );

if ( NULL == p )

{

               exit( 1 );

}

strcpy( p, ”hello world!” );

free( p );

 

  1.   用free或者delete释放内存后应立即将指针设置为NULL。

例:

char *p = NULL;

p = ( char* )malloc( sizeof( char ) * 100 );

if ( NULL == p )

{

               exit( 1 );

}

strcpy( p, ”hello world!” );

free( p );

p = NULL;

  1.   如果函数的参数是一个指针,禁止用该指针去申请动态内存。

 

  1.  动态内存的申请与释放必须配对,防止内存泄漏,在C++代码中,使用new和delete,而不是malloc和free。重新定义new操作的时候必须重新定义delete操作。new和delete操作的格式保持一致。                        

 

10质量保证

10-1:代码质量保证优先原则

(1)正确性,指程序要实现设计要求的功能。

(2)稳定性、安全性,指程序稳定、可靠、安全。

(3)可测试性,指程序要具有良好的可测试性。

(4)规范/可读性,指程序书写风格、命名规则等要符合规范。

(5)全局效率,指软件系统的整体效率。

(6)局部效率,指某个模块/子模块/函数的本身效率。

(7)个人表达方式/个人方便性,指个人编程习惯

10-2:防止引用已经释放的内存空间。 说明:在实际编程过程中,稍不留心就会出现在一个模块中释放了某个内存块(如 C 语言 指针),而另一模块在随后的某个时刻又使用了它。要防止这种情况发生。

10-3:过程/函数中分配的内存,在过程/函数退出之前要释放。

10-4:过程/函数中申请的(为打开文件而使用的)文件句柄,在过程/函数退出之前要关闭。

 说明:分配的内存不释放以及文件句柄不关闭,是较常见的错误,而且稍不注意就有可能 发生。这类错误往往会引起很严重后果,且难以定位。 示例:下函数在退出之前,没有把分配的内存释放。

 

typedef unsigned char BYTE;

 

 

int example_fun( BYTE gt_len, BYTE *gt_code )

 

{

 

BYTE *gt_buf;

 

 

gt_buf = (BYTE *) malloc (MAX_GT_LENGTH);

 

...   //program code, include check gt_buf if or not NULL.

/* global title length error */

 

if (gt_len > MAX_GT_LENGTH)

 

{

return GT_LENGTH_ERROR; // 忘了释放 gt_buf

 

}

 

 

...   // other program code

 

}

 

 

应改为如下。

 

int example_fun( BYTE gt_len, BYTE *gt_code )

 

{

 

BYTE *gt_buf;

 

 

gt_buf = (BYTE * ) malloc ( MAX_GT_LENGTH );

 

...   // program code, include check gt_buf if or not NULL.

 

 

/* global title length error */

 

if (gt_len > MAX_GT_LENGTH)

 

{

free( gt_buf   ); // 退出之前释放 gt_buf =NULL;

return GT_LENGTH_ERROR;

}

 

 

...   // other program code

 

}

 

10-5:防止内存操作越界。

说明:内存操作主要是指对数组、指针、内存地址等的操作。内存操作越界是软件系统主 要错误之一,后果往往非常严重,所以当我们进行这些操作时一定要仔细小心。 示例:假设某软件系统最多可由  10 个用户同时使用,用户号为  1-10,那么如下程序存在 问题。

 

#define MAX_USR_NUM 10

 

unsigned char usr_login_flg[MAX_USR_NUM]= "";

void set_usr_login_flg( unsigned char usr_no )

 

{

 

if (!usr_login_flg[usr_no])

 

{

 

usr_login_flg[usr_no]= TRUE;

 

}

 

}

 

 

当 usr_no 为 10 时,将使用 usr_login_flg 越界。可采用如下方式解决。

 

void set_usr_login_flg( unsigned char usr_no )

 

{

 

if (!usr_login_flg[usr_no - 1])

 

{

 

usr_login_flg[usr_no - 1]= TRUE;

 

}

 

}

10-6:系统运行之初,要初始化有关变量及运行环境,防止未经初始化的变量被引用。

10-7:严禁随意更改其它模块或系统的有关设置和配置。 说明:编程时,不能随心所欲地更改不属于自己模块的有关设置如常量、数组的大小等。

 

10-8:编程时,要防止差1错误。 说明:此类错误一般是由于把“<=”误写成“<”或“>=”误写成“>”等造成的,由此 引起的后果,很多情况下是很严重的,所以编程时,一定要在这些地方小心。当编完程序 后,应对这些操作符进行彻底检查。

 

10-9:要时刻注意易混淆的操作符。当编完程序后,应从头至尾检查一遍这些操作符,以防 止拼写错误。

说明:形式相近的操作符最容易引起误用,如 C/C++中的“=”与“==”、|”与“||、 “&”与“&&”等,若拼写错了,编译器不一定能够检查出来。 示例:如把“&”写成“&&,或反之。

 

ret_flg = (pmsg->ret_flg & RETURN_MASK);

被写为:

 

ret_flg = (pmsg->ret_flg && RETURN_MASK);

 

 

rpt_flg = (VALID_TASK_NO( taskno ) && DATA_NOT_ZERO( stat_data ));

被写为:

 

rpt_flg = (VALID_TASK_NO( taskno ) & DATA_NOT_ZERO( stat_data ));

10-10: 有可能的话,if语句尽量加上else分支,对没有else分支的语句要小心对待;switch语句必须有default分支。

10-11: 不要滥用goto语句。

说明:goto 语句会破坏程序的结构性,所以除非确实需要,最好不使用 goto 语句。

10-12: 精心构造算法,并对其性能、效率进行测试。

10-13: 对较关键的算法最好使用其它算法来确认。

10-14: 时刻注意表达式是否会上溢、下溢。

示例:如下程序将造成变量下溢。

 

unsigned char size ;

while (size-- >= 0) // 将出现下溢

 

{

 

... // program code

 

}

 

 

当 size 等于 0 时,再减 1 不会小于 0,而是 0xFF,故程序是一个死循环。应如下修改。

char size; // 从 unsigned char 改为 char while (size-- >= 0)

{

 

... // program code

 

}

10-15: 使用变量时要注意其边界值的情况。

示例:如 C 语言中字符型变量,有效值范围为-128 到 127。故以下表达式的计算存在一

定风险。

 

char chr = 127;

 

int sum = 200;

 

 

chr += 1; // 127 为 chr 的边界值,再加 1 将使 chr 上溢到-128,而不是 128。

sum += chr; // 故 sum 的结果不是 328,而是 72。

 

 

若 chr 与 sum 为同一种类型,或表达式按如下方式书写,可能会好些。

 

sum = sum + chr + 1;

10-16: 系统应具有一定的容错能力,对一些错误事件(如用户误操作等)能进行自动补救。

10-17: 对一些具有危险性的操作代码(如写硬盘、删数据等)要仔细考虑,防止对数据、硬 件等的安全构成危害,以提高系统的安全性。

10-18: 使用第三方提供的软件开发工具包或控件时,要注意以下几点:

(1)充分了解应用接口、使用环境及使用时注意事项。

(2)不能过分相信其正确性。

(3)除非必要,不要使用不熟悉的第三方工具包与控件。

说明:使用工具包与控件,可加快程序开发速度,节省时间,但使用之前一定对它有较充 分的了解,同时第三方工具包与控件也有可能存在问题。

10-19: 资源文件(多语言版本支持),如果资源是对语言敏感的,应让该资源与源代码文件 脱离,具体方法有下面几种:使用单独的资源文件、DLL文件或其它单独的描述文件(如数据 库格式)

 

11 代码编辑、编译,审查

11-1:打开编译器的所有告警开关对程序进行编译。

11-2:在产品软件(项目组)中,要统一编译开关选项

11-3:通过代码走读及审查方式对代码进行检查。 说明:代码走读主要是对程序的编程风格如注释、命名等以及编程时易出错的内容进行检 查,可由开发人员自己或开发人员交叉的方式进行;代码审查主要是对程序实现的功能及及程序的稳定性、安全性、可靠性等进行检查及评审,可通过自审、交叉审核或指定部门抽查等方式进行。

11-4:测试部测试产品之前,应对代码进行抽查及评审。

11-5:编写代码时要注意随时保存,并定期备份,防止由于断电、硬盘损坏等原因造成代码 丢失。

11-6:合理地设计软件系统目录,方便开发人员使用。 说明:方便、合理的软件系统目录,可提高工作效率。目录构造的原则是方便有关源程序 的存储、查询、编译、链接等工作,同时目录中还应具有工作目录----所有的编译、链 接等工作应在此目录中进行,工具目录----有关文件编辑器、文件查找等工具可存放在 此目录中。

11-7:某些语句经编译后产生告警,但如果你认为它是正确的,那么应通过某种手段去掉告警信息。

说明:在 Borland C/C++中,可用“#pragma warn”来关掉或打开某些告警。 示例:

#pragma warn -rvl // 关闭告警

 

int examples_fun( void )

 

{

// 程序,但无 return 语句。

 

}

#pragma warn +rvl // 打开告警

编译函数 examples_fun 时本应产生“函数应有返回值”告警,但由于关掉了此告警信 息显示,所以编译时将不会产生此告警提示。

 

 

12 代码测试、维护

 

12-1:单元测试要求至少达到语句覆盖。

 

12-2:单元测试开始要跟踪每一条语句,并观察数据流及变量的变化。

 

12-3:清理、整理或优化后的代码要经过审查及测试。

 

12-4:代码版本升级要经过严格测试。

 

12-5:使用工具软件对代码版本进行维护。

 

12-6:正式版本上软件的任何修改都应有详细的文档记录。

 

12-7:发现错误立即修改,并且要记录下来。

 

12-8:仔细设计并分析测试用例,使测试用例覆盖尽可能多的情况,以提高测试用例的效率。

 

12-9:尽可能模拟出程序的各种出错情况,对出错处理代码进行充分的测试。

 

12-10:仔细测试代码处理数据、变量的边界情况。

 

12-11:保留测试信息,以便分析、总结经验及进行更充分的测试。

 

12-12:不应通过“试”来解决问题,应寻找问题的根本原因。

 

12-13:对自动消失的错误进行分析,搞清楚错误是如何消失的。

 

12-14:修改错误不仅要治表,更要治本。

 

12-15:测试时应设法使很少发生的事件经常发生。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/xpj8888/article/details/85261199