高质量编程指南 难点记录

1.头文件规范:
如:

RB-Tree的插入和删除操作的实现算法
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  1.版权声明:
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME    Copyright @ 2006,pengkuny( http://www.cppblog.com/pengkuny
),All rights reserved.
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME    您可以自由的传播,修改这份代码,转载处请注明原作者 
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  2.文件名称: 
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  3.参考资料:
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  1) <<Introduction to algorithm>>
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME
  2) 
http://www.ececs.uc.edu/ ~franco/C321/html/RedBlack/redblack.html
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  4.内容摘要:
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  红黑树的几个性质:
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  1) 每个结点只有红和黑两种颜色
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  2) 根结点是黑色的
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  3) 每个叶子结点(空结点被认为是叶子结点)是黑色的
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  4) 如果一个结点是红色的,那么它的左右两个子结点的颜色是黑色的
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  5) 对于每个结点而言,从这个结点到叶子结点的任何路径上的黑色结点
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  的数目相同 
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  5.完成日期:
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME    2006.11.14

高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME  -------------------------------------------------------------
*/   

2.【规则】一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。如:

int width; // 宽度
int height; // 高度
int depth; // 深度
x = a + b;
y = c + d;
z = e + f;
if (width < height)
{
dosomething();
}
for (initialization; condition; update)
{
dosomething();
}
// 空行
other();

 

3.【建议】尽可能在定义变量的同时初始化该变量(就近原则)
如:

int  width  =   10 ;   //  定义并初绐化width 
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME
int  height  =   10 //  定义并初绐化height 
高质量编程指南 难点记录 - 魑魅魍魉福 - 魑魅魍魉HOME
int  depth  =   10 ;   //  定义并初绐化depth 

4.空格写法:
 【规则】if、for、while等关键字之后应留一个空格再跟左括号‘(’,以突出关键字。

 【规则】函数名之后不要留空格,紧跟左括号‘(’,以与关键字区别。

 【规则】‘(’向后紧跟,‘)’、‘,’、‘;’向前紧跟,紧跟处不留空格。

 【规则】‘,’之后要留空格,如Function(x, y, z)。如果‘;’不是一行的结束符号,其后要留空格,如for (initialization; condition; update)。

 【规则】赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。

 【规则】一元操作符如“!”、“~”、“++”、“--”、“&”(地址运算符)等前后不加空格。

 

5.【建议】对于表达式比较长的for语句和if语句,为了紧凑起见可以适当地去掉一些空格,如for (i=0; i<10; i++)和if ((a<=b) && (c<=d))

 

6.长行拆分:
 【规则】长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。

 

7.应当将修饰符 * 和 & 紧靠变量名
例如:
char  *name;
int   *x, y; // 此处y不会被误解为指针

 

8.注释的写法:
   程序块的注释常采用“/*…*/”,行注释一般采用“//…”。

 【规则】注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主,注释太多了会让人眼花缭乱。注释的花样要少。

 【规则】边写代码边注释

 【规则】注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。

 

9.类的版式:
   我们不可以滥用类的封装功能,不要把它当成火锅,什么东西都往里扔。
   将public类型的函数写在前面,而将private类型的数据写在后面,“以行为为中心”,重点关注的是类应该提供什么样的接口(或服务)。

 10 目录结构

如果一个软件的头文件数目比较多(如超过十个),通常应将头文件和定义文件分别
保存于不同的目录,以便于维护。
例如可将头文件保存于include 目录,将定义文件保存于source 目录(可以是多级
目录)。
如果某些头文件是私有的,它不会被用户的程序直接引用,则没有必要公开其“声
明”。为了加强信息隐藏,这些私有的头文件可以和定义文件存放于同一个目录。

11命名规则

命名规则尽量与所采用的操作系统或开发工具的风格保持一致。
例如Windows 应用程序的标识符通常采用“大小写”混排的方式,如AddChild。而
Unix 应用程序的标识符通常采用“小写加下划线”的方式,如add_child。别把这两类风
化境编程界推荐图书系列 - 高质量C++/C 编程指南,v 1.0
2001 Page 23 of 101
格混在一起用。

【建议3-1-1】尽量避免名字中出现数字编号,如Value1,Value2 等,除非逻辑上的
确需要编号。这是为了防止程序员偷懒,不肯为命名动脑筋而导致产生无意义的名
字(因为用数字编号最省事)。

12  简单的Windows 应用程序命名规则
作者对“匈牙利”命名规则做了合理的简化,下述的命名规则简单易用,比较适合
于Windows 应用软件的开发。

z 【规则3-2-1】类名和函数名用大写字母开头的单词组合而成。
例如:
class Node; // 类名
class LeafNode; // 类名
void Draw(void); // 函数名
void SetValue(int value); // 函数名
z 【规则3-2-2】变量和参数用小写字母开头的单词组合而成。
例如:
BOOL flag;
int drawMode;
z 【规则3-2-3】常量全用大写的字母,用下划线分割单词。
例如:
const int MAX = 100;
const int MAX_LENGTH = 100;
z 【规则3-2-4】静态变量加前缀s_(表示static)。
例如:
void Init(…)
{
static int s_initValue; // 静态变量

}
z 【规则3-2-5】如果不得已需要全局变量,则使全局变量加前缀g_(表示global)。
例如:
int g_howManyPeople; // 全局变量
int g_howMuchMoney; // 全局变量
z 【规则3-2-6】类的数据成员加前缀m_(表示member),这样可以避免数据成员与
成员函数的参数同名。
例如:
void Object::SetValue(int width, int height)
{
m_width = width;
m_height = height;
}
z 【规则3-2-7】为了防止某一软件库中的一些标识符和其它软件库中的冲突,可以为
各种标识符加上能反映软件性质的前缀。例如三维图形标准OpenGL 的所有库函数
均以gl 开头,所有常量(或宏定义)均以GL 开头。

 

13.布尔变量与零值比较:
【规则1】不可将布尔变量直接与TRUE、FALSE或者1、0进行比较。
    根据布尔类型的语义,零值为“假”(记为FALSE),任何非零值都是“真”(记为TRUE)。TRUE的值究竟是什么并没有统一的标准。例如Visual C++ 将TRUE定义为1,而Visual Basic则将TRUE定义为-1。
假设布尔变量名字为flag,它与零值比较的标准if语句如下:
if (flag) // 表示flag为真
if (!flag) // 表示flag为假
其它的用法都属于不良风格,例如:
 if (flag == TRUE) 
 if (flag == 1 )  
 if (flag == FALSE) 
 if (flag == 0) 

对于林锐提出的规则我有质疑,既然bool型的值按定义记为FALSE和TRUE,
那么我根本不关心FALSE和TRUE的具体值,那么
 if (flag == TRUE)  
又有何不可?系统总不会把TRUE定义为0吧?(如果是这样,林锐还是对的)

 

14.整型变量与零值比较:
【规则】应当将整型变量用“==”或“!=”直接与0比较。
与零值比较的标准if语句如下:
if (value == 0) 
if (value != 0)
不可模仿布尔变量的风格而写成
if (value)  // 会让人误解 value是布尔变量
if (!value)

 

15.浮点变量与零值比较:
if ((x>=-EPSINON) && (x<=EPSINON))

 

16.指针变量与零值比较:
if (p == NULL) // p与NULL显式比较,强调p是指针变量
if (p != NULL) 


为了防止将 if (p == NULL) 误写成 if (p = NULL),而有意把p和NULL颠倒。
编译器认为 if (p = NULL) 是合法的,但是会指出 if (NULL = p)是错误的,
因为NULL不能被赋值。

 

17 if (condition) //不良风格,容易出错
 return x;
return y;
改写为
if (condition)
{
 return x;
}
else
{
 return y;
}
或者改写成更加简练的
return (condition ? x : y);

18 for

【建议4-4-1】在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的
循环放在最外层,以减少CPU 跨切循环层的次数。例如示例4-4(b)的效率比示例
4-4(a)的高。

【建议4-4-2】如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到
循环体的外面。示例4-4(c)的程序比示例4-4(d)多执行了N-1 次逻辑判断。并且由
于前者老要进行逻辑判断,打断了循环“流水线”作业,使得编译器不能对循环进
行优化处理,降低了效率。如果N 非常大,最好采用示例4-4(d)的写法,可以提高
效率。如果N 非常小,两者效率差别并不明显,采用示例4-4(c)的写法比较好,因
为程序更加简洁。

建议for 语句的循环控制变量的取值采用“半开半闭区间”写法。

 

19 goto

goto 语句至少有一处可显神通,它能从多重循环体中咻
地一下子跳到外面,用不着写很多次的break 语句;

 

20   const 与 #define 的比较
C++ 语言可以用const 来定义常量,也可以用 #define 来定义常量。但是前者比后
者有更多的优点:
(1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安
全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会
产生意料不到的错误(边际效应)。
(2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。
z 【规则5-2-1】在C++ 程序中只使用const 常量而不使用宏常量,即const 常量完
全取代宏常量。

【规则】需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部(cpp文件)。
为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。

 

21 类中使用const常量:
1)不能在类声明中初始化const数据成员。
  因为类的对象未被创建时,编译器不知道该成员的值是什么。
  const数据成员的初始化只能在类构造函数的初始化表中进行.
2)const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,
  因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。
3)怎样才能建立在整个类中都恒定的常量呢?
  别指望const数据成员了,应该用类中的枚举常量来实现。
  但是缺点很明显,只能表示有限大小的整型值.

 

22 参数的规则

【规则6-1-1】参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。
如果函数没有参数,则用void 填充。
例如:
void SetValue(int width, int height); // 良好的风格
void SetValue(int, int); // 不良的风格
float GetValue(void); // 良好的风格
float GetValue(); // 不良的风格

【规则6-1-4】如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来
传递,这样可以省去临时对象的构造和析构过程,从而提高效率。

对于赋值函数,应当用“引用传递”的方式返回String 对象。如果用“值传递”的
方式,虽然功能仍然正确,但由于return 语句要把 *this 拷贝到保存返回值的外部存储
单元之中,增加了不必要的开销,降低了赋值函数的效率

23 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 在函数结束
时被销毁(调用析构函数)。然而“创建一个临时对象并返回它”的过程是不同的,编译
器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了
效率。

 

24 assert

断言assert 是仅在Debug 版本起作用的宏,它用于检查“不应该”发生的情况。示
例6-5 是一个内存复制函数。在运行过程中,如果assert 的参数为假,那么程序就会中
止(一般地还会出现提示对话,说明在什么地方引发了assert)。

【规则6-5-1】使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况
之间的区别,后者是必然存在的并且是一定要作出处理的。
z 【规则6-5-2】在函数的入口处,使用断言检查参数的有效性(合法性)。
z 【建议6-5-1】在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”
一旦确定了的假定,就要使用断言对假定进行检查。
z 【建议6-5-2】一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风格可
能会隐瞒错误。当进行防错设计时,如果“不可能发生”的事情的确发生了,则要
使用断言进行报警。

 24  内存

内存分配方式有三种:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的
整个运行期间都存在。例如全局变量,static 变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函
数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集
中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多
少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期
由我们决定,使用非常灵活,但问题也最多。

 

【规则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,防止产
生“野指针”。

 

25 计算内存容量
用运算符sizeof 可以计算出数组的容量(字节数)。示例7-3-3(a)中,sizeof(a)
的值是12(注意别忘了’\0’)。指针p 指向a,但是sizeof(p)的值却是4。这是因为
sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p 所指的内
存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。示例
7-3-3(b)中,不论数组a 的容量是多少,sizeof(a)始终等于sizeof(char *)

 

26 free 和delete

别看free 和delete 的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给
释放掉,但并没有把指针本身干掉。
用调试器跟踪示例7-5,发现指针p 被free 以后其地址仍然不变(非NULL),只是
该地址对应的内存是垃圾,p 成了“野指针”。如果此时不把p 设置为NULL,会让人误
以为p 是个合法的指针。
如果程序比较长,我们有时记不住p 所指的内存是否已经被释放,在继续使用p 之
前,通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if 语句起不到防错作用,
因为即便p 不是NULL 指针,它也不指向合法的内存块。

malloc 与free 是C++/C 语言的标准库函数,new/delete 是C++的运算符。它们都可
用于申请动态内存和释放内存。
对于非内部数据类型的对象而言,光用maloc/free 无法满足动态对象的要求。对象
在创建的同时要自动执行构造函数, 对象在消亡之前要自动执行析构函数。由于
malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数
和析构函数的任务强加于malloc/free。
因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个
能完成清理与释放内存工作的运算符delete。注意new/delete 不是库函数。

所以我们不要企图用malloc/free 来完成动态对象的内存管理,应该用new/delete。
由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free 和new/delete
是等价的。
既然new/delete 的功能完全覆盖了malloc/free,为什么C++不把malloc/free 淘
汰出局呢?这是因为C++程序经常要调用C 函数,而C 程序只能用malloc/free 管理动
态内存。
如果用free 释放“new 创建的动态对象”,那么该对象因无法执行析构函数而可能
导致程序出错。如果用delete 释放“malloc 申请的动态内存”,理论上讲程序不会出错,
但是该程序的可读性很差。所以new/delete 必须配对使用,malloc/free 也一样。

不行。如果发生“内存耗尽”这样的事情,一般说来应用程序已经无药可救。如果
不用exit(1) 把坏程序杀死,它可能会害死操作系统。道理如同:如果不把歹徒击毙,歹
徒在老死之前会犯下更多的罪。

有一个很重要的现象要告诉大家。对于32 位以上的应用程序而言,无论怎样使用
malloc 与new,几乎不可能导致“内存耗尽”。我在Windows 98 下用Visual C++编写了
测试程序,见示例7-9。这个程序会无休止地运行下去,根本不会终止。因为32 位操作
系统支持“虚存”,内存用完了,自动用硬盘空间顶替

27 malloc/free 的使用要点
函数malloc 的原型如下:
void * malloc(size_t size);
用malloc 申请一块长度为length 的整数类型的内存,程序如下:
int *p = (int *) malloc(sizeof(int) * length);

如果p 是NULL 指针,
那么free 对p 无论操作多少次都不会出问题。如果p 不是NULL 指针,那么free 对p
连续操作两次就会导致程序运行错误。

28  new/delete 的使用要点
运算符new 使用起来要比函数malloc 简单得多,例如:
int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];
这是因为new 内置了sizeof、类型转换和类型安全检查功能

在用delete 释放对象数组时,留意不要丢了符号‘[]’。例如
delete []objects; // 正确的用法
delete objects; // 错误的用法
后者相当于delete objects[0],漏掉了另外99 个对象。

 

posted @ 2009-07-12 23:44  thunderhao  阅读(107)  评论(0编辑  收藏  举报