目录
程序的版式
一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样 的代码容易阅读,并且方便于写注释。
if、for、while、do 等语句自占一行,执行语句不得紧跟其后。不论 执行语句有多少都要加{}。这样可以防止书写失误。
尽可能在定义变量的同时初始化该变量(就近原则) 引用处和其定义处相隔比较远,变量的初始化很容易被忘记。
关于代码的空格规范
【规则 2-3-1】关键字之后要留空格。象 const、virtual、inline、case 等关键字之后 至少要留一个空格,否则无法辨析关键字。象 if、for、while 等关键字之后应留一个 空格再跟左括号‘(’,以突出关键字。
【规则 2-3-2】函数名之后不要留空格,紧跟左括号‘(’,以与关键字区别
【规则 2-3-3】‘(’向后紧跟,‘)’、‘,’、‘;’向前紧跟,紧跟处不留空格。
【规则 2-3-4】‘,’之后要留空格,如 Function(x, y, z)。如果‘;’不是一行的结束 符号,其后要留空格,如 for (initialization; condition; update)
【规则 2-3-5】赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符, 如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元 操作符的前后应当加空格。
对齐
【规则 2-4-1】程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用 它们的语句左对齐
【规则 2-4-2】{ }之内的代码块在‘{’右边数格处左对齐。
长行拆分
【规则 2-5-1】代码行最大长度宜控制在 70 至 80 个字符以内。代码行不要过长,否 则眼睛看不过来,也不便于打印。 z 【规则 2-5-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();
}
修饰符的位置
【规则 2-6-1】应当将修饰符 * 和 & 紧靠变量名
例如: char *name; int *x, y; // 此处 y 不会被误解为指针
注释
【规则 2-7-1】注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主, 注释太多了会让人眼花缭乱。注释的花样要少。
【规则 2-7-2】如果代码本来就是清楚的,则不必加注释。否则多此一举,
例如 i++; // i 加 1,多余的注释
【规则 2-7-3】边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码 的一致性。不再有用的注释要删除
【规则 2-7-4】注释应当准确、易懂,防止注释有二义性。错误的注释不但无益反而 有害
【规则 2-7-5】尽量避免在注释中使用缩写,特别是不常用缩写。
【规则 2-7-6】注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不 可放在下方。 z 【规则 2-7-8】当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注 释,便于阅读
类的版式
建议读者采用“以行为为中心”的书写方式,即首先考虑类应该提供什么样的函数。
命名规则
该命名规则的主要思想是“在变量和函数名中加入前缀以增进人们对程序的理解”。
倘若采用“匈牙利”命名规则,则应当写成
int iI, iJ, ik; // 前缀 i 表示 int 类型
float fX, fY, fZ; // 前缀 f 表示 float 类型
共性规则
【规则 3-1-1】标识符应当直观且可以拼读,可望文知意,不必进行“解码”
【规则 3-1-2】标识符的长度应当符合“min-length && max-information”原则。
【规则 3-1-3】命名规则尽量与所采用的操作系统或开发工具的风格保持
例如 Windows 应用程序的标识符通常采用“大小写”混排的方式,如 AddChi
而Unix 应用程序的标识符通常采用“小写加下划线”的方式,如 add_child。
【规则 3-1-4】程序中不要出现仅靠大小写区分的相似的标识符。 例如: int x, X; // 变量 x 与 X 容易混淆 void foo(int x); // 函数 foo 与 FOO 容易混淆 void FOO(float x);
【规则 3-1-6】变量的名字应当使用“名词”或者“形容词+名词”。 例如: float value; float oldValue; float newValue;
【规则 3-1-7】全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)。 类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。 例如: DrawBox(); // 全局函数 box->Draw()// 类的成员函数
【规则 3-1-8】用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。 例如: int minValue; int maxValue; int SetValue(…); int GetValue(…);
【建议 3-1-1】尽量避免名字中出现数字编号,如 Value1,Value2 等,除非逻辑上的 确需要编号。这是为了防止程序员偷懒,
简单的 Windows 应用程序命名规则
【规则 3-2-1】类名和函数名用大写字母开头的单词组合而成。 例如: class Node; // 类名 class LeafNode; // 类名 void Draw(void); // 函数名 void SetValue(int value); // 函数名
【规则 3-2-2】变量和参数用小写字母开头的单词组合而成。
【规则 3-2-3】常量全用大写的字母,用下划线分割单词。 例如: const int MAX = 100; const int MAX_LENGTH = 100;
【规则 3-2-4】静态变量加前缀 s_(表示 static)。 例如: void Init(…) { static int s_initValue; // 静态变量 … }
【规则 3-2-6】类的数据成员加前缀 m_(表示 member),这样可以避免数据成员与 成员函数的参数同名。 例如: void Object::SetValue(int width, int height) { m_width = width; m_height = height; }
【规则 3-2-7】为了防止某一软件库中的一些标识符和其它软件库中的冲突,可以为 各种标识符加上能反映软件性质的前缀。例如三维图形标准 OpenGL 的所有库函数 均以 gl 开头,所有常量(或宏定义)均以 GL 开头。
简单的 Unix 应用程序命名规则
表达式和基本语句
运算符的优先级
【规则 4-1-1】如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免 使用默认的优先级
复合表达式
如 a = b = c = 0 这样的表达式称为复合表达式。允许复合表达式存在的理由是:(1) 书写简洁;(2)可以提高编译效率。但要防止滥用复合表达式
【规则 4-2-1】不要编写太复杂的复合表达式
例如: i = a >= b && c < d && c + f <= g + h ; // 复合表达式过于复杂
【规则 4-2-2】不要有多用途的复合表达式。 例如: d = (a = b + c) + r ; 该表达式既求 a 值又求 d 值。应该拆分为两个独立的语句: a = b + c; d = a + r;
【规则 4-2-3】不要把程序中的复合表达式与“真正的数学表达式”混淆
if 语句
布尔变量与零值比较
【规则 4-3-1】不可将布尔变量直接与 TRUE、FALSE 或者 1、0 进行比较
假设布尔变量名字为 flag,它与零值比较的标准 if 语句如下: if (flag) // 表示 flag 为真 高质量 C++/C 编程指南,v 1.0 2001 Page 28 of 101 if (!flag) // 表示 flag 为假 其它的用法都属于不良风格,例如: if (flag == TRUE) if (flag == 1 ) if (flag == FALSE) if (flag == 0)
整型变量与零值比较
【规则 4-3-2】应当将整型变量用“==”或“!=”直接与 0 比较
假设整型变量的名字为 value,它与零值比较的标准 if 语句如下: if (value == 0) if (value != 0) 不可模仿布尔变量的风格而写成 if (value) // 会让人误解 value 是布尔变量 if (!value)
浮点变量与零值比较
【规则 4-3-3】不可将浮点变量用“==”或“!=”与任何数字比较
千万要留意,无论是 float 还是 double 类型的变量,都有精度限制。所以一定要避 免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。 假设浮点变量的名字为 x,应当将 if (x == 0.0) // 隐含错误的比较
转化为 if ((x>=-EPSINON) && (x<=EPSINON)) 其中 EPSINON 是允许的误差(即精度
指针变量与零值比较
【规则 4-3-4】应当将指针变量用“==”或“!=”与 NULL 比较。 指针变量的零值是“空”(记为 NULL)。尽管 NULL 的值与 0 相同,但是两者意义不 同。假设指针变量的名字为 p,它与零值比较的标准 if 语句如下: if (p == NULL) // p 与 NULL 显式比较,强调 p 是指针变量 if (p != NULL) 不要写成 if (p == 0) // 容易让人误解 p 是整型变量 if (p != 0) 或者 if (p) // 容易让人误解 p 是布尔变量 if (!p)
循环语句的效率
【建议 4-4-1】在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的 循环放在最外层,以减少 CPU 跨切循环层的次数。例如示例 4-4(b)的效率比示例 4-4(a)的高
【建议 4-4-2】如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到 2001 Page 30 of 101 循环体的外面。示例 4-4(c)的程序比示例 4-4(d)多执行了 N-1 次逻辑判断。并且由 于前者老要进行逻辑判断,打断了循环“流水线”作业,使得编译器不能对循环进 行优化处理,降低了效率。如果 N 非常大,最好采用示例 4-4(d)的写法,可以提高 效率。如果 N 非常小,两者效率差别并不明显,采用示例 4-4(c)的写法比较好,因 为程序更加简洁。
for 语句的循环控制变量
【规则 4-5-1】不可在 for 循环体内修改循环变量,防止 for 循环失去控制。 【建议 4-5-1】建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法。
switch 语句
goto 语句
常量
为什么需要常量
【规则 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】需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义 文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。 【规则 5-3-2】如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不 高质量 C++/C 编程指南,v 1.0 2001 Page 34 of 101 应给出一些孤立的值。 例如: const float RADIUS = 100; const float DIAMETER = RADIUS * 2;
类中的常量
函数设计
本章重点论述函数的接口设计和内部实现的一些规则。 函数接口的两个要素是参数和返回值。C 语言中,函数的参数和返回值的传递方式 有两种:值传递(pass by value)和指针传递(pass by pointer)。C++ 语言中多了引用传 递(pass by reference)。由于引用传递的性质象指针传递,而使用方式却象值传递,初 学者常常迷惑不解,容易引起混乱
参数的规则
【规则 6-1-1】参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。 如果函数没有参数,则用 void 填充。 例如:
void SetValue(int width, int height); // 良好的风格
void SetValue(int, int); // 不良的风格
float GetValue(void); // 良好的风格
float GetValue(); // 不良的风格
【规则 6-1-2】参数命名要恰当,顺序要合理。
例如编写字符串拷贝函数 StringCopy,它有两个参数。如果把参数名字起为 str1 和 str2,例如 void StringCopy(char *str1, char *str2);
【规则 6-1-3】如果参数是指针,且仅作输入用,则应在类型前加 const,以防止该 指针在函数体内被意外修改
void StringCopy(char *strDestination,const char *strSource);
【规则 6-1-4】如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来 传递,这样可以省去临时对象的构造和析构过程,从而提高效率。
【建议 6-1-1】避免函数有太多的参数,参数个数尽量控制在 5 个以内。如果参数太 多,在使用时容易将参数类型或顺序搞错
【建议 6-1-2】尽量不要使用类型和数目不确定的参数。
int printf(const chat *format[, argument]…);
返回值的规则
【规则 6-2-1】不要省略返回值的
【规则 6-2-2】函数名字与返回值类型在语义上不可冲突
【规则 6-2-3】不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而 错误标志用 return 语句返回
【建议 6-2-1】有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达, 可以附加返回值。 例如字符串拷贝函数 strcpy 的原型: char *strcpy(char *strDest,const char *strSrc); strcpy 函数将 strSrc 拷贝至输出参数 strDest 中,同时函数的返回值又是 strDest。这 样做并非多此一举,可以获得如下灵活性: char str[20]; int length = strlen( strcpy(str, “Hello World”) );
【建议 6-2-2】如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传 递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递”,否则会出 错。
函数内部实现的规则
规则 6-3-1】在函数体的“入口处”,对参数的有效性进行检查。 很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言”(assert) 来防止此类错误。
【规则 6-3-2】在函数体的“出口处”,对 return 语句的正确性和效率进行检查
其它建议
【建议 6-4-1】函数的功能要单一,不要设计多用途的函数。
【建议 6-4-2】函数体的规模要小,尽量控制在 50 行代码之内。
【建议 6-4-3】尽量避免函数带有“记忆”功能。相同的输入应当产生相同的输出。 带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某 高质量 C++/C 编程指南,v 1.0 2001 Page 41 of 101 种“记忆状态”。这样的函数既不易理解又不利于测试和维护。在 C/C++语言中,函数的 static 局部变量是函数的“记忆”存储器。建议尽量少用 static 局部变量,除非必需。
【建议 6-4-4】不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内 的变量的有效性,例如全局变量、文件句柄等。
【建议 6-4-5】用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误 情况
使用断言
如果程序在 assert 处终止 了,并不是说含有该 assert 的函数有错误,而是调用者出了差错,assert 可以帮助我们 找到发生错误的原因
【规则 6-5-1】使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况 之间的区别,后者是必然存在的并且是一定要作出处理的。
z 【规则 6-5-2】在函数的入口处,使用断言检查参数的有效性(合法性)。
z 【建议 6-5-1】在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?” 一旦确定了的假定,就要使用断言对假定进行检查。
z 【建议 6-5-2】一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风格可 能会隐瞒错误。当进行防错设计时,如果“不可能发生”的事情的确发生了,则要 使用断言进行报警。
引用与指针的比较
内存管理
内存分配方式
-
从静态存储区域分配。
-
在栈上创建。
-
从堆上分配,亦称动态内存分配。
常见的内存错误及其对策
常见的内存错误及其对策如下: 内存分配未成功,却使用了它。
编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是, 在使用内存之前检查指针是否为 NULL。如果指针 p 是函数的参数,那么在函数的入口 处用 assert(p!=NULL)进行检查。如果是用 malloc 或 new 来申请内存,应该用 if(p==NULL) 或 if(p!=NULL)进行防错处理。
内存分配虽然成功,但是尚未初始化就引用它。
犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值 全为零,导致引用初值错误(例如数组)。
内存分配成功并且已经初始化,但操作越过了内存的边界。
例如在使用数组时经常发生下标“多 1”或者“少 1”的操作。特别是在 for 循环语 句中,循环次数很容易搞错,导致数组操作越界
忘记了释放内存,造成内存泄露。
含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你 看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。 动态内存的申请与释放必须配对,程序中 malloc 与 free 的使用次数一定要相同,否 则肯定有错误(new/delete 同理)
释放了内存却继续使用它。
有三种情况:
(1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内 存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
(2)函数的 return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”, 因为该内存在函数体结束时被自动销毁。 (3)使用 free 或 delete 释放了内存后,没有将指针设置为 NULL。导致产生“野指针”。
z 【规则 7-2-1】用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL。 防止使用指针值为 NULL 的内存。 z
【规则 7-2-2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右 值使用。
z 【规则 7-2-3】避免数组或指针的下标越界,特别要当心发生“多 1”或者“少 1” 操作。
z 【规则 7-2-4】动态内存的申请与释放必须配对,防止内存泄漏。
z 【规则 7-2-5】用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产 生“野指针”。