《高质量-C++-C-编程指南v-1-0》-总结(中)
第 4 章 表达式和基本语句
【规则 4-1-1 】如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级
- 加了增强理解, 即使看上去加了等于没加
- 加了可避免一些不希望使用默认的优先级的行为
【规则 4-2-2 】不要有多用途的复合表达式。
例如:
d = (a = b + c) + r ;
该表达式既求 a 值又求 d 值。应该拆分为两个独立的语句:
a = b + c;
d = a + r;
浮点变量与零值比较
【规则 4-3-3 】不可将浮点变量用“”或“!=”与任何数字比较。
千万要留意,无论是 float 还是 double 类型的变量,都有精度限制。所以一定要避
免将浮点变量用“”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。
假设浮点变量的名字为 x,应当将
if (x == 0.0) // 隐含错误的比较
转化为
if ((x>=-EPSINON) && (x<=EPSINON))
其中 EPSINON 是允许的误差(即精度)。
4.3.5 对 if 语句的补充说明
有时候我们可能会看到if (NULL == p)
这样古怪的格式。不是程序写错了,是程序员为了将 if (p == NULL) 防止误写成 if (p = NULL),而有意把 p 和 NULL 颠倒。编译器认为 if (p = NULL) 是合法的,但是会指出 if (NULL = p)是错误的,因为 NULL不能被赋值。
【建议 4-4-1 】在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的
循环放在最外层,以减少 CPU 跨切循环层的次数。例如示例 4-4(b)的效率比示例
4-4(a)的高。
【规则 4-6-2】 】不要忘记最后那个 default 分支。即使程序真的不需要 default 处理,也应该保留语句 default : break; 这样做并非多此一举,而是为了防止别人误以为你忘了 default 处理。
goto 语句: 很多人建议废除 C++/C 的 goto 语句,以绝后患。但实事求是地说,错误是程序员自己造成的,不是 goto 的过错。goto 语句至少有一处可显神通,它能从多重循环体中咻地一下子跳到外面,用不着写很多次的 break 语句
第 5 章 常量
为什么需要常量
如果不使用常量,直接在程序中填写数字或字符串,将会有什么麻烦?
(1) 程序的可读性(可理解性)变差。程序员自己会忘记那些数字或字符串是什么意
思,用户则更加不知它们从何处来、表示什么。
(2) 在程序的很多地方输入同样的数字或字符串,难保不发生书写错误。
(3) 如果要修改数字或字符串,则会在很多地方改动,既麻烦又容易出错。
【规则 5-2-1 】在 C++ 程序中只使用 const 常量而不使用宏常量,即 const 常量完全取代宏常量。
第 6 章 函数设计
参数的规则
【规则 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);
那么我们很难搞清楚究竟是把 str1 拷贝到 str2 中,还是刚好倒过来。
可以把参数名字起得更有意义,如叫 strSource 和 strDestination。这样从名字上就可以看出应该把 strSource 拷贝到 strDestination。
还有一个问题,这两个参数那一个该在前那一个该在后?参数的顺序要遵循程序员的习惯。一般地,应将目的参数放在前面,源参数放在后面。
如果将函数声明为:
void StringCopy(char *strSource, char *strDestination);
别人在使用时可能会不假思索地写成如下形式:
StringCopy(str, “Hello World”); // 参数顺序颠倒
【规则 6-1-3 】如果参数是指针,且仅作输入用,则应在类型前加 const,以防止该指针在函数体内被意外修改。
【建议 6-1-1 】避免函数有太多的参数,参数个数尽量控制在 5 个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。
【规则 6-2-3 】不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用 return 语句返回。
我们在实际工作中,经常会碰到上述令人为难的问题。为了避免出现误解,我们应该将正常值和错误标志分开。即:正常值用输出参数获得,而错误标志用 return 语句返回。
函数 getchar 可以改写成BOOL GetChar(char *c);
虽然 gechar 比 GetChar 灵活,例如 putchar(getchar()); 但是如果 getchar 用错了,它的灵活性又有什么用呢?【建议 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-4-1 】函数的功能要单一,不要设计多用途的函数。
【建议 6-4-2 】函数体的规模要小,尽量控制在 50 行代码之内。
【建议 6-4-3 】尽量避免函数带有“记忆”功能。相同的输入应当产生相同的输出。
带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某
种“记忆状态”。这样的函数既不易理解又不利于测试和维护。在 C/C++语言中,函数的static 局部变量是函数的“记忆”存储器。建议尽量少用 static 局部变量,除非必需。
使用断言
【规则 6-5-1 】使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况
之间的区别,后者是必然存在的并且是一定要作出处理的【建议 6-5-2 】一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风格可能会隐瞒错误。当进行防错设计时,如果“不可能发生”的事情的确发生了,则要使用断言进行报警。
引用与指针的比较
引用的一些规则如下:
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
(2)不能有 NULL 引用,引用必须与合法的存储单元关联(指针则可以是 NULL)。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
对比上述三个示例程序,会发现“引用传递”的性质象“指针传递”,而书写方式象“值传递”。实际上“引用”可以做的任何事情“指针”也都能够做,为什么还要“引用”这东西?
答案是**“用适当的工具做恰如其分的工作”。**
指针能够毫无约束地操作内存中的如何东西,尽管指针功能强大,但是非常危险。
就象一把刀,它可以用来砍树、裁纸、修指甲、理发等等,谁敢这样用?
如果的确只需要借用一下某个对象的“别名”,那么就用“引用”,而不要用“指针”,
以免发生意外。比如说,某人需要一份证明,本来在文件上盖上公章的印子就行了,如果把取公章的钥匙交给他,那么他就获得了不该有的权利。