技 术 文 件
技术文件名称:编码规范_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:内存管理
- 为动态内存或者数据赋初值。
- 用malloc或者new分配内存后应立即检查指针是否为NULL。
例:
char *p = NULL;
p = ( char* )malloc( sizeof( char ) * 100 );
if ( NULL == p )
{
exit( 1 );
}
strcpy( p, ”hello world!” );
free( p );
- 用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;
- 如果函数的参数是一个指针,禁止用该指针去申请动态内存。
- 动态内存的申请与释放必须配对,防止内存泄漏,在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:测试时应设法使很少发生的事件经常发生。