C/C++高质量编程指南之一
第一章:文件结构
1 在文件开头加上版本信息。
【规则 1-2-1】为了防止头文件被重复引用,应当用 ifndef/define/endif 结构产生预处理块
【规则 1-2-2】用 #include <filename.h> 格式来引用标准库的头文件(编译器将从 标准库目录开始搜索)
【规则 1-2-3】用 #include “filename.h” 格式来引用非标准库的头文件(编译器将 从用户的工作目录开始搜索)
【建议 1-2-1】头文件中只存放“声明”而不存放“定义”
第二章:程序的版式
【规则 2-1-1】在每个类声明之后、每个函数定义结束之后都要加空行。
【规则 2-1-2】在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应 加空行分隔。
【规则 2-2-1】一行代码只做一件事情,如只定义一个变量,或只写一条语句。
【规则 2-2-2】if、for、while、do 等语句自占一行,执行语句不得紧跟其后。不论 执行语句有多少都要加{}。这样可以防止书写失误
【建议 2-2-1】尽可能在定义变量的同时初始化该变量(就近原则)
【规则 2-3-1】关键字之后要留空格。
【规则 2-3-2】函数名之后不要留空格,紧跟左括号‘(’,以与关键字区别
【规则 2-3-5】 “=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元 操作符的前后应当加空格
【规则 2-3-6】一元操作符如“!”、“~”、“++”、“--”、“&”(地址运算符)等前后不 加空格。
【建议 2-3-1】对于表达式比较长的 for 语句和 if 语句,为了紧凑起见可以适当地去 掉一些空格,
如 for (i=0; i<10; i++)和 if ((a<=b) && (c<=d))
【规则 2-4-1】程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用 它们的语句左对齐。
【规则 2-4-2】{ }之内的代码块在‘{’右边数格处左对齐。
注意:这个是有争议的,可能只是在C/C++中这样去做,在其他语言中好像不是这样的,
我的习惯是第一个。
长行拆分:
【规则 2-6-1】应当将修饰符 * 和 & 紧靠变量名
char *name; int *x, y; // 此处 y 不会被误解为指针
【规则 2-7-6】注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不 可放在下方。
第三章:命名规则
这块好好看看哈。
【规则 3-1-3】命名规则尽量与所采用的操作系统或开发工具的风格保持一致。
例如 Windows 应用程序的标识符通常采用“大小写”混排的方式,如 AddChild。
而 Unix 应用程序的标识符通常采用“小写加下划线”的方式,如 add_child。
【规则 3-1-6】变量的名字应当使用“名词”或者“形容词+名词” 。
float value; float oldValue; float newValue;
【规则 3-1-7】全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组) 。
类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。
DrawBox(); // 全局函数 box->Draw(); // 类的成员函数
【建议 3-1-1】尽量避免名字中出现数字编号,如 Value1,Value2 等,除非逻辑上的 确需要编号
【规则 3-2-1】类名和函数名用大写字母开头的单词组合而成。 例如:
class Node; // 类名 class LeafNode; // 类名 void Draw(void); // 函数名 void SetValue(int value); // 函数名
【规则 3-2-2】变量和参数用小写字母开头的单词组合而成。
BOOL flag; int drawMode; 也可以这样: string window_name; // OK 使用下划线 string windowname; // OK 全部小写 string windowName; // Bad 大小写混合使用 全局变量:没有特殊要求,尽量少用?可以加上前缀g_以与局部变量区分。 类的成员变量:可以加上前缀m_ 当然也有就加一个_的
【规则 3-2-3】常量全用大写的字母,用下划线分割单词
const int MAX = 100; const int MAX_LENGTH = 100;
【规则 3-2-4】静态变量加前缀 s_(表示 static)
static int s_initValue; // 静态变量
【规则 3-2-5】如果不得已需要全局变量,则使全局变量加前缀 g_(表示 global)。
int g_howMuchMoney; // 全局变量
【规则 3-2-6】类的数据成员加前缀 m_(表示 member),这样可以避免数据成员与 成员函数的参数同名。
第四章:表达式和基本语句
【规则 4-1-1】如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免 使用默认的优先级。
与零值比较:
布尔变量与零值比较
【规则 4-3-1】不可将布尔变量直接与 TRUE、FALSE 或者 1、0 进行比较。
假设布尔变量名字为 flag,它与零值比较的标准 if 语句如下:
if (flag) // 表示 flag 为真
if (!flag) // 表示 flag 为假
整型变量与零值比较 :
【规则 4-3-2】应当将整型变量用“==”或“!=”直接与 0 比较。
if (value == 0)
if (value != 0)
浮点变量与零值比较 :
【规则 4-3-3】不可将浮点变量用“==”或“!=”与任何数字比较。
千万要留意,无论是 float 还是 double 类型的变量,都有精度限制。
所以一定要避 免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。
正确的比较方式
if ((x>=-EPSINON) && (x<=EPSINON)) 其中 EPSINON 是允许的误差(即精度) 。
其中EPSINON是允许的误差(即精度)。 const float EPSINON = 0.000001,至于为什么取0.000001,可以自己按实际情况定义。
指针变量与零值比较
【规则 4-3-4】应当将指针变量用“==”或“!=”与 NULL 比较
if (p == NULL) // p 与 NULL 显式比较,强调 p 是指针变量
if (p != NULL)
【建议 4-4-1】在多重循环中,如果有可能,应当将长的循环放在内层,短的 循环放在外层,以减少 CPU 跨切循环层的次数
第五章:常量
为什么需要常量:
如果不使用常量,直接在程序中填写数字或字符串,将会有什么麻烦?
(1) 程序的可读性(可理解性)变差。程序员自己会忘记那些数字或字符串是什么意 思,用户则更加不知它们从何处来、表示什么。
(2) 在程序的很多地方输入同样的数字或字符串,难保不发生书写错误。
(3) 如果要修改数字或字符串,则会在很多地方改动,既麻烦又容易出错。
【规则 5-1-1】 尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或
#define MAX 100 /* C 语言的宏常量 */ const int MAX = 100; // C++ 语言的 const 常量 const float PI = 3.14159; // C++ 语言的 const 常量
const和define的比较
C++ 语言可以用 const 来定义常量,也可以用 #define 来定义常量。但是前者比后 者有更多的优点:
(1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安 全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会 产生意料不到的错误(边际效应)。
(2) 有些集成化的调试工具可以对 const常量进行调试,但是不能对宏常量进行调试。
【规则 5-2-1】在 C++ 程序中只使用 const 常量而不使用宏常量,即 const 常量完 全取代宏常量。
【规则 5-3-1】需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义 文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。
第六章:函数设计
【规则 6-1-2】参数命名要恰当,顺序要合理。
应将目的参数放在前面,源参数放在后面。
例如编写字符串拷贝函数 StringCopy
void StringCopy(char *strDestination,const char *strSource);
调用时:StringCopy(str, “Hello World”);
【规则 6-1-4】如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来 传递,这样可以省去临时对象的构造和析构过程,从而提高效率
【建议 6-2-1】有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,
例如字符串拷贝函数 strcpy 的原型:
char *strcpy(char *strDest,const char *strSrc);
适用于:int length = strlen( strcpy(str, “Hello World”) );
【规则 6-3-1】在函数体的“入口处”,对参数的有效性进行检查 用assert
【规则 6-3-2】在函数体的“出口处”,对 return 语句的正确性和效率进行检查。
(1)return 语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数 体结束时被自动销毁。
例如
char * Func(void) { char str[] = “hello world”; // str 的内存位于栈上 … return str; // 将导致错误 }
(2)要搞清楚返回的究竟是“值” 、“指针”还是“引用”
(3)如果函数返回值是一个对象,要考虑 return 语句的效率。
例如 return String(s1 + s2);
这是临时对象的语法,表示“创建一个临时对象并返回它”。
不要以为它与“先创建 一个局部对象 temp 并返回它的结果”是等价的,
如:
String temp(s1 + s2);
return temp; 。
实质不然,上述代码将发生三件事。
首先,temp 对象被创建,同时完成初始化;
然 后拷贝构造函数把 temp 拷贝到保存返回值的外部存储单元中;
最后,temp 在函数结束 时被销毁(调用析构函数)。
然而“创建一个临时对象并返回它”的过程是不同的,编译 器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了 效率。