15-赋值、比较和可能犯的错误
5.1.13 赋值、比较和可能犯的错误
不要混淆等于运算符(= =)与赋值运算符(=)。下面的表达式问了一个音乐问题——musicians是否等于4?
musicians == 4 // comparison
该表达式的值为true或false。下面的表达式将4赋给musicians:
musicians = 4 // assignment
在这里,整个表达式的值为4,因为该表达式左边的值为4。
for循环的灵活设计让用户很容易出错。如果不小心遗漏了= =运算符中的一个等号,则for循环的测试部分将是一个赋值表达式,而不是关系表达式,此时代码仍是有效的。这是因为可以将任何有效的C++表达式用作for循环的测试条件。别忘了,非零值为true,零值为false。将4赋给musicians的表达式的值为4,因此被视为true。如果以前使用过用=判断是否相等的语言,如Pascal或BASIC,则尤其可能出现这样的错误。
程序清单5.10中指出了可能出现这种错误的情况。该程序试图检查一个存储了测验成绩的数组,在遇到第一个不为20的成绩时停止。该程序首先演示了一个正确进行比较的循环,然后是一个在测试条件中错误地使用了赋值运算符的循环。该程序还有另一个重大的设计错误,稍后将介绍如何修复(应从错误中吸取教训,而程序清单5.10在这方面很有帮助)。
程序清单5.10 equal.cpp
// equal.cpp -- equality vs assignment
#include <iostream>
int main()
{
using namespace std;
int quizscores[10] =
{ 20, 20, 20, 20, 20, 19, 20, 18, 20, 20};
cout << "Doing it right:\n";
int i;
for (i = 0; quizscores[i] == 20; i++)
cout << "quiz " << i << " is a 20\n";
// Warning: you may prefer reading about this program
// to actually running it.
cout << "Doing it dangerously wrong:\n";
for (i = 0; quizscores[i] = 20; i++)
cout << "quiz " << i << " is a 20\n";
return 0;
}
由于这个程序存在一个严重的问题,读者可能希望了解它,而不是真正运行它。下面是该程序的一些输出:
Doing it right:
quiz 0 is a 20
quiz 1 is a 20
quiz 2 is a 20
quiz 3 is a 20
quiz 4 is a 20
Doing it dangerously wrong:
quiz 0 is a 20
quiz 1 is a 20
quiz 2 is a 20
quiz 3 is a 20
quiz 4 is a 20
quiz 5 is a 20
quiz 6 is a 20
quiz 7 is a 20
quiz 8 is a 20
quiz 9 is a 20
quiz 10 is a 20
quiz 11 is a 20
quiz 12 is a 20
quiz 13 is a 20
...
第一个循环在显示了前5个测验成绩后正确地终止,但第二个循环显示整个数组。更糟糕的是,显示的每个值都是20。更加糟糕的是,它到了数组末尾还不停止。最糟糕的是,该程序可能导致其他应用程序无法运行,您必须重新启动计算机。
当然,错误出在下面的测试表达式中:
quizscores[i] = 20
首先,由于它将一个非零值赋给数组元素,因此表达式始终为非零,所以始终为true。其次,由于表达式将值赋给数组元素,它实际上修改了数据。最后,由于测试表达式一直为true,因此程序在到达数组结尾后,仍不断修改数据。它把一个又一个20放入内存中!这会带来不好的影响。
发现这种错误的困难之处在于,代码在语法上是正确的,因此编译器不会将其视为错误(然而,由于C和C++程序员频繁地犯这种错误,因此很多编译器都会发出警告,询问这是否是设计者的真正意图)。
警告: 不要使用=来比较两个量是否相等,而要使用= =。
和C语言一样,C++比起大多数编程语言来说,赋予程序员更大的自由。这种自由以程序员应付的更大责任为代价。只有良好的规划才能避免程序超出标准C++数组的边界。然而,对于C++类,可以设计一种保护数组类型来防止这种错误,第13章提供一个这样的例子。另外,应在需要的时候在程序中加入保护措施。例如,在程序清单5.10的循环中,应包括防止超出最后一个成员的测试,这甚至对于“好”的循环来说也是必要的。如果所有的成绩都是20,“好”的循环也会超出数组边界。总之,循环需要测试数组的值和索引的值。第6章将介绍如何使用逻辑运算符将两个这样的测试合并为一个条件。