第一章 文件结构
每个C++/C 程序通常分为两个文件。一个文件用于保存程序的声明(declaration),
称为头文件。另一个文件用于保存程序的实现(implementation),称为定义(definition)
文件。C++/C 程序的头文件以“.h”为后缀,C 程序的定义文件以“.c”为后缀,C++程序
的定义文件通常以“.cpp”为后缀(也有一些系统以“.cc”或“.cxx”为后缀)。
1.1 版权和版本的声明
版权和版本的声明位于头文件和定义文件的开头(参见示例1-1),主要内容有:
(1)版权信息。
(2)文件名称,标识符,摘要。
(3)当前版本号,作者/修改者,完成日期。
(4)版本历史信息。
/*
* Copyright (c)
* All rights reserved.
*
* 文件名称: filename.h
* 文件标识: 见配置管理计划书
* 摘要: 简要描述本文件的内容
*
* 当前版本: 1.1
* 作者: 输入作者(或修改者)名字
* 完成日期: 2001年7月20日
*
* 取代版本:1.0
* 原作者: 输入原作者(或修改者)名字
* 完成日期: 2001年5月10日
*/
1.2 头文件的结构
头文件由三部分内容组成:
(1)头文件开头处的版权和版本声明(参见示例1-1)。
(2)预处理块。
(3)函数和类结构声明等。
假设头文件名称为graphics.h,头文件的结构参见示例1-2。
【规则1-2-1】为了防止头文件被重复引用,应当用ifndef/define/endif 结构产生预处理
【规则1-2-2】用#include <filename.h> 格式来引用标准库的头文件(编译器将从标准库目录开始搜索)。
【规则1-2-3】用#include “filename.h” 格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)。
【建议1-2-1】不提倡使用全局变量,尽量不要在头文件中出现象extern int value 这类声明。
// 版权和版本声明见示例1-1,此处省略。
#ifndef GRAPHICS_H // 防止graphics.h 被重复引用
#define GRAPHICS_H
#include <math.h> // 引用标准库的头文件
.
#include “myheader.h” // 引用非标准库的头文件
.
void Function1(.); // 全局函数声明
.
class Box // 类结构声明
{ .
};
#endif
1.3 定义文件的结构
定义文件有三部分内容:
(1) 定义文件开头处的版权和版本声明(参见示例1-1)。
(2) 对一些头文件的引用。
(3) 程序的实现体(包括数据和代码)。
假设定义文件的名称为graphics.cpp,定义文件的结构参见示例1-3。
// 版权和版本声明见示例1-1,此处省略。
#include “graphics.h” // 引用头文件
.
// 全局函数的实现体
void Function1(.)
{
.
}
// 类成员函数的实现体
void Box::Draw(.)
{
.
}
1.4 头文件的作用
(1)通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取相应的代码。
(2)头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。
1.5 目录结构
如果一个软件的头文件数目比较多(如超过十个),通常应将头文件和定义文件分别保存于不同的目录,以便于维护。例如可将头文件保存于include 目录,将定义文件保存于source 目录(可以是多级目录)。如果某些头文件是私有的,它不会被用户的程序直接引用,则没有必要公开其“声明”。为了加强信息隐藏,这些私有的头文件可以和定义文件存放于同一个目录。
第二章 程序的版式
2.1 空行和空格
【规则2-1-1】在每个类声明之后、每个函数定义结束之后都要加空行。参见示例
// 空行
void Function1(.)
{
.
}
// 空行
void Function2(.)
{
.
}
【规则2-1-2】在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。参见示例
while (condition)
{
statement1;
// 空行
if (condition)
{
statement2;
}
else
{
statement3;
}
// 空行
statement4;
}
【规则2-1-3】在{}标注后,在起始处有一个Tab的空格, 出现嵌套的{}则使用缩进对齐。
if(nWidth>10)
{
if(nWidth>20)
{
nHeight=20;
}
}
2.2 代码行
【规则2-2-1】一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易
阅读,并且方便于写注释。
int nWidth; // 宽度
int nHeight; // 高度
int nDepth; // 深度
x = a + b;
y = c + d;
z = e + f;
【规则2-2-2】if、for、while、do 等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}。这样可以防止书写失误。
if (nWidth < nHeight)
{
dosomething();
}
for (initialization; condition; update)
{
dosomething();
}
// 空行
other();
【建议2-2-1】尽可能在定义变量的同时初始化该变量(就近原则)
int nWidth = 10; // 定义并初绐化nWidth
2.3 对齐
【规则2-3-1】程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的句左对齐。
void Function(int nX)
{
. // program code
}
【规则2-3-2】{ }之内的代码块在‘{’右边数格处左对齐。
if (condition)
{
. // program code
}
else
{
. // program code
}
2.4 长行拆分
【规则2-4-1】代码行最大长度宜控制在70 至80 个字符以内。代码行不要过长,否则眼睛看不过来,也不便于打印。
if ((very_longer_variable1 >= very_longer_variable12)
&& (very_longer_variable3 <= very_longer_variable14)
&& (very_longer_variable5 <= very_longer_variable16))
{
dosomething();
}
【规则2-4-2】长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。
if ((very_longer_variable1 >= very_longer_variable12)
&& (very_longer_variable3 <= very_longer_variable14)
&& (very_longer_variable5 <= very_longer_variable16))
{
dosomething();
}
2.5 注释
C 语言的注释符为“/*…*/”。C++语言中,程序块的注释常采用“/*…*/”,行注释一般采用“//…”。
【规则2-5-1】在四个地方一定要加注释:
(1) 版本、版权声明
参见示例1-1
(2) 函数作用和接口说明包括:
函数功能说明,参数说明,返回值说明
//在画一个圆
//Pos:圆心 nR:圆的半径
//返回TRUE 表示成功,FALSE 表示失败
BOOL Circle::Draw(CDC *pDC,CPoint Pos,int nR)
{
.
}
(3)重要的代码行或算法
//有七参数,先将B,L,H转换为X,Y,Z,在进行七参数转换
BtoX(par.da,par.df,dB,dL,dH,dX,dY,dZ);
XtoX(-par.dDX, -par.dDY, -par.dDZ,
-par.dWx, -par.dWy, -par.dWz,
-par.dK,dX,dY,dZ);
(4)类的公共成员函数、变量和结构声明处
class Cline:public CDraw
{
public:
double m_dXstart,m_dYStart; //线段的起点坐标
double m_dXend,m_dYend; //线段的起点坐标
BOOL Draw(CDC *Pdc,int nDrawMode);//画线
……
};
struct ALLPar
{
public:
double dDX;//七参数
double dDY;
double dDZ;
double dWx;
double dWy;
double dWz;
double dK;
double dCxl;//一级地方转换参数
double dCyl;
double dCal;
double dCkl;
…
};
【规则2-5-2】尽量避免在注释中使用缩写,特别是不常用缩写。
【规则2-5-3】注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方.
2.6 类的版式
【规则2-6-1】将public 类型的函数写在前面,而将private 类型的数据写在后面。
class A
{
public:
void Func1(void);
void Func2(void);
…
private:
int nI, nJ;
float fX, fY;
}
第三章 命名规则
3.1 共性规则
本节论述的共性规则是被大多数程序员采纳的,我们应当在遵循这些共性规则的前提下,再扩充特定的规则,如3.2 节。
【规则3-1-1】统一使用匈牙利命名法来表示变量。
前缀 | 数据类型 |
u | 无符号(unsigned) |
c | 字符(char) |
s | 短整数(short) |
n | 整数(integer) |
f | 单精度(float) |
d | 双精度(double) |
b | 字节 |
str | 以‘/0’结尾的字符串 |
bl | BOOL |
w | 字(WORD,无符号短整数) |
l | 长整数(long) |
h | HANDLE(无符号int) |
dw | 双字节(DWORD,无符号长整数) |
p | 指针 |
【规则3-1-2】标识符应当直观且可以拼读,可望文知意,不必进行“解码”。标识符最好采用英文单词或其组合,便于记忆和阅读。切忌使用汉语拼音来命名。
【规则3-1-3】标识符的长度应当符合“min-length && max-information”原则。
【规则3-1-4】在变量前缀后统一大写第一个字母。
int nX;
float fHeight;
【规则3-1-5】命名规则尽量与所采用的操作系统或开发工具的风格保持一致。
【规则3-1-6】程序中不要出现仅靠大小写区分的相似的标识符。
int nx, nX; // 变量x 与X 容易混淆
void Foo(int nX); // 函数Foo 与FOO 容易混淆
void FOO(float fX);
【规则3-1-7】程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解。
【规则3-1-8】变量的名字应当使用“名词”或者“形容词+名词”。
例如:
float fValue;
float fOldValue;
float fNewValue;
【规则3-1-9】全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)。类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。
例如:
DrawBox(); // 全局函数
box->Draw(); // 类的成员函数
【规则3-1-10】用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。
例如:
int nMinValue;
int nMaxValue;
int SetValue(…);
int GetValue(…);
【规则3-1-11】尽量避免名字中出现数字编号,如Value1,Value2 等,除非逻辑上的确需要编号。这是为了防止程序员偷懒,不肯为命名动脑筋而导致产生无意义的名字(因为用数字编号最省事)。
3.2 简单的Windows 应用程序命名规则
【规则3-2-1】类名和函数名用大写字母开头的单词组合而成。
class Node; // 类名
class LeafNode; // 类名
void Draw(void); // 函数名
void SetValue(int nValue); // 函数名
【规则3-2-2】常量全用大写的字母,用下划线分割单词。
例如:
const int MAX = 100;
const int MAX_LENGTH = 100;
【规则3-2-3】静态变量加前缀s_(表示static)。
例如:
void Init(…)
{
static int s_nInitValue; // 静态变量
…
}
【规则3-2-4】如果不得已需要全局变量,则使全局变量加前缀g_(表示global)。
例如:
int g_nHowManyPeople; // 全局变量
int g_nHowMuchMoney; // 全局变量
【规则3-2-5】类的数据成员加前缀m_(表示member),这样可以避免数据成员与成员函数的参数同名。
例如:
void Object::SetValue(int nWidth, int nHeight)
{
m_nWidth = nWidth;
m_nHeight = nHeight;
}
【规则3-2-6】为了防止某一软件库中的一些标识符和其它软件库中的冲突,可以为各种标识符加上能反映软件性质的前缀。例如三维图形标准OpenGL 的所有库函数均以gl 开头,所有常量(或宏定义)均以GL 开头。
第四章 表达式和基本语句
4.1 运算符的优先级
【规则4-1-1】如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级。
word = (high << 8) | low
if ((a | b) && (a & c))
4.2 复合表达式
【规则4-2-1】不要编写复合表达式。
4.3 if 语句
【规则4-3-1】不可将布尔变量直接与TRUE、FALSE 或者1、0 进行比较。
假设布尔变量名字为blFlag,它与零值比较的标准if 语句如下:
if (blFlag) // 表示flag 为真
if (!blFlag) // 表示flag 为假
【规则4-3-2】应当将整型变量用“==”或“!=”直接与0 比较。假设整型变量的名字为nValue,它与零值比较的标准if 语句如下:
if (nValue == 0)
if (nValue != 0)
【规则4-3-3】不可将浮点变量用“==”或“!=”与任何数字比较。千万要留意,无论是float还是double 类型的变量,都有精度限制。应当将
if (fX == 0.0) // 隐含错误的比较
转化为
if ((fX>=-EPSINON) && (fX<=EPSINON))
其中EPSINON 是允许的误差(即精度)。
【规则4-3-4】应当将指针变量用“==”或“!=”与NULL 比较。
if (p == NULL) // p 与NULL 显式比较,强调p 是指针变量
if (p != NULL)
4.4 for 语句的循环控制变量
【规则4-4-1】不可在for 循环体内修改循环变量,防止for 循环失去控制。
【规则4-4-2】for 语句的循环控制变量的取值采用“半开半闭区间”写法。示例4-5(a)中的x 值属于半开半闭区间“0 =< nX < N”,起点到终点的间隔为N,循环次数为N。
for (int nX=0; nX<N; nX++)
{
.
}
4.5 switch 语句
【规则4-5-1】每个case 语句的结尾不要忘了加break,否则将导致多个分支重叠(除非有意使多个分支重叠)。
【规则4-5-2】不要忘记最后那个default 分支。即使程序真的不需要default 处理,也应该保留语句default : break;
4.6 goto 语句
【规则4-6-1】少用、慎用goto 语句。
第五章 常量
常量是一种标识符,它的值在运行期间恒定不变。C 语言用#define 来定义常量(称为宏常量)。C++ 语言除了#define 外还可以用const 来定义常量(称为const 常量)。
5.1 为什么需要常量
【规则5-1-1】尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串。
#define MAX 100 /* C 语言的宏常量*/
const int MAX = 100; // C++ 语言的const 常量
const float PI = 3.14159; // C++ 语言的const 常量
5.2 const 与#define 的比较
【规则5-2-1】在C++ 程序中只使用const 常量而不使用宏常量,即const 常量完全取代宏常量。
5.3 常量定义规则
【规则5-3-1】需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。
【规则5-3-2】如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。
const float RADIUS = 100;
const float DIAMETER = RADIUS * 2;
第六章 函数设计
6.1 参数的规则
【规则6-1-1】参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。如果函数没有参数,则用void 填充。
void SetValue(int nWidth, int nHeight);
float GetValue(void);
【规则6-1-2】参数命名要恰当,顺序要合理。
void StringCopy(char *cSource, char *cDestination);
【规则6-1-3】如果参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改。
void StringCopy(char *cDestination,const char *cSource);
【规则6-1-4】如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。
【建议6-1-1】避免函数有太多的参数,参数个数尽量控制在5 个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。
6.2 返回值的规则
【规则6-2-1】不要省略返回值的类型。
【规则6-2-2】函数名字与返回值类型在语义上不可冲突。
【规则6-2-3】不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用return 语句返回。
【建议6-2-1】有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,
例如字符串拷贝函数strcpy 的原型:
char *strcpy(char *cDest,const char *cSrc);
6.3 函数内部实现的规则
【规则6-3-1】在函数体的“入口处”,对参数的有效性进行检查。
【规则6-3-2】在函数体的“出口处”,对return 语句的正确性和效率进行检查。
(1)return 语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。例如
char * Func(void)
{
char cStr[] = “hello world”; // str 的内存位于栈上
…
return cStr; // 将导致错误
}
(2)要搞清楚返回的究竟是“值”、“指针”还是“引用”。
希望对大家编写代码有一定的帮助,有不足之处,请大家多多指正,一起交流与学习!