4.2 条件
程序通过测试关键变量的值从备选语句中进行选择。例如,人的心脏健康的标志之一是安静状态的心跳速率。通常安静状态的心跳速率等于或小于每分钟75次表明心脏健康,但超过每分钟75次则表明可能有问题。
如果rest_heart_rate是int类型的变量,表达式
rest_heart_rate > 75
执行必要的比较,当rest_heart_rate超过75时取值为1(true);如果rest_heart_rate不超过75,表达式取值为0(false)。由于这样的表达式建立了执行或跳过一组语句的标准,它被称为条件。
条件(condition) 值为假(用0表示)或真(通常用1表示)的表达式。
4.2.1 关系运算符和判等运算符
绝大多数用来执行比较的条件具有以下形式之一:
变量 关系运算符 变量
变量 关系运算符 常量
变量 判等运算符 变量
变量 判等运算符 常量
表4-1列出了关系运算符和判等运算符。
表4-1 关系运算符和判等运算符
|
运算符 |
含 义 |
类 型 |
|
< |
小于 |
关系运算符 |
|
> |
大于 |
关系运算符 |
|
<= |
小于等于 |
关系运算符 |
|
>= |
大于等于 |
关系运算符 |
|
== |
等于 |
判等运算符 |
|
!= |
不等于 |
判等运算符 |
例4-1 表4-2显示了C语言的一些示例条件。每个条件的取值以下面这些变量和常量宏的假定值为基础:
|
x |
power |
MAX_POW |
y |
item |
MIN_ITEM |
mom_or_dad |
num |
SENTINEL |
||||||||
|
-5 |
1024 |
1024 |
7 |
1.5 |
-999.0 |
'M' |
999 |
999 |
表4-2 示例条件
|
运算符 |
条 件 |
含 义 |
值 |
|
<= |
x < = 0 |
x小于或等于0 |
1(true) |
|
< |
power < MAX_POW |
power小于MAX_POW |
0(false) |
|
>= |
x >= y |
x大于或等于y |
0(false) |
|
> |
item > MIN_ITEM |
item大于MIN_ITEM |
1(true) |
|
== |
mom_or_dad == 'M' |
mom_or_dad 等于'M' |
1(true) |
|
!= |
num != SENTINEL |
num不等于SENTINEL |
0(false) |
4.2.2 逻辑运算符
使用&&(与)、||(或)、!(非)这三个逻辑运算符可以组成更复杂的条件或逻辑表达式。以下是由这些运算符组成的逻辑表达式。
逻辑表达式(logical expression) 包含一个或多个逻辑运算符&&(与)、||(或)、!(非)的表达式。
salary < MIN_SALARY || dependents > 5
temperature > 90.0 && humidity > 0.90
n >= 0 && n <= 100
0 <= n && n <= 100
第一个表达式确定员工是否符合奖学金的条件。如果条件
salary < MIN_SALARY
或条件
dependents > 5
成立,它取值为1(true)。第二个逻辑表达式描述了一个无法忍受的夏日,温度和湿度都超过了90。只有这两个条件都为true时表达式的取值才为true。最后两个表达式是等价的,并且当n在0和100之间(包括0和100)时取值为true。
第三个逻辑运算符!(非)只有一个操作数,其结果为操作数的逻辑补或非(即,如果变量positive非零(true),!positive为0(false),反之亦然)。逻辑表达式
!(0 <= n && n <= 100)
是上面列出的最后一个表达式的非。当n不在0和100之间(包括0和100)时取值为1(true)。
逻辑补(非)(logical complement/negation) 当条件的值为0(false)时,条件的逻辑补为1(true);当条件的值为非零(true)时,条件的逻辑补为0(false)。
表4-3表明只有当两个操作数都为true时,&&运算符(与)得到的结果才为true。表4-4表明只有当两个操作数都为false时,||运算符(或)得到的结果才为false。表4-5给出了!运算符(非)。
表4-3 &&运算符(与)
|
操作数1 |
操作数2 |
操作数1 && 操作数2 |
|
非零(true) |
非零(true) |
1(true) |
|
非零(true) |
0(false) |
0(false) |
|
0(false) |
非零(true) |
0(false) |
|
0(false) |
0(false) |
0(false) |
表4-4 ||运算符(或)
|
操作数1 |
操作数2 |
操作数1 || 操作数2 |
|
非零(true) |
非零(true) |
1(true) |
|
非零(true) |
0(false) |
1(true) |
|
0(false) |
非零(true) |
1(true) |
|
0(false) |
0(false) |
0(false) |
表4-5 !运算符(非)
|
操作数1 |
!操作数1 |
|
非零(true) |
0(false) |
|
0(false) |
1(true) |
表4-3到表4-5表明,当C语言对逻辑表达式求值时结果总是为0或1。但是,C语言认为任何非零值代表true。目前,当需要值true时,总是用整数1表示,但了解C语言实际上如何看待逻辑表达式有助于理解为什么某些常见错误不会被C编译器当作语法错误。
4.2.3 运算符优先级
运算符的优先级决定了它的求值顺序。表4-6按从高到低的顺序列出了目前为止所有C运算符的优先级。
表4-6 运算符优先级
|
运算符 |
优先级 |
|||
|
函数调用 |
高
低 |
|||
|
! + - & (一元运算符) |
||||
|
* / % |
||||
|
+ - |
||||
|
< <= >= > |
||||
|
== != |
||||
|
&& |
||||
|
|| |
||||
|
= |
这个表格表明函数调用最先求值。其次是具有单个操作数的一元运算符!(非)、+(正号)、–(负号)和&(取地址)求值。接下来是所有的二元运算符:算术运算符、关系运算符、判等运算符和逻辑运算符(&&然后是||)。赋值运算符(=)最后求值。需要注意的是,运算符+和-的优先级取决于它们具有一个还是两个操作数。在表达式
–x – y * z
中,一元运算符减号首先求值(–x),然后是*,接下来是第二个–。
一元运算符(unary operator) 拥有一个操作数的运算符。
可以使用小括号来改变求值顺序。如果从表达式
(x < y || x < z) && x > 0.0
中去掉小括号,C语言会在||之前先对&&求值,因而会改变表达式的意义。
也可以使用小括号来使表达式的意义更清晰。如果x、min和max为double类型,C编译器将把表达式
x + y < min + max
正确地解释为
(x + y) < (min + max)
这是因为+的优先级比<的优先级高,但第二种表达式更加清晰。
例4-2 下面的表达式1~4包含了不同的操作数和运算符。每个表达式的值在对应的注释中给出,假设x、y和z为double类型,flag是int类型,并且这些变量具有以下的值:
![]()
1. !flag /* !0 is 1 (true) */
2. x + y / z <= 3.5 /* 5.0 <= 3.5 is 0 (false) */
3. !flag || (y + z >= x- z) /* 1 || 1 is 1 (true) */
4. !(flag || (y + z >= x- z)) /* !(0 || 1) is 0 (false) */
图4-1给出了表达式3的求值树和逐步求值。
|
|
|
图4-1 表达式!flag || (y + z >= x – z)的求值树和逐步求值 |
4.2.4 短路求值法
尽管图4-1展示了对整个逻辑表达式求值,但C语言只对表达式的一部分求值。如果a为true,a || b形式的表达式必定为true。因此,当确定!flag的值为1(true)时,C语言停止对表达式求值。类似地,如果a为false,a && b形式的表达式必定为false,因此,如果这样的表达式的第一个操作数求值为0,C语言将停止对表达式求值。只要逻辑表达式的值可以确定就停止对表达式求值的技术称为短路求值法。
短路求值法(short-circuit evaluation) 只要逻辑表达式的值能够确定就停止对表达式求值。
4.2.5
用C语言表示条件
要解决编程问题,必须将用英语表示的条件表达式转换为用C语言表示。许多算法步骤需要检测某个变量的值是否位于指定的取值范围内。例如,如果min表示取值范围的下限,max表示取值范围的上限(min小于max),表达式
min <= x && x <= max
检测x是否位于min到max的范围内,包括上下限。在图4-2中,这个取值范围用阴影表示。如果x位于该范围内,表达式值为1(true);如果x不在这个范围内表达式值为0(false)。
例4-3 表4-7给出了某些英语条件和对应的C语言表达式。每个表达式求值时都假定x为3.0,y为4.0,z为2.0。
表4-7 用C语言表达式表示英语条件
|
英语条件 |
逻辑表达式 |
求 值 |
|
x and y are greater than z |
x > z && y >z |
1 && 1 is 1 (true) |
|
x is equal to 1.0 or 3.0 |
x == 1.0 || x== 3.0 |
0 || 1 is 1 (true) |
|
x is in the range z to y, inclusive |
z <=x && x <= y |
1 && 1 is 1 (true) |
|
x is outside the range z to y |
!( z <=x && x <= y) z > x || x > y |
!(1 && 1) is 0 (false) 0 || 0 is 0 (false) |
第一个逻辑表达式给出了英语条件“x and y are greater than z”的C语言代码。读者可能会试图将这个表达式写作
x && y > z /* invalid logical expression */
但是,如果将优先级规则应用到这个表达式,很快会发现它不具有预期的含义,并且double类型的变量x对逻辑运算符&&而言是无效的操作数。
第三个逻辑表达式给出了数学关系的C语言代码。使结果为true的x取值范围包含边界值2.0和4.0。
当x位于z和y作为边界的取值范围之外时,最后一个表项给出的一对逻辑表达式为true。这一对表达式中的第一个表达式正好是上一个表达式取反。第二个表达式表明如果z大于x或者x大于y时x在取值范围之外。图4-3中的阴影区域表示使表达式结果为true的x取值。y和z都被排除在使表达式为true的值集之外。

图4-3 表达式z > x || x > y 值为true的取值范围
4.2.6 比较字符
在C语言中也可以使用关系运算符和判等运算符来比较字符。表4-8给出了这类比较的一些例子。
表4-8 字符比较
|
表 达 式 |
值 |
|
'9' >= '0' |
1(true) |
|
'a' < 'e' |
1(true) |
|
'B' <= 'A' |
0(false) |
|
'Z' == 'z' |
0(false) |
|
'a' <= 'A' |
与系统有关 |
|
'a' <= ch && ch <= 'z' |
如果ch是小写字母,值为1(true) |
表4-8的前三行表明数字字符和字母按预期的顺序排序(即'0' < '1' < ... < '8' < '9'并且'a' < 'b' < 'c'... < 'y' < 'z')。接下来的两行表明同一个字母的小写和大写具有不同的值,并且它们的顺序和系统有关。最后一行表明当ch是小写字母时表达式值为true。(在某些系统上,对于某些不是小写字母的字符,这个表达式值也是true。)
4.2.7 逻辑赋值
C语言中逻辑表达式最简单的形式是表示值true或false的一个int类型的值或变量。可以使用赋值语句将这些变量设置为true(非零值)或false(0)。
例4-4 给出以下声明
int age ; /* input - a person' s age */
char gender ; /* input - a person' s gender */
int senior_citizen; /* logical - indicates senior status */
假设变量senior_citizen的值为1表示这个人是一位老人(65岁及以上)。可以使用赋值语句
senior_citizen = 1; /* Set senior status */
将senior_citizen设置为true。
可能性更大的方案是根据读入age变量的值来设置senior_citizen的值:
scanf ( "%d", &age) ; /* Read the person' s age */
senior_citizen = (age >= 65); /* Set senior status */
在上面的赋值中,小括号中的条件首先求值。如果读入age的值大于等于65,它的值为1(true)。因此,当age满足条件时senior_citizen的值为true,否则为false。
逻辑运算符&&、||和!可以被应用于senior_citizen。当age小于65时,表达式
! senior_citizen
为1(true)。最后,如果senior_citizen为1(true)并且gender中的字符是M,逻辑表达式
senior_citizen && gender == 'M'
值为1(true)。
例4-5 以下的赋值语句给两个int类型的变量in_range和is_letter赋值。如果n的值在-10和10之间(包括-10和10),变量in_range为1(true);如果ch是大写或小写字母,变量is_letter为1(true)。
in_range = (n > -10 && n < 10 );
is_letter = ('A' <= ch && ch <= 'Z') ||
('a' <= ch && ch <= 'z');
如果n满足列出的两个条件(n大于-10并且n小于10),第一条赋值语句中的表达式为true,否则表达式为false。
第二条赋值语句中的表达式使用了逻辑运算符&&和||。如果ch是大写字母,||之前的子表达式为true;如果ch是小写字母,||之后的子表达式为true。因此,只要任何一个子表达式为true(即ch是一个字母),is_letter为1(true);否则is_letter为0(false)。删除小括号不会影响运算符的求值顺序。
例4-6 下面的语句在n是偶数时给even(int类型)赋值1(true):
even = (n % 2 == 0);
由于所有的偶数都被2整除,当n是偶数时,n除以2的余数(C语言中用n % 2表示)是0。小括号中的表达式比较余数和0,当余数是0时它的值为1(true),当余数非零时它的值为0(false)。
4.2.8 条件取反
我们已经知道如何通过在表达式前放置符号!将逻辑表达式取反。还可以通过调整运算符对一个简单表达式取反。
例4-7 条件
item == SENT
取反的形式是
!(item == SENT) 或者 item != SENT
右边这种形式是通过调整运算符得到的(将==变为!=)。
通常,改变相等性或关系运算符来对简单条件取反非常容易。关系运算符<=应该变为>,<应该变为>=,等等。对较复杂的表达式使用!取反。
例4-8 对于一个单身且超过25岁的人而言,条件
status == 'S' && age>25
为true。这个条件取反是
!( status == 'S' && age>25)
4.2.9 德摩根定理
德摩根定理给出了一种简化上述表达式的方法。德摩根定理指出:
l 对expr1 && expr2 取反写作comp1 || comp2,其中comp1是对expr1取反,comp2是对expr2取反。
l 对expr1 || expr2 取反写作comp1 && comp2,其中comp1是对expr1取反,comp2是对expr2取反。
使用德摩根定理,对
age >25 && (status == 'S' || status == 'D')
取反可以写作
age <= 25 || (status != 'S' && status != 'D')
对于任何超过25岁并且是单身或离婚的人,最初的条件为true。对于任何小于或等于25岁或者已经结婚的人,对最初条件取反的值为true。
练习
自测
1. 假设x是15.0且y是25.0,下列条件的值是什么?
x !=y
x < x
x >= y – x
x == y + x – y
2. 如果a是5,b是10,c是15,flag是1,对以下表达式求值。根据短路求值法,这些表达式中的哪些部分不会被求值?
a. c == a + b || !flag
b. a != 7 && flag || c >= 6
c. !(b <= 12) && a % 2 == 0
d. !(a >5 || c < a + b)
3. 给出例4-2中表达式4的逐步求值。
4. 对自测题2中的每个表达式取反,在适当时使用德摩根定理。
5. 在下面的语句中,如果p的值是100且q的值是50,int类型变量ans被赋予什么值?
ans = (p > 95) + (q < 95);
这条语句不是一个合理的赋值语句的例子,而是对读者没有什么意义的示例语句。但是,由于C语言用整数表示逻辑值true和false,这条语句在C语言中仍然合法并且能够执行。
编程
1. 编写测试下列每个关系的表达式
a. age从18到21(包括18和21)。
b. water小于1.5且大于0.1。
c. year被4整除(提示:使用%)。
d. speed不大于55。
e. y大于x并小于z。
f. w等于6或者不大于3。
2. 编写以下赋值语句
a. 如果n在-k到+k范围内(包括-k和+k),给between赋值1,否则赋值0。
b. 如果ch是大写字母给uppercase赋值1,否则赋值0。
c. 如果m是n的约数给divisor赋值1,否则赋值0。








