代码规范(转载 2008.4)
1 引言
1.1 本文目的
随着越来越多的项目需要使用C++来编写,为了便于在编码过程之中,保持一致的风格,有利于软件工程项目的推行。本文所提汲的内容若不明确指出,皆为强制遵守的风格。
1.2 背景
在软件工程领域,源程序的风格统一标志着可维护性、可读性,是软件项目的一个重要组成部分。而目前还没有成文的编码风格文档,以致于很多时候,程序员没有一个共同的标准可以遵守,编码风格各异,程序可维护性差、可读性也很差。
目前在编码上也有许多相关的风格约定,包括匈牙利命名法(一种变量的取名办法)、类取名方式,但是内容大多比较少、零散。本规范从命名规则、程序版式等几个代码编写的主要环节出发,对C++代码的编写方法、风格作出了明确的说明,力求编写的产品代码都能形成一致的风格。
1.3 术语
系统:指一个软件工程项目,是一个系统;
项目:指一个Visual C++项目;
MFC:Microsoft Foundation Class Library
2 概述
每位程序员都有自己的编程风格,因为每位程序员都有自己的学习过程,就象每个人的个性一样,所以编程风格是风彩各异、百花齐放;而从软件工程理论、实践来看,现代软件是多人合作的结晶,编程风格是否统一,直接关系到软件项目的可读性、可维护性、培训,继而对软件开发成本有着直接的关系,编程风格一致,软件项目易培训,其它人员接手老项目的时间缩短,便于程序员之间的交流。编程风格混乱,则其它人员接手老项目时间增长,同时随着项目的不断开发,项目或者单个源程序文件内有着多种编程风格,这样不利于整个项目的开展以及程序员之间的交流。
本规范在参考业界已有的编码风格的基础上,描述了一个基于Visual C++编译器的项目风格,力求一种统一的编程风格,并从代码文件风格、函数编写风格、变量风格、注释风格几个方面进行阐述。
3 文件结构
3.1 头文件结构
头文件由三部分组成:
(1)、版权和版本声明。
(2)、预处理块。
(3)、函数和结构声明。
示例:
// ***************************************************************
// MyClass(模块名称) 版本: 2.6
// -------------------------------------------------------------
//
// -------------------------------------------------------------
// Copyright (C) 2008 - **科技有限公司
// ***************************************************************
// 当前版本:2.6
// 作 者:
// 完成日期:年月日
//
// 取代版本:2.2
// 原作者 :
// 完成日期:年月日
//
// ***************************************************************
#ifndef MyClass_h__ // 防止MyClass.h被重复引用
#define MyClass_h__
#pragma once
#include <math.h> // 引用标准库
#include "otherheader.h" // 引用非标准库
// 全局函数
void MyFunction1(bool bSet); // 全局函数声明
class CMyClass
{
public:
CMyClass(void);
// 设置年龄大小
void SetAge(int nAge);
// 获得年龄大小
int GetAge();
public:
~CMyClass(void);
private:
int m_Age;
};
#endif
注意事项:
(1)、头文件尽可能只存放“声明”,不存放“定义”。
(2)、头文件中尽量不使用全局变量。
3.2 文件结构
文件由三部分组成:
(1)、头文件引用。
(2)、程序实现体。
示例:
#include "MyClass.h"
// 全局函数
void MyFunction1(bool bSet)
{
……
}
CMyClass::CMyClass(void)
{
}
CMyClass::~CMyClass(void)
{
}
// 设置年龄大小
void CMyClass::SetAge(int nAge)
{
m_Age = nAge;
}
// 获得年龄大小
int CMyClass::GetAge()
{
return m_Age;
}
3.3 文件生成(推荐风格)
对于规范的VC派生类,尽量用Class Wizard生成文件格式,避免用手工制作的头文件/实现文件。
无论是MFC源文件还是由App Wizard生成的文件,会发现在这些类中有以下注释;
// Constructors
// Attributes
// Operations
// Overridables
// Implementation
每一次类都至少有一个//Implementation,在不同的位置MFC做不同的处理,在编写代码时最好与MFC这种风格一致。
3.4 文件目录结构
当软件项目较大,文件较多时,建议头文件(".h")保存于include目录,定义文件(".cpp")保存于source目录,资源文件保存于res目录,工程文件保存于根目录,如果工程编译成DLL,则将(".def”)保存于source目录。
4 程序版式
4.1 空行
文件之中不得存在无规则的空行(比如说连续十个空行),函数与函数之前的空行为1行。在函数体内部,在逻辑上独立的两个函数块可适当空行,一般为1行。
空行使用规则如下:
(1)、在每个类声明之后、每个函数定义结束之后都要加空行。
(2)、在一个函数体内,逻揖上密切相关的语句之间不加空行其他地方应加空行分隔。
示例:
|
函数之间的空行 |
函数内部的空行 |
|
// 空行 // 注释 void Function1(…) { … }
// 注释 void Function2(…) { … }
// 注释 void Function3(…) { … } |
// 空行 while (condition) { statement1; // 空行 if (bCondition) { statement2; } else { statement3; } // 空行 statement4; } |
4.2 代码行
代码行使用规则如下:
(1)、一行代码只做一件事情,如只定义一个变量,或只写一条语句。
(2)、if、for、while、do 等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}。
示例:
|
不良风格的代码行 |
良好风格的代码行 |
|
int width, height, depth; // 宽度高度深度 |
int nWidth; // 宽度 int nHeight; // 高度 int nDepth; // 深度 |
|
x = a + b; y = c + d; z = e + f; |
// 此处仅仅说明格式,一般变量不可起名如此简单 x = a + b; y = c + d; z = e + f; |
|
if (width<height) DoSomething (); |
|
|
for (initialization; condition; update) dosomething(); other();
|
if (nWidth < nHeight) { DoSomething(); } // 空行 Other();
|
(3)、尽可能在定义变量的同时初始化该变量(就近原则)。禁止引用未被初始化的变量。
示例:
int nWidth = 10; // 定义并初始化width
int nHeight = 10; // 定义并初始化height
int nDepth = 10; // 定义并初始化depth
4.3 代码行内的空格
(1)、关键字之后要留空格。像const、virtual、inline、case等关键字之后至少要留一个空格。像if、for、while 等关键字之后应留一个空格再跟左括号“(”,以突出关键字。
(2)、函数名之后不要留空格,紧跟左括号“(”以与关键字区别。
(3)、“(”向后紧跟,“)”、“,”、“;”向前紧跟,紧跟处不留空格。
(4)“,”之后要留空格,如Function (x, y, z)。如果“;”不是一行的结束符号,其后要留空格,如for (initialization;condition;update)。
(5)、赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+=”、“>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”、“^”等二元操作符的前后应另空格。
(6)、一元操作符如“!”、“~”、“++”、“--”、“&”(地址运算符)等前后不加空格。
(7)、像“[ ]”、“.”、“->”这类操作符前后不加空格。
(8)、对于表达式比较长的for 语句和if 语句,为了紧凑起见可以适当地去掉一些空格,如for (i =0; i <10; i++)和 if ((a<=b) && (c<=d))。
示例:
|
void Funcl(int nX, int nY, int nZ); //良好的风格 void Funcl(int x,int y,int z); //不良的风格 |
|
if (nYear >= 2000) // 良好的风格 if (year>=2000) // 不良的风格 if ((a>=b) && (c<=d)) // 良好的风格 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; // 良好的风格 x=a<b?a:b; // 不良的风格 int *x = &y; // 良好的风格 int * x = & y; // 不良的风格 |
|
array[5] = 0; // 不要写成array [ 5 ] = 0; a.Function(); // 不要写成a . Function(); b->Function(); // 不要写成b -> Function(); |
4.4 对齐
(1)、每一个嵌套的函数块,使用一个TAB缩进,程序的分界符“{”和“}”应独占一行并且位于同一列,同时与引用它们的语句左对齐。
(2)、{ }之内的代码块在‘{’右边数格处对齐。
示例:
|
不良风格的代码行 |
良好风格的代码行 |
|
void Function(int x){ … // program code }
|
void Function(int nX) { … // 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 } 如果出现嵌套的 { },则使用缩进对齐,如: { … { … } … }
|
4.5 长行拆分
(1)、代码行最大长度宜控制在70至80个字符以内。
(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_longer_initialization; very_longer_condition; very_longer_update) { DoSomething(); } |
4.6 修饰符的位置
将修饰符 * 和 & 紧靠变量名。例如:
char *name;
int *x, y; // 此处y不会被误解为指针,但是一般变量不应该这样定义要分做两行
4.7 注释
单行注释用双斜杠进行注释;多行注释用/* */进行注释;在封存的某一版本的源代码之中不得存在由于调试而留下的大篇的注释。
注释一行不要太多,一般60个字符以内(保证VC集成编辑环境的可见区域之内),如有超过,则换行处理。
注释通常用于:版本、版权声明;函数接口说明;重要的代码行或段落提示。
虽然注释有助于理解代码,但注意不可过多地使用注释。注意遵守以下规则:
(1)、注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主,注释太多了会让人眼花缭乱。注释的花样要少。
(2)、如果代码本来就是清楚的,则不必另注释。否则多此一举,令人厌烦。例如:
i++; // i 加1。“多余的注释”
(3)、用双斜杠进行注释推荐在“//”后面加一个空格,用“/* */”进行注释推荐“/*”和“*/”独立占一行,注释内容退一个制表符写。例如:
int nCount = 0; // 定义累计值 “好的风格”
int nCount = 0; //定义累计值 “不好的风格”
/*
函数介绍:
输入参数:
输出参数:
返回值:
*/
(4)、边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除掉。
(5)、注释应当准确、易懂,防止注释有二义性。错误的注释不但无益反而有害。
(6)、尽量避免在注释中使用缩写,特别是不常用的缩写。
(7)、注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。
(8)、当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。
|
示例1 |
示例2 |
|
/* 函数介绍: 输入参数: 输出参数: 返回值: */ void Function(float x,float y) { … } |
if (…) { … while(…) { … }// end of while … }// end of if
|
4.8 类的版式
本规范要求类的版式采用如下方式:
1、本规范使用以行为为主的模式,即先写函数的定义后写变量的定义。
2、本规范遵循先公有后私有,即将public类型的函数写在前面,然后是protected而将private类型的数据写在最后。
3、不同访问类型之间空一行,即将public类型的函数定义完之后,空一行再定义protected类型的函数。
3、两个函数或者成员变量之间空一行
示例:
class A
{
public:
// 函数功能说明
void Func1(void);
protected:
// 函数功能说明
void Func2(void);
private:
// 函数功能说明
void Func3 (void);
public: //先函数后变量,使得函数和变量分开便于查找,否则混在一起眼花缭乱
int m_nWidth;
protected:
int m_nSize;
private:
int m_nStudentNum;
}
5 命名规则
5.1 基本规则
为了实现跨平台定义,同时为了将实用软件中定义的类型与其它系统定义的类型加以区别,接口头文件需对变化类型进行统一命名。类例下面的命名方式:
|
实用类型 |
对应C类型 |
前缀 |
|
psVoid |
Void |
v |
|
psFloat |
Float |
f |
|
psDouble |
Double |
df |
|
psChar |
Char |
c |
|
psUChar |
Unsigned char |
uc |
|
psBool |
Boolean |
b |
|
psInt8 |
Char |
c |
|
psInt16 |
Short |
s |
|
psInt32 |
Long |
n |
|
psInt64 |
__int64 |
l |
|
psUInt8 |
Unsigned char |
uc |
|
psUInt16 |
Unsigned short |
us |
|
psUInt32 |
Unsigned long |
un |
|
psUInt64 |
Unsigned __int64 |
ul |
|
psString |
char* |
str |
|
psUnicode |
short* |
str |
|
psHandle |
Long |
h |
对枚举类型的定义为(举例说明):
typedef enum
{
psDataType_Undefined = 0, // 未定义
psDataType_Bit, // 开关量
psDataType_Int8, // 一字节整数
psDataType_Int16, // 二字节整数
psDataType_Int32, // 四字节整数
psDataType_Int64, // 八字节整数
psDataType_Float, // 单精度浮点数
psDataType_Double, // 双精度浮点数
psDataType_Char, // 字符型数
psDataType_String, // ANSI字符串
psDataType_FixedString, // 定长ANSI字符串
psDataType_Unicode, // Unicode字符串
psDataType_FixedUnicode, // 定长Unicode字符串
psDataType_Time, // 时间
psDataType_Blob, // 二进制数据块
psDataType_Max // 最大数据类型定义
} psDataType; // 实用软件中支持的数据类型种类
上面的枚举类型定义了实用软件中支持的所有测点类型,枚举定义的方式为:
枚举类型:ps+以大写字母开头的名称定义,一般为两个单词。
枚举值:枚举类型+“_”+以大写字母开头的值定义。
第一个枚举值一般为Undefined,表示未定义,最后一个一般为Max,表示最大值。
对结构类型的定义为(举例说明):
typedef struct
{
psUInt32 Second; // 秒
psUInt16 Millisec; // 豪秒
} psTime; // 时间类型定义
结构类型的定义方式:
结构类型名称为ps+类型名称;
结构中的字段不需要加前缀;
变量的命名规则采用匈牙利命名法结合VC++的原则。其规则为:<scope>[prefix]<BaseTag><Name>。
scope表示变量的作用域,全局变量用g_开始,类成员变量用m_,局部变量一般不加scope,如果函数较大则可考虑用l_用以显示说明其是局部变量。如下表所示:
|
<TBODY>scope |
类型 |
例子 |
|
g_ |
Global Variable |
g_Servers |
|
m_ |
Member variable |
m_pDoc, m_nCustomers </TBODY> |
|
l_ |
Local variable |
l_nValue </TBODY> |
prefix是可选的,常用的前缀如下表所示:
|
<TBODY>prefix |
类型 |
例子 |
|
P |
指针 |
char* psz; |
|
Rg |
集合 |
DWORD rgType[…] |
|
C |
计数器 |
DWORD cBook; |
|
H |
处理 |
HANDLE hFile; |
5.1 其他规则
(1)、标识符采用英文单词或其组合。严禁使用汉语拼音。
(2)、标识符采用“大小写”混排方式,如AddChild。
(3)、程序不能出现仅靠大小写区分的相似的标识符。如:int x, X;
(4)、程序不能出现标识符完全相同的局部变量和全局变量。
(5)、变量的名字应当使用“名词”或者“形容词+名词”。如:
float fValue;
float fOldValue;
(6)变量的名字采用“前缀 + 名称”,名称一个字母大写。如:
int nCount; // 好的命名风格
int ncount; // 不好的命名风格
(7)、全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)。类的成员函数应当只使用“动词”,被省略的名词就是对象本身。如:
DrawBox(); //全局函数
pBox->Draw(); //类的成员函数
(8)、用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。如:
int nMinValue;
int nMaxValue;
int SetValue();
int GetValue();
(9)、除非逻辑上的确需要编号,其余情况严禁在名字中出现数字编号,如:Value1,Value2等。
6 表达式和基本语句
6.1 运算符优先级
如果代码行中的运算符比较多,要用括号确定表达式的操作顺序,避免使用默认的优先级。如:
word = (high << 8) | low
if ((a|b) && (a&c))
6.2 复合表达式
本规范并不禁止使用复合表达式,如:a = b = c = 0;
但破坏可读性的复合表达式则禁止使用。下表列举了一些典型情况:
|
禁止使用的情况 |
实例 |
|
太复杂的复合表达式 |
i = a >= b && c < d && c + f <= g + h |
|
多用途的表达式 |
d = (a = b + c) + r; // 应拆成: a = b + c; d = a + r; |
|
与数学表达式混淆 |
if ( a < b < c) // 并不表示if ((a<b)&&(b<c))而是if((a<b)<c) |
6.3 if语句
(1)、布尔变量的比较
|
正确的用法 |
不良的用法 |
|
if (bFlag) // 如果bFlag为真 if (!bFlag) // 如果bFlag为假 |
if (bFlag == TRUE) if (bFlag == 1) if (bFlag == FALSE) if (bFlag == 0) |
(2)、整型变量的比较
|
正确的用法 |
不良的用法 |
|
if (nValue == 0) if (nValue != 0) |
if (nValue) // 容易误解Value为布尔 if (!nValue) |
(3)、浮点变量的比较
不可将浮点变量用“==”或“!=”与任何数字比较。而要采用“>=”或“<=”形式。
|
正确的用法 |
错误的用法 |
|
// EPSINON是允许的误差(精度) if ((fValue >= -EPSINON) && (fValue <= EPSINON) |
if (fValue == 0.0) |
(5)、指针变量的比较
|
正确的用法 |
不良的用法 |
|
if (p == NULL) if (p != NULL) |
if (p == 0) // 容易误解为整数 if (p != 0) if (p) // 容易误解为布尔 if (!p) |
(6)、补充说明
对于if/else/return的组合,要注意采用下面的书写风格:
|
正确的用法 |
不良的用法 |
|
// 获得年龄大小 int CMyClass::GetAge() { return m_Age; } |
|
|
if (condition) { return x; } else { return y; } 或者 return (condition ? x : y); |
if (condition) return x; return y;
|
6.4 循环语句
(1)在多重循环中,为提高效率,尽可能将最长的循环放在最内层,最短的循环放在最外层。
|
低效率 |
高效率 |
|
for (row = 0; row < 100; row ++) { for (col = 0; col < 5; col ++) { sum = sum + a[row][col]; } } |
for (int nCol=0; nCol<5; nCol++) { for (int nRow=0; nRow<5; nRow++) { nSum = nSum + a[nRow][nCol]; } }
|
(2)如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。
|
低效率程序简洁 |
高效率程序不简洁 |
|
for (i = 0; i < N; i ++) { if (condition) DoSomething(); else DoOtherthing(); }
|
if (condition) { for (i=0; i < N; i++) DoSomething(); } else { for (i=0; i < N; i++) DoOtherthing(); } |
(3)以上两条需要灵活处理,如果程序循环次数很多占用CPU不可忽略,可以参照以上两条。否则应该以简洁容易理解为主。
(4)不可在for循环体内修改循环变量,防止循环失去控制。
6.5 switch语句
switch的标准书写格式如下:
switch (variable)
{
case value1 :
…
break;
case value2 :
…
break;
…
default:
…
break;
}
6.6 goto语句
本规范要求慎用goto语句,而不禁用。
示例:
{…
{…
{…
goto ERROR;
}
}
}
ERROR:
…
7 常量
7.1 常量定义规则
(1)、常量定义采用const形式,禁止使用#define(返回值定义除外)。
(2)、对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。
(3)、如果某一常量与其他常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。如:
const float RADIUS = 100;
const float DIAMETER = RADIUS * 2;
8 函数
8.1 函数注释
对于自行编写的函数,若是系统关键函数,则必须在函数实现部分的上方标明该函数的信息,格式如下:
//////////////////////////////////////////////////////////////////////////////////////
// 编写者:
// 参考资料:
// 功能:
// 输入参数:
// 输出参数:
// 备注:(对使用的关键、复杂性算法进行说明)
//////////////////////////////////////////////////////////////////////////////////////
8.2 参数规则
(1)、参数书写要完整。
|
不良风格 |
良好风格 |
|
void SetValue(int, int); |
Void SetValue(int nWidth, int nHeight); |
(2)、参数命名要恰当,顺序要合理。目的参数放在前面,源参数放在后面。如:
void StringCopy (char *str1, char *str2); // 不良风格
void StringCopy (char* pszDestination, char* pszSource); // 良好风格
(3)、如果参数是指针,且仅做输入用,要在类型前加const。如:
void StringCopy (char* pszDestination, const char* pszSource);
(4)、如果输入参数以值传递的方式传递对象,建议采用“const &”方式来传递。
(5)、设计函数时,参数个数尽量控制在5个以内。
(6)、禁止使用类型和数目不确定的参数。设计函数时,参数个数尽量控制在5个以内。
8.3 返回值规则
(1)、不要将正常和错误标志混在一起返回。正常值用输出参数获得,而错误标志用return语句返回。
8.4 函数实现规则
(1)、在函数的“入口处”要加强对参数有效性的检查。
(2)、在函数的“出口处”对return语句的正确性和效率要加强检查。
8.5 其他
(1)、函数功能要单一,不要设计多用途的函数。
(2)、函数体的规模要小,尽量控制在50行以内。
(3)、尽量避免函数有“记忆”功能。相同的输入应当产生相同的输出。尽量不使用static局部变量,除非必需。
(4)、不仅要检查输入参数的有效性,还要检查通过其他途径进入函数体内的变量的有效性,例如全局变量、文件句柄等。
(5)、用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况。
浙公网安备 33010602011771号