《深入浅出C++(第2版)》作者的原文:Top Ten Tips for Correct C++ Coding

1:不要搞混赋值(=)与相等测试(==)

尽管可能会难住福尔摩斯,但这一条确实太初级了。假如C++再像一点BASIC,那么下面的语句应该是无害的,而且可以正常编译运行:

if (a = b)
    cout << "a is equal b.";

由于表面上看不出有什么问题,因此这种写法在大程序中就会造成难以追踪的逻辑错误——除非你有意寻找。(我在调试程序时,首先会查这种问题。)在C和C++中,下面的表达式并非在测试两个变量是否相等:

a = b

当然,这个表达式的意思是把b的值赋给a,然后再求出所赋的值。

问题在于,对a = b求值的结果通常得不到合理的true/false条件——稍后我会提到一个明显的例外。但在C和C++中,任何数值都可以用作if或while语句的条件。

假设a和b的值都是0。前面那条if语句的作用就是把b的值放到a中,然后对表达式a = b求值得到0,而0等同于false。结果,a和b本来相等,但打印出来的消息却错误的:

if (a = b) //这个条件本来是用于确保a和b相等的
    cout << "a and b are equal.";
else
    coun << "a and b are not equal."; //但打印出来的是这个消息

解决方案当然是有意识地使用相等测试。注意相等测试使用两个等号,这样的条件才是正确的:

//正确的写法
if (a == b)
    cout << "a and b are equal.";

2:别用“魔法数字”

所谓魔法数字,指的是在程序里突然出现一个数字,但没有任何解释。大多数老到的程序员都愿意在程序里看到像MAX_ROWS、SCREEN_WIDTH这样有意义的符号名。

简言之,专业的计算机程序员——包括一些对数学情有独钟的人,都很讨厌数字。

了解一点历史有助理解为什么。那是在1940年代,所有人编程使用的都是机器码,程序全都是位模式。生活在十八层地狱第十八层的程序员必须不断地翻译这些模式。而当汇编语言中引入了符号名之后,编程一下子简单了一千倍。

即使是到了今天,程序员们也不喜欢类似这样的声明:

char input_string[81];

这里的81就是一个“魔法数字”。这个数是从哪儿来的?更好的做法是使用#define或enum语句来控制数字的使用。

#define SCREEN_WIDTH 80

SCREEN_WIDTH比81更好理解,如果你想将来重置这个宽度,只要修改一行代码即可。而且修改之后就能够自动地反映到类似下面这样的语句当中:

int input_string[SCREEN_WIDTH + 1];

3:别太依赖整数除法(除非你就想那么做)

在不想保存分数的情况下,整型(在C/C++语系中是int,以及short、long和现在的long long)是比较好的选择———但我们这里不谈小数。

不过,有时候整数在位于一个长表达式中时也会牵扯到小数。下面就是我的书《深入浅出C++(第2版)》(C++ Without Fear)中的一行代码:

cout << results / (n / 10);

这个程序用于生成0到9之间的随机数,每个数字出现的概率应该是1/10。这里是用每个数字实际出现的次数除以期望它出现的次数。比如,如果3的results(出现的实际次数)是997,而总共进行了10 000次测试,那么就是用997除以期望它出现的次数:1000。

此时的results、n和10都是整数。因此,997除以1000后得到的是0!

……等等,我们觉得应该得0.997啊。怎么回事?

整数除法向下舍入,于是会得到一个完美的整数。余数呢?扔了。好像也不是完全没有道理。C++提供了两种 独立的除法操作:除和求余。

int dividend = n / m;   //保存比例
int remainder = n % m;  //保存余数

噢——对了,前面那行代码中使用了一个“魔法数字”——10,我晕~$#@&^!好了,下一条要诀我们就聊聊这个更麻烦的事儿:数据丢失。

4:记住利用数据提升来控制结果

在混有整数和浮点数的表达式中,整数运算数会被提升为double型。因此,下面的表达式可以得到我们想要的结果:

cout << results / (n / 10.0);

注意,虽然并没有小数,但这里用了10.0,就是为了把它保存为double型。这样,C++就会把n和results也提升为double型,最终达到执行浮点除法的目的:

另一种达到目的的方法是转型数据:

cout << results / (n / (double) 10);
cout << results / (n / static_cast<double>(10));

5:不要使用非布尔条件(除非你会留意)

C语言最初是为了编写操作系统而设计的,因而它赋予了程序员很大的自由,不仅可以在机器码的层面上操作数据(通过使用指针),还可以写出快捷方法。快捷方法对于初学者是很危险的,但对于知道该怎么用的程序员来说,有时候则非常可心。但愿他们心甘情愿活在危险之中。

比如,下面是“做N次……”的一种最优雅的快捷写法:

while (n--){
    do_something();
}

实际上还可以写得更短:

while (n--) do_something();

同样,无论怎样写,都是基于一条相同的规则:C和C++中的任何数值都可以作为条件来用。当每次循环递减1,每次循环递减1,直到n变成0时,循环结束。不过,这里有问题:如果n一开始就是负值呢?那刚才看到的那个循环就会永远循环下去,至少是可以循环到内存溢出之前的最小负值。这会破坏你的好心情的。

一般来说,只有严格意义上的Boolean(也就是true或false)表达式才能用作条件:

while (n-- > 0){
    do_something();
}

不过也有一个例外。当某个操作失败时,将指针设置为NULL(也就是0),那这个简写方式就能起作用。实际上,NULL意味着false。在链表结构中,空指针也可以用来表示列表的结束,就是让它的next_node成员指向不存在的地方(nowhere;1950年和1960年的时候,说是指向Nowheresville)。在下面这种情况下,空指针意味着打开文件失败:

if (! file_pointer){
    cout << "File open failed.";
    return ERROR_CODE;
}

提醒一句,你应该在这个条件中使用一个赋值语句:

if (! (file_pointer = open_the_file(args)){
    cout << "File open failed.";
    return ERROR_CODE;
}

(To be continued!)

本文参加 Translate Geeks to Chinese 翻译活动