目录
一、文件结构
- 为了防止头文件被重复引用,应当用
ifndef/define/endif
结构产生预处理块。 - 用
#include <filename.h>
格式来引用标准库的头文件(编译器将从标准库目录开始搜索)。 - 用
#include "filename.h"
格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)。 - 头文件中只存放声明而不存放定义。
- 不提倡使用全局变量,尽量不要在头文件中出现像
extern int value
这类声明。
二、程序的版式
2.1 空行
- 在每个声明之后、每个函数定义结束之后都要加空行。
- 在一个函数体内,逻辑上密切相关的语句之间不加空行,其它地方应加空行分隔。
- 一行代码只做一件事情,如只定义一个变量,或只写一条语句。
if for while do
等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}。这样可以防止书写失误。- 尽可能在定义变量的时候初始化变量。
- 关键字如
const virtual inline case
等后面至少要留一个空格,否则无法辨析关键字。像if for while
等关键字后面应该加一个空格后再紧跟左括号(
,以突出关键字。 - 函数名之后不要留空格,紧跟左括号
(
,以与关键字区别。 (
向后紧跟,)
、,
、;
向前紧跟,紧跟处不留空格。- 二元操作符的前后应当加空格。
- 一元操作符的前后不加空格。
2.2 代码行
- 代码行最大长度宜控制在70至80个字符以内。代码行不要过长,否则眼睛看不过来,也不便于打印。
- 长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。
- 应当将修饰符
*
和&
紧靠变量名。这样类似
int *x, y; //此处y不会被误解为指针
2.3 注释
- 注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。
- 边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
- 当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。例如:
if (…)
{
…
while (…)
{
…
} // end of while
…
} // end of if
2.4 类
- 建议将
public
类型的函数写在前面,而将private
类型的数据写在后面。这是很多人的经验——“这样做不仅让自己在设计类时思路清晰,而且方便别人阅读。因为用户最关心的是接口,谁愿意先看到一堆私有数据成员!”
三、常量
- 尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串,例如:
#define MAX 100 // C语言的宏常量
const int MAX = 100; // C++ 语言的const常量
const float PI = 3.14159; // C++ 语言的const常量
- 在C++程序中只使用
const
常量而不使用宏常量,即const
常量完全取代宏常量。因为前者相对于后者有更多优点,比如前者有数据类型,有些集成化的调试工具可以对前者进行调试。在C++程序中只使用const
常量而不使用宏常量,即const
常量完全取代宏常量。因为前者相对于后者有更多优点,比如前者有数据类型,有些集成化的调试工具可以对前者进行调试。 - 需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为方便管理可以把不同模块的常量集中存放在一个公共的头文件中。
- 如果某一常量和其他常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。例如:
const float RADIUS = 100;
const float DIAMETER = RADIUS * 2;
四、命名规则
- 标识符应当直观且可以拼读,可望文知意,不必使用“解码”。
- 标识符的长度应当符合“min-length && max-information”原则。
- 程序中不要出现仅靠大小写区分的相似的标识符。
五、表达式和基本语句
- 为了防止歧义并提高可读性,应当用括号确定表达式的操作顺序。例如:
word = (high << 8) | low
- 在多重循环中,如果有可能,应将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数。
for (row=0; row<100; row++)
{
for ( col=0; col<5; col++ )
{
sum = sum + a[row][col];
}
}
低效率:长循环在最外层
for (col=0; col<5; col++ )
{
for (row=0; row<100; row++)
{
sum = sum + a[row][col];
}
}
高效率:长循环在最内层
- 如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。
- 不可在
for
循环体内修改循环变量,防止for
循环失去控制。 switch
语句的基本格式是:
switch (variable)
{
case value1 : …
break;
case value2 : …
break;
…
default : …
break;
}
- 每个
case
语句的结尾不要忘了加break
,否则将导致多个分支重叠。 - 慎用
goto
语句。它至少有一处可显神通,它能从多重循环体中咻地一下子跳到外面,用不着写很多次的break
语句。
六、函数设计
6.1 参数的规则
- 参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。如果函数没有参数,则用void填充。例如:
void SetValue(int width, int height); // 良好的风格
void SetValue(int, int); // 不良的风格
float GetValue(void); // 良好的风格
float GetValue(); // 不良的风格
- 参数命名要恰当合理。例如编写字符串拷贝函数
StringCopy
,它有两个参数。如果把参数名字起为str1
和str2
,例如下一。那么我们很难搞清楚是把str1
拷贝到了str2
中还是刚好倒过来。可以把参数名字起得更有意义,例如下二:
void StringCopy(char *str1, char *str2);
void StringCopy(char *strDestination, char *strSource);
- 如果参数是指针,且仅作输入用,则应在类型起那么加
const
,以防止该指针在函数体内被意外修改。例如:
void StringCopy(char *strDestination, const char *strSource);
- 如果输入参数以值传递的方式传递对象,则宜改用“
const &
”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。 - 避免函数有太多的参数,参数个数尽量控制在5个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。
- 尽量不要使用类型和数目不确定的参数。
6.2 返回值的规则
- 不要省略返回值的类型,如果函数没有返回值,那么应该声明为
void
类型。 - 函数名字与返回值类型在语义上不可冲突。违反这条规则的典型代表是C标准库函数
getchar
。不幸的是getchar()
的确不是char
函数,而是int
类型,其原型如下:
int getchar(void);
- 如果函数的返回值是一个对象,有些场合用“
引用传递
”替换“值传递
”可以提高效率。而有些场合只能用“值传递
”而不能用“引用传递
”,否则会出错。
6.3 函数内部实现的规则
- 在函数体的“入口处”,对参数有效性进行检查,应充分理解并正确使用“断言”(
assert
)来防止此类错误。例如:
void *memcpy(void *pvTo, const void *pvFrom, size_t size)
{
assert((pvTo != NULL) && (pvFrom != NULL)); //使用断言
byte *pbTo = (byte *)pvTo; // 防止改变pvTo的地址
byte *pbFrom = (byte *) pvFrom; // 防止改变pvFrom的地址
while(size -- > 0 )
*pbTo ++ = *pbFrom ++ ;
return pvTo;
}
- 在函数的“出口处”,对
return
语句的正确性和效率进行检查。
注意:
(1)return
语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
(2)要搞清楚返回的究竟是“值”,“指针”还是“引用”。
(3)如果函数返回值是一个对象,要考虑return
语句的效率。例如
return String(s1 + s2);
这是临时对象的语法,表示“创建一个临时对象并返回它”。不要以为它与“先创建一个局部对象temp并返回它的结果”是等价的,如
String temp(s1 + s2);
return temp;
实质不然,上述代码将发生三件事。首先,temp
对象被创建,同时完成初始化;然后拷贝构造函数把temp
拷贝到保存返回值的外部存储单元中;最后,temp
在函数结束时被销毁(调用析构函数)。然而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了效率。
类似地,我们不要将
return int(x + y);// 创建一个临时变量并返回它
写成
int temp = x + y;
return temp;
由于内部数据类型如int
,float
,double
的变量不存在构造函数与析构函数,虽然该“临时变量的语法”不会提高多少效率,但是使程序更加简洁易读。
6.4 断言
assert
不是函数,而是宏。程序员可以把assert
看成一个任何系统状态下都可以安全使用的无害测试手段。如果程序在assert
处终止了,并不是说含有该assert
的函数有错误,而是调用者出了差错,assert
可以帮助我们找到发生错误的原因。- 使用断言捕捉不应该发生的非法情况
6.5 其他建议
- 函数的功能要单一,不要涉及多用途的函数。
- 函数体的规模要小,尽量控制在50行代码之内。
- 尽量避免函数带有“记忆”功能,相同的输入应当产生相同的输出。在C++和C语言中,函数的
static
局部变量是函数的“记忆”存储器。建议尽量少用static
局部变量,除非必需。 - 不仅要检查输入参数的有效性,还要检查通过其他途径进入函数体内的变量的有效性,例如全局变量、文件句柄等。
- 用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况。