1 排版
排版可以使程序结构清晰,观者心怡,对于增强程序的可读性和可维护性起着至关重要的作用,因此程序员应该注意排版,这也会给以后的工作(修改、维护程序的工作)带来极大的方便。排版需要注意的内容如下:
1-1 缩进
规则 1-1-1 程序块采用缩进风格编写,缩进为4个空格键位(通常默认为行末的一个回车键位)。
规则 1-1-2 函数或过程的开始,类、结构、联合、枚举的定义以及循环、判断、分支选择等语句中的代码都要采用缩进风格。
程序示例
void main ()
{
cout << "Hello World!" << endl; // 函数的开始部分采用缩进风格
// 编写的代码,规则 1-1-1 ,1-1-2
}
1-2 空行
规则 1-2-1 在函数、类、结构、枚举等相对独立的程序块声明结束后,必须加空行以示强调。
程序示例
class Car
{
...
}
// 这一空行表示Car类定义的结束,规则 1-2-1
class Bike
{
......
}
// 这一空行表示Bike类定义的结束,规则 1-2-1
规则 1-2-2 在变量声明结束后必须加空行以示强调。
规则 1-2-3 在同一个函数体内,逻辑上相关的语句之间不加空行,其它地方应加空行以示区别。
规则 1-2-4 将注释与其上面的代码之间用空行隔开。
程序示例
#include <iostream.h>
// 这一空行将注释与其上面的代码隔开,规则 1-2-4
/*以下为主函数,……*/
int main()
{
char cName;
int iAge;
// 这一空行表示变量声明结束,规则 1-2-2
if (age > 18)
{
......
}
// 这一空行表示上下两段语句在逻辑上不相关,规则 1-2-3
return 0;
}
1-3 空格
规则 1-3-1 程序行中逗号和分号的前面不留空格,后面留空格。
规则 1-3-2 关键字的两边都需要留空格,关键字位于行首时关键字前不需要留空格。
规则 1-3-3 函数名之后不要留空格,紧跟左括号"(",与关键字区别。
规则 1-3-4 比较操作符,例如">=",赋值操作符,例如"="、 "+=",算术操作符,例如"+"、"%",逻辑操作符,例如"&&"、"&",位域操作符,例如"<<"、"^"等二元操作符的前后加空格。
规则 1-3-5 "!"、"~"、"++"、"--"、"&"(地址运算符)等一元操作符前后不加空格。
规则 1-3-6 "[]"、"."、"->"前后不加空格。
规则 1-3-7 单行注释语句在"//"后空一格再开始书写,"//"与前面的语句至少距离一个空格。
说明 1-3-1 在较长的语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连续留两个以上空格。对于表达式比较长的for语句和if语句,为了紧凑起见可以适当地去掉一些空格。
说明 1-3-2 由于留空格所产生的清晰性是相对的,所以,在已经非常清晰的语句中没有必要再留空格,如果语句已足够清晰则括号内侧(即左括号后面和右括号前面)不需要加空格,多重括号间不必加空格,因为括号已经是很清晰的标志了。
程序示例
#include <iostream.h> // 关键字"include"后留空格,规则 1-3-2
// "<"后和">"前不留空格,说明 1-3-2
int main() // 函数名"main"后不留空格,规则 1-3-3
{
int aArray[5] = {1, 2, 3, 4, 5}; // 逗号前面不留空格,后面留空格,规则 1-3-1
// "[]"前后不加空格,规则 1-3-6
// "="前后加空格,规则 1-3-4
for (int i=0; i<5; i++) // 为紧凑起见,省去for语句中的一些空格,说明 1-3-1
// 一元操作符"++"前后不加空格,规则 1-3-5
{
cout << aArray[i] - 1 << endl; // 位域操作符"<<"的前后加空格,规则 1-3-4
}
return 0;
}
1-4 对齐
规则 1-4-1 程序块的分界符 "{" 和 "}" 应各独占一行并且位于同一列,同时与引用它们的语句左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以及if、for、do、while、switch、case语句中的程序都要采用如上的对齐方式。
规则 1-4-2 变量的定义通过添加空格形成对齐,同一类型的变量最好放在一起。
规则 1-4-3 switch语句中所有的Case需要对齐,break需要缩进。
规则 1-4-4 if语句中的成对的if…else要对齐
注:规则 1-4-1 到1-4-4的程序示例见下一小节"1-5 语句"
说明 1-4-1 do…while语句中的while语句在do语句结束的 "}" 后空一格开始书写,无需和do对齐。
程序示例
do
{
……
} while ();
技巧 1-4-1 在Visual C++中选中要排版的程序段落,然后按下Alt+F8键即为系统自动对齐功能。
1-5 语句
规则 1-5-1 单行语句以小于80字符为宜,不要写得过长,较长的语句(>80字符)要分成多行书写,书写时要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读性好。
程序示例
for (i = 0, j = 0;
(i < ifirstWord) && (j < isecondWord);
i++, j++)
{
……
}
规则 1-5-2 if、for、do、while、case、switch、default等语句自占一行,且各自的执行语句部分无论多少都要加括号"{}",如果 for、if 等语句如果分成多行书写,其后的执行语句必须以"{}"括起。
规则 1-5-3 不允许把多个短语句写在一行中,每一行只准书写一条语句。
程序示例
#include <iostream.h>
int main()
{
int iAge = 18;
char cSex = 'M'; // 定义变量对齐,规则 1-4-2
if (iAge >= 18) // if语句独占一行,规则 1-5-2
else // else与if对齐,规则 1-4-4
{
cout << "child" << endl;
}
switch (cSex)
{
case 'M':
{
cout << "BOY" << endl;
}
break; // break缩进,规则 1-4-3
case 'F':
{
cout << "GIRL" << endl;
}
break; // 一行只准书写一条语句,规则 1-5-3
default:
{
cout << "I don't know your sex" << endl;
}
break;
}
return 0;
}
1-6 类
之所以把类拿到这里来单独讨论,是因为类的版式主要有两种方式:一是将private类型的数据写在前面,而将public类型的函数写在后面,
代码示例A
class A
{
private:
int m_iV1;
float m_fV2;
…
public:
void Func1(void);
void Func2(void);
…
}
采用这种版式的程序员主张类的设计“以数据为中心”,重点关注类的内部结构。
还有一种是将public类型的函数写在前面,而将private类型的数据写在后面
代码示例B
class B
{
public:
void Func1(void);
void Func2(void);
…
private:
int m_iV1;
float m_fV2;
…
}
采用这种版式的程序员主张类的设计“以行为为中心”,重点关注的是类应该提供什么样的接口(或服务)。
在这里建议采用“以行为为中心”(代码示例B)的书写方式,即首先考虑类应该提供什么样的函数,这通常也是我们接触到一个类时最关心的问题。
2 注释
注释的主要作用是帮助理解程序,注释语言必须含义准确、简单明了,一定要防止注释的二义性。注释的数量不宜太多或者太少,一般情况下,源程序有效注释量必须在源程序的20%以上,推荐在20%——30%之间。总的来说,注释首先要遵循以下的一些原则。
注释原则1尽量避免在注释中使用缩写,特别是不常用缩写。
注释原则2不要在一行代码或表达式的中间插入注释。
注释原则3注释应与其描述的代码相近,对单条语句的注释要放在其上方或右方相邻的位置,不可放在语句下方,并使用"///>"进行注释;对多行语句或程序块的注释要放在其上方相邻的位置,同样不可放在语句下方,并使用"/**"和"*/"进行注释。
注释原则4一定要边写代码边写注释,修改代码的同时也要修改相应的注释,以保证注释与代码的一致性。不再有用的注释要及时删除。
此外,出于对维护人员的考虑,建议使用中文进行注释。
2-1 常、变量
规则 2-1-1 对于有物理含义的常量、变量,如果其命名不是充分自注释的(即通过名字不能直观的判断出该常量、变量代表的物理含义),在声明时都必须加以注释,说明其物理含义。
代码示例
#define MAX_ACT_TASK_NUMBER 1000;///> 活动任务的统计数值,规则 2-1-1
规则 2-1-2 全局变量要有较详细的注释,包括对其功能、取值范围、哪些函数或过程存取它以及存取时注意事项等的说明。
代码示例
/**
@brief 用来表示数据传输中的状态的,取值范围为0, 1, 2
0-表示传输成功 1-表示传输失败 2-表示没有数据传输
通过函数SCCPTranslate()可以修改它的状态,
函数GetGTTransErrorCode()仅可以对它进行只读访问
*/
int g_GTTranErrorCode;
规则 2-1-3 在对指针变量、特别是比较复杂的指针变量声明时,应对其含义、作用及使用范围进行注释说明,如有必要,还应说明其使用方法、注意事项等。
2-2 代码
规则 2-2-1 对重要代码段的功能、意图进行注释,提供有用的、额外的信息。并在该代码段的结束处加一行注释表示该段代码结束。
代码示例
/**
@brief 对输入的年份是否是闰年进行判断,如果是闰年,输出"闰年"
字样,如果不是闰年,则输出还差几年才到下一个闰年
@param iYear 年份
@retval 1=闰年
@retval 0=非闰年
*******************************************************/
int LeapYear( int iYear )
{
...
if ()
{
...
} ///> 判断是否是闰年结束
...
}
规则 2-2-2 对于比较复杂的选择、分支、循环语句,必须编写较为详细的注释,因为这些语句往往是程序实现某一特定功能的关键。
2-3 数据结构
规则 2-3-1 对于数据结构(包括数组、结构、类、枚举等)来说,如果其命名不是充分自注释的,必须加以注释。对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的单个成员变量或者成员函数的注释放在其右方相邻位置。
代码示例
/**
@brief 本数据结构的说明
struct ADDR
{
char m_cName[4];
long m_lTel;
char m_cStreet[30];
char m_aCity[20];
char m_aCountry[30];
long m_lPostcode; // 邮政编码,规则 2-3-1
};
2-4 函数
规则 2-4-1 在函数头部要对函数进行注释,需要列出:函数的目的、功能,输入参数,输出参数,返回值,调用函数,被调用函数,修改日志等相关信息,对一些复杂的函数,在注释中最好提供典型用法。
函数注释参考形式如下:
/**
@brief // 函数功能、性能的描述
// 调用哪些函数
@param 参数列表
@param
….. 多个参数
@retval 返回值
修改日志:
备 注: // 其它有用的信息
*/
参见2-2代码示例
2-5 头文件、源文件
规则 2-4-2 类似于头文件、源文件之类的说明性文件,必须提供详细的注释,内容包括:版权说明、版本号、生成日期、作者、内容、功能、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。
说明性文件注释参考形式如下:
/*!
版权声明:
@file: 文 件 名
文件信息: // 用于详细说明此程序文件完成的主要功能,与其他模块
// 或函数的接口,输出值、取值范围、含义及参数间的控
// 制、顺序、独立或依赖等关系
@author: 作者
@version 版本
@date 完成日期:
备 注: // 其它内容的说明,例如特殊缩写与约定
函数列表: // 主要函数列表
函数名称 功能描述
1. ..... ....
2. ..... ....
修改历史:
作者 修改时间 版本 修改内容
... ... ... .....
*/
3 命名规则
为变量、函数、数据结构等命名时,最基本的要求有以下几个:
基本要求3-1命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。
基本要求3-2命名中若使用特殊约定或缩写,则要有注释说明,通常放在源文件开始的地方。
基本要求3-3除非必要,否则禁止使用数字或较奇怪的字符来定义标识符。
以下规定的各项命名规则仅适用于基于Windows平台的开发过程,对于Unix平台下的命名规则暂时不予考虑。
3-1 常、变量与参数
规则 3-1-1 变量和参数用大写字母开头的单词组合而成,其后字母的大、小写不做要求。尽量使用“名词”或者“形容词+名词”的形式,每一行只准声明一个变量。
代码示例
float fValue;
float fOldValue;
float fNewValue;
规则 3-1-2 i、j、k等单个字符仅可以用作局部循环变量。
建议 3-1-1 对于变量命名,除了要有具体含义外,最好还能表明其变量类型、数据类型等,因此建议采取以下的命名方法,可称之为前后缀命名法。
步骤1确定变量作用的范围,如果是类或者结构的成员变量,一律标记为小写的m_(member);如果是全局变量,则一律标记为小写的g_(global);如果是静态变更,则一律标记为小写的s_(static)。其它变量暂无规定标记;
步骤2确定变量的数据类型,常用数据类型对应的字母如下:
int-------------i
long-----------l
char-----------c
指针类型----p
数组----------a
……
步骤3为变量起名,参见规则 3-1-1 、基本要求3-1、3-2、3-3;
请看下面的几个例子:
变量命名举例
m_cName…………类成员、字符型、变量Name
iRate_s …………整型、静态变量Rate
g_iPassline ……….全局、整型、变量Passline
虽然以上的命名方法很显繁琐,但可以避免许多因为变量命名不当引起的程序错误(例如全局变量与局部变量重名)。
规则 3-1-3 宏和常量全用大写的字母,以下划线来分割各单词。
代码示例
const int MAX_LENGTH = 100;
3-2 函数
规则 3-2-1 函数名用大写字母开头的单词组合而成,其中每个单词的开头字母均大写。函数名必须以一个动词开头。函数名最好能做到让人仅从函数名上就可以大概判断出该函数的作用。
代码示例
void PerformSelfTest(void) ;
规则 3-2-2 用正确的反义词组命名具有互斥意义或者相反动作的函数。下面列出一些在软件中常用的反义词组。
add / remove begin / end create / destroy
insert / delete first / last get / release
add / delete lock / unlock open / close
min / max old / new start / stop
next / previous source / target show / hide
send / receive put / get up / down
cut / paste increment / decrement
source / destination
3-3 类
规则 3-3-1 类名用大写字母开头的单词组合而成,其中每个单词的开头字母均大写,单词之间不用下划线。类名中只可以包含名词和形容词。
代码示例
class MyClass;
3-4 结构
规则 3-4-1 结构名各单词的字母均为大写,单词间用下划线连接,使用该结构定义的结构变量名命名规则与之相同。结构成员的命名同变量的命名规则。
代码示例
struct LOCAL_SPC_TABLE_STRU
{
char m_cValid;
int m_iNspccode[max];
} LOCAL_SPC_TABLE ;
3-5 枚举
规则 3-5-1 枚举名以小写字母"e"开头,其后单词的字母均为大写,单词间用下划线隔开;要求各成员的第一个单词相同,这样易于识别。
代码示例
enum
{
eLAPD_ MDL_ASSIGN_REQ,
eLAPD_MDL_ASSIGN_IND,
eLAPD_DL_DATA_REQ,
eLAPD_DL_DATA_IND,
eLAPD_DL_UNIT_DATA_REQ,
eLAPD_DL_UNIT_DATA_IND,
} LAPD_PRMV_TYPE;
4 循环语句与执行效率
首先强调,任何语句的嵌套层次不得超过5层。嵌套层次太多,增加了代码的复杂度及测试的难度,容易出错,增加代码维护的难度。
4-1 for语句
规则 4-1-1 不可在for 循环体内修改循环变量,防止for 循环失去控制。
规则 4-1-2 for语句的循环控制变量的取值采用“半开半闭区间”写法,即假设我们有一个变量i要循环5次,则我们要求采取以下的for语句写法:
for (int i=0; i<5; i++)
而不要采取以下的写法:
for (int i=0; i<=4; i++)
很显然,第一种写法更接近我们最初的意识,也更直观。
规则 4-1-3 循环体内工作量最小化,将最忙的循环放在最外层,减少CPU切入循环的次数,可有效的提高循环体的执行效率。
代码示例
代码1,低效代码 for (row = 0; row < 100; row++) { for (col = 0; col < 5; col++) { sum += a[row][col]; } } | 代码2,高效代码 for (col = 0; col < 5; col++) { for (row = 0; row < 100; row++) { sum += a[row][col]; } } |
规则 4-1-4 避免循环体内含判断语句,应将循环语句置于判断语句的代码块之中,这样做的目的是减少判断次数。循环体中的判断语句是否可以移到循环体外,要视程序的具体情况而言,一般情况,与循环变量无关的判断语句可以移到循环体外,而有关的则不可以。
4-2 goto语句
在实现中,尽量避免 goto 语句的使用。并且禁止使用 goto 由外部向控制语句内部跳转。
5 函数
对于每一个函数建议尽可能控制其代码长度为50行左右,超过50行太多的代码要重新考虑将其拆分为两个或两个以上的函数。函数拆分规则应该以不破坏原有算法为基础,同时拆分出来的部分应该是可以重复利用的。每个函数只完成单一的功能,不设计多用途面面俱到的函数,这样将使函数的理解、测试、维护等工作变得困难。
5-1 参数
规则 5-1-1 避免函数有太多的参数,参数个数尽量控制在5个以内,并且参数排列顺序要合理。这样可以有效地减少接口的复杂度,另外,太多的参数,在使用时也容易将参数类型或顺序搞错。
规则 5-1-2 参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。
代码示例
void SetValue(int iWidth, int iHeight);
void SetValue(int, int); // 违反规定的书写方法
float GetValue(void);
float GetValue();
规则 5-1-3 声明函数原形时,用 IN/OUT 指示参数以及缓冲区的性质。
规则 5-1-4 在函数体的“入口处”,对参数的有效性、合法性与一致性进行检查。我们明确规定,对接口函数参数的检查应由函数的调用者来负责。同样,对于函数的所有的非参数输入的有效性,如数据文件、公共变量等,同样要进行有效性、合法性和一致性的检查。
规则 5-1-5 尽量避免强制类型转换,除非确认转换无误,因为太多的错误出现在非法的强制类型转换上,而且这种错误很难查错纠正。
5-2 返回值
规则 5-2-1 不要省略返回值的类型。无返回类型,则必须指明为void类型。
规则 5-2-2 不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用return语句返回。
规则 5-2-3 对所调用函数的错误返回码的处理要仔细、全面。
规则 5-2-4 对于提供了返回值的函数,在引用时最好使用其返回值。
5-3 函数执行效率
规则 5-3-1 减少函数本身或函数间的递归调用。
规则 5-3-2 即使是简单的功能也应该编写函数来完成。
规则 5-3-3 防止把没有关联的语句放到一个函数中。
说明:这样做的目的是防止函数内出现随机内聚。随机内聚是指将没有关联或关联很弱的语句放到同一个函数中。随机内聚给函数的维护、测试及以后的升级等造成了不便,同时也使函数的功能不明确。使用随机内聚函数,常常容易出现在一种应用场合需要改进此函数,而另一种应用场合又不允许这种改进,从而陷入困境。
在编程时,经常遇到在不同函数中使用相同的代码,许多开发人员都愿把这些代码提出来,并构成一个新函数。若这些代码关联较大并且是完成一个功能的,那么这种构造是合理的,否则这种构造将极有可能产生随机内聚的函数。
规则 5-3-4 尽量降低函数、模块间的耦合,以利于功能扩充、代码复用;
规则 5-3-5 较小的函数,特别是仅有一个上级函数调用它时,应考虑把它合并到上级函数中,而不必单独存在。
6 代码的编译与审查
规则6-1打开编译器的所有告警开关对程序进行编译。
规则6-2通过代码走读及审查方式对代码进行检查。
说明:代码走读主要是对程序的编程风格如注释、命名等以及编程时易出错的内容进行检查,可由开发人员自己或开发人员交叉的方式进行;代码审查主要是对程序实现的功能及程序的稳定性、安全性、可靠性等进行检查及评审,可通过自审、交叉审核或指定部门抽查等方式进行。
规则6-3编写代码时要注意随时保存,并定期备份,防止由于断电、硬盘损坏等原因造成代码丢失。
规则6-4同产品软件(项目组)内,最好使用相同的编辑器,并使用相同的设置选项。
规则6-5使用BoundChecker/ErrorDetect等工具检查内存泄露/内存越界。
规则6-6除非实在由于编译器的原因无法消除某些Warning,否则坚决杜绝编译期间的Warning;执行适当的类型转换以消除类型不匹配Warning,但是转换时要小心。
7 特别关注
以下陈述的或者是编程时经常会出现的问题,或者是对程序影响比较严重的问题,总之在书写程序时,需要引起对它们的特别关注。
7-1 =和==
一个是用来比较的(= =),一个是用来赋值的(=),但是二者却经常被程序员用错地方,不管是新手还是老手,都会不约而同的犯这个拼写错误,究其根源还是我们在学习数学的时候以及脑海里树立了牢固的信念,"="代表等于。对于这个问题,没有什么好的办法,只能提醒大家多加小心了。
另外容易混淆的还有"|"与"||"、"&"与"&&"等,拼写错误很多情况下都会引起很严重的错误,所以编程时,一定要在这些地方小心。当编完程序后,应对这些操作符进行彻底检查。
7-2 运算符的优先级
C++/C语言的运算符有数十个,运算符的优先级与结合律如下表所示。
优先级 | 运算符 | 结合律 |
从
高
到
低
排
列 | ( ) [ ] -> . | 从左至右 |
! ~ ++ -- (类型) sizeof + - * & | 从右至左
| |
* / % | 从左至右 | |
+ - | 从左至右 | |
<< >> | 从左至右 | |
< <= > >= | 从左至右 | |
= = != | 从左至右 | |
& | 从左至右 | |
^ | 从左至右 | |
| | 从左至右 | |
&& | 从左至右 | |
|| | 从右至左 | |
?: | 从右至左 | |
= += -= *= /= %= &= ^= |= <<= >>= | 从左至右 |
规则 7-2-1 熟记这张表是比较困难的(我们也不主张大家这么做),为了防止产生歧义并提高可读性,应当用括号确定表达式的操作顺序,避免使用默认的优先级。
代码示例
word = (high << 8) | low;
if ((a | b) && (a & c))
7-3 内存
对内存进行操作时的必须注意,因为对内存操作的错误带给系统的打击将是毁灭性的,因此必须牢记以下的几点。
规则 7-3-1 结构/类的构造函数中初始化所有的成员变量;析构函数中释放所申请的内存。
规则 7-3-2 函数执行时申请分配的内存,在函数退出之前一定要释放,且释放后把内存指针清为 NULL。
规则 7-3-3 指针声明时初始化为NULL, 使用完随即释放,释放内存后立刻清为NULL。
规则 7-3-4 防止引用已经释放的内存空间。
说明:在实际编程过程中,稍不留心就会出现在一个模块中释放了某个内存块,而另一模块在随后的某个时刻又使用了它。要防止这种情况发生。
规则 7-3-5 防止内存操作越界。
说明:内存操作主要是指对数组、指针、内存地址等的操作。内存操作越界是软件系统主要错误之一,后果往往非常严重,所以当我们进行这些操作时一定要仔细小心。