预计阅读本页时间:-
因为while循环经常要依赖于进行比较的判断表达式,所以比较表达式值得我们进一步研究。这样的表达式称为关系表达式(relational expression),其中出现的运算符称为关系运算符(relational operator)。您已经使用过了一些,表6.1列出了C中的关系运算符的完整列表。这个表覆盖了数值关系的所有可能性。
表6.1 关系运算符
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
运算符 | 含 义 |
---|---|
< | 小于 |
<= | 小于或等于 |
== | 等于 |
>= | 大于或等于 |
> | 大于 |
!= | 不等于 |
关系运算符用来构成在while语句和我们将要讨论到的其他C语句中使用的关系表达式。这些语句检查表达式为真还是为假。下面是包含了关系表达式实例的三个不相关的语句。它们的意思是显而易见的。
在第二个例子中,请注意关系表达式也可以用于字符的比较。进行比较时使用的是机器的字符代码(我们假定为ASCII)。然而,不能使用关系运算符来比较字符串。第11章“字符串和字符串函数”将介绍如何对字符串进行比较。
关系运算符也可以用于浮点数。但要小心,在浮点数比较中只能使用<和>。原因在于舍入误差可能造成两个逻辑上应该相等的数不相等。例如,3和1/3的乘积应该是1.0。但是如果您用6位小数来表示1/3,乘积就是.999999而不等于1。使用在math.h头文件中声明的fabs()函数可以方便地进行浮点数判断。这个函数返回一个浮点值的绝对值(即没有代数符号的值)。例如,您可以使用类似程序清单6.5的方法来判断一个数是否接近一个想要的结果。
程序清单6.5 cmpflt.c程序
在用户的答案与正确值的误差小于0.0001之前,这个循环反复地请求输入答案:
What is the value of pi?
3.14
Try again!
3.1416
Close enough!
每个关系表达式都被判定为真或假(永远没有也许);这引起了一个有趣的问题。
6.3.1 什么是真
您可以回答这个古老的问题,至少对于C是如此。回忆一下,C的表达式通常具有一个值。像程序清单6.6显示的那样,即使对关系表达式也是如此。在这个程序中,您打印了两个关系表达式的值,一个为真,一个为假。
程序清单6.6 t_and_f.c程序
程序清单6.6把两个关系表达式的值赋给两个变量。直接地说,它把一个真表达式的值赋为true_val,把一个假表达式的值赋为false_val。运行这个程序会产生下列简单的输出:
true = 1:false = 0
原来,对C来说,一个真表达式的值为1,而一个假表达式的值为0。确实,有些C程序使用以下的循环结构,这意味着它会永远运行,因为1永远为真:
6.3.2 还有什么是真
既然可以使用1或0来作为while语句的判断表达式,那么还可以使用其他数字吗?如果可以,会发生什么?我们试试程序清单6.7中的程序来做个实验。
程序清单6.7 truth.c程序
下面是结果:
3 is true
2 is true
1 is true
0 is false
-3 is true
-2 is true
-1 is true
0 is false
第一个循环在n为3、2和1时得到执行,而在n为0时结束。类似地,第二个循环在n为-3、-2和-1时得到执行,而在n为0时结束。更一般地,所有的非零值都被认为是真,只有0被认为是假。C对真的范围放得非常宽!
可以说只要while循环的判断条件的值非零,它就可以执行循环。这使得判断条件是建立在数值的基础上而不是在真/假的基础上。要谨记如果关系表达式为真,它的值就为1;如果为假,它的值就为0。因此这样的表达式实际上是数值的。
很多C程序员对判断条件的这一属性加以利用。例如,while(goats!=0)语句可以被while(goats)代替,因为表达式(goats!=0)和(goats)都是只有在goats的值为0时才为0或假。第一种形式可能对那些刚学这种语言的人来说更清楚一些,但是第二种形式是C程序员最常使用的。您应该努力去熟悉while(goats)这样的形式,使它对您来说看上去是自然的。
6.3.3 真值的问题
C对真值的范围放得很宽,这可能引起一些问题。例如,我们对程序清单6.1的程序做一些细微的更改,就产生了程序清单6.8中的程序。
程序清单6.8 trouble.c程序
程序清单6.8产生了以下这样的输出:
Please enter an integer to be summed(q to quit): 20
Please enter next integer(q to quit): 5
Please enter next integer(q to quit): 30
Please enter next integer(q to quit): q
Please enter next integer(q to quit):
Please enter next integer(q to quit):
Please enter next integer(q to quit):
Please enter next integer(q to quit):
(……等等,直到您强行关闭这个程序。所以也许您不应该实际运行这个例子)。
这个麻烦的例子改变了while的判断条件,用status=1代替了status==1。前一个表达式是一个赋值语句,它把status赋值为1。而且赋值表达式的值就是其左侧的值,这样status=1的值也为1。因此,实际上这个while循环就等于是使用了while(1),也就是说循环永远不会退出。输入q,status被设置为0,但是循环在判断时又把status重置为1并开始另一次循环。
您可能感到迷惑,因为程序将保持循环,并且用户在输入q之后根本没有机会进行更多的输入。当scanf()未能读取指定形式的输入时,它就留下这个不相容的输入,以供下次进行读取。当scanf()试着把q作为整数读取并失败时,它就把q留在那里。在下次循环中读取前面留下来的q时,scanf()再次失败。所以这个例子不但建立了一个无限循环的例子,它也建立了一个无限失败的循环,这是一个可怕的概念。幸运的是计算机目前尚未具有感情。对计算机来说,无限地执行愚蠢的指令与成功地预测未来10年的股票市场没有什么区别。
不要在应该使用==的地方使用=。的确,有些计算机语言(例如BASIC)为赋值运算符和关系等于运算符使用相同的符号,但这两个运算符有很大的差别(请参见图6.2)。赋值运算符把一个值赋给左边的变量,而关系等于运算符检查左边与右边的值是否相等,它并不改变左边变量的值(如果左边是一个变量)。
图6.2 关系运算符==和赋值运算符=
canoes = 5 | 把canoes赋值为5 |
canoes == 5 | 检查canoes的值是否为5 |
要确保使用正确的运算符。编译器允许您使用错误的形式,产生您不希望的结果(但是太多的人错误地使用=,以致于今天的大多数编译器都会产生一个警告以提示可能您的意思不是要做这个)。如果进行比较的双方中有一个是常量,则可以把它放在比较表达式的左边,这样做有助于发现错误:
5 = canoes | 语法错误 |
5 == canoes | 检查canoes的值是否为5 |
关键之处在于为常量赋值是非法的,所以编译器可以把赋值运算符的这种用法识别为语法错误。很多程序员在构建相等判断表达式时都习惯把常量放在前面。
总之,关系运算符被用来构成关系表达式。关系表达式在为真时值为1,为假时值为0。通常使用关系表达式作为判断条件的语句(例如while和if)可以使用任何表达式作为判断,非零值被认为是“真”,而零值被认为是“假”。
6.3.4 新的_Bool类型
在C中,表示真/假的变量一直是由int类型来表示的。C99专门为这种类型的变量添加了_Bool类型。这种类型是以英国数学家George Boole的名字来命名的,他开发了用代数来表示并解决逻辑问题的系统。在编程领域,表示真或假的变量开始时被称为布尔变量(Boolean variable)。这样_Bool就是布尔变量的C类型名。一个_Bool变量只可以具有值1(真)或0(假)。如果您把一个_Bool变量赋为一个非零的数值,变量就被设置为1。这说明C把任何非零的值都认为是真。
程序清单6.9纠正了程序清单6.8中的判断条件,并用_Bool变量input_is_good来代替int变量status。通常习惯为布尔变量取一个表明真或假值的名字。
程序清单6.9 boolean.c程序
注意代码是如何把比较的结果赋值给变量的:
input_is_good=(scanf(“%ld”,&num)==1);
这是有意义的,因为==运算符的返回值为1或0。顺便说一句,把==表达式括起来的圆括号不是必需的,因为==运算符的优先级比=要高,但是它们可以使代码更容易阅读。同时也要注意变量名称的选择使while循环判断更容易理解了:
while(input_is_good)
C99还提供了一个stdbool.h头文件。包含这个头文件可以使用bool来代替_Bool,并把true和false定义成值为1和0的符号常量。在程序中包含这个头文件可以写出与C++兼容的代码,因为C++把bool、true和false定义为关键字。
如果您的系统还不支持_Bool类型,则可以使用int来代替_Bool,这个例子会进行同样的工作。
6.3.5 关系运算符的优先级
关系运算符的优先级要低于包括+和-在内的算术运算符,但是要高于赋值运算符。这意味
x>y+2
也意味着:
x> (y+2)
也就是说:
x=y>2
意味着:
x=(y>2)
换句话说,如果y大于2,x为1;否则x为0。就是并没有把y的值赋给X。
关系运算符比赋值运算符的优先级要高,所以
x_bigger=x>y;
意味着:
x_bigger=(x>y):
关系运算符本身也分为两组不同的优先级。
高优先级的组: | < <= > >= |
低优先级的组: | == != |
像大多数其他的运算符一样,关系运算符从左到右进行结合。这样:
ex != wye == zee
就等于:
(ex != wye) == zee
C首先检查ex与wye的值是否不相等;然后结果值1或0(真或假)再与zee的值进行比较。我们不希望您使用这种结构,但是有必要对其进行说明。
表6.2显示了迄今为止学习过的运算符的优先级,参考资料2“C运算符”有全部运算符的完整优先级列表。
表6.2 运算符优先级
运算符(优先级从高到低) | 结合性 |
---|---|
() | 从左到右 |
-+ ++ --sizeof(type)(所有的一元运算符) | 从右到左 |
* / % | 从左到右 |
+ - | 从左到右 |
< > <= >= | 从左到右 |
== != | 从左到右 |
= | 从右到左 |