3.4 关系运算符
关系运算符(也称比较运算符)检验两个操作数的值是否满足某种关系。关系成立时,表达式的结果是 int 类型的 1;不成立时,结果是 0。if 语句和 while 循环的条件中经常使用这些运算符。
| 运算符 | 名称 | 示例 | 描述 |
|---|---|---|---|
== | 等于 (Equal to) | a == b | 如果 a 的值等于 b,则为真 (1) |
!= | 不等于 (Not equal to) | a != b | 如果 a 的值不等于 b,则为真 (1) |
> | 大于 (Greater than) | a > b | 如果 a 的值大于 b,则为真 (1) |
< | 小于 (Less than) | a < b | 如果 a 的值小于 b,则为真 (1) |
>= | 大于或等于 (Greater than or equal to) | a >= b | 如果 a 的值大于或等于 b,则为真 (1) |
<= | 小于或等于 (Less than or equal to) | a <= b | 如果 a 的值小于或等于 b,则为真 (1) |
1. 相等性运算符 (==, !=)
== 判断两个操作数的值是否相等,!= 判断两个操作数的值是否不等。
int a = 5;
int b = 5;
int c = 10;
int result1 = (a == b); // a 和 b 的值相等,result1 的值为 1 (真)
int result2 = (a == c); // a 和 c 的值不相等,result2 的值为 0 (假)
int result3 = (a != c); // a 和 c 的值不相等,result3 的值为 1 (真)2
3
4
5
6
7
注意:== 与 = 的区别
==是比较运算符,用于判断两个值是否相等。=是赋值运算符,用于将右侧的值赋给左侧的对象。
在 if 语句中误用 = 会导致逻辑错误,且通常不会产生编译警告,因为赋值表达式本身求值也会得到一个值(即赋的值),在 C 语言中非零值会被视为“真”。
int a = 5;
if (a = 10) { // 错误!这里是赋值操作
// (a = 10) 这个表达式的值是 10
// 所以这个 if 代码块总是会执行,并且 a 的值被意外地修改为 10
printf("a 的值现在是:%d\n", a);
}2
3
4
5
6
该分支会执行并输出:
a 的值现在是:10
2. 比较运算符 (>, <, >=, <=)
这四个运算符用于比较两个操作数的相对大小。它们可以用于任何算术类型(整数和浮点数)。
int score = 88;
int is_excellent = (score >= 90); // 88 >= 90 为假,is_excellent 的值为 0
int is_pass = (score >= 60); // 88 >= 60 为真,is_pass 的值为 1
double temperature = -5.5;
int is_freezing = (temperature < 0.0); // -5.5 < 0.0 为真,is_freezing 的值为 12
3
4
5
6
7
3. 运算符的优先级
- 关系运算符 (
>,<,>=,<=) 的优先级高于相等性运算符 (==,!=)。 - 所有关系运算符的优先级都低于算术运算符 (
+,-,*,/等)。 - 所有关系运算符的优先级都高于逻辑运算符 (
&&,||)。
优先级只决定运算符与操作数怎样结合(相当于在哪里加括号),不决定求值的先后。下面的表达式按优先级分组为 ((a + 5) < b) && (c == b):
int a = 5, b = 10, c = 10;
// 分组与求值:
// 1. (a + 5) -> 10
// 2. (10 < b) -> (10 < 10) -> 0 (假)
// 3. && 的左操作数为 0,结果已确定为 0,右操作数 (c == b) 不会被求值
int result = a + 5 < b && c == b; // result 的值为 02
3
4
5
6
7
但为了代码的清晰和可读性,强烈建议使用圆括号 () 写明分组。
int result = ((a + 5) < b) && (c == b);4. 习题
4.1 巩固
写出下面程序的输出,再逐行解释每个结果是怎样得到的。
#include <stdio.h>
int main(void) {
int a = 5, b = 5, c = 10;
printf("%d %d %d\n", a == b, a == c, a != c);
printf("%d %d %d\n", 1 < 2 < 3, 3 > 2 > 1, 10 < 5 == 0);
printf("%d %d\n", (a < c) + (b < c), 5 == 5.0);
printf("%d\n", 0.1 + 0.2 == 0.3);
return 0;
}2
3
4
5
6
7
8
9
10
11
编译时加上 -Wall,把第二条 printf 触发的警告抄下来。然后回答:
1 < 2 < 3与数学上的写法含义相同吗?它实际按什么顺序结合,中间结果是什么类型?10 < 5 == 0的结果说明<和==的优先级关系如何?(a < c) + (b < c)的值为什么是2?这体现了关系运算符结果的什么性质?5 == 5.0为真、0.1 + 0.2 == 0.3为假,两者分别说明了什么?
4.2 编程
写三个函数,只用关系运算符表达判断,不借助 ? ::
int mdr_cmp_int(int a, int b);
int mdr_between(int value, int lo, int hi);
int mdr_cmp_date(int y1, int m1, int d1, int y2, int m2, int d2);2
3
要求:
mdr_cmp_int返回-1、0、1,分别表示a < b、a == b、a > b。提示:(a > b) - (a < b)。mdr_between判断value是否落在闭区间[lo, hi]之内,返回0或1,两端都算在内。mdr_cmp_date按年、月、日的顺序比较两个日期,返回-1、0、1,可以调用前两个函数。- 三个函数体都只有一条
return语句。
验收:用下面的 main 编译运行,输出必须逐行相同。
int main(void) {
printf("%d %d %d\n", mdr_cmp_int(1, 2), mdr_cmp_int(2, 2), mdr_cmp_int(3, 2));
printf("%d %d %d %d %d\n", mdr_between(5, 1, 10), mdr_between(1, 1, 10),
mdr_between(10, 1, 10), mdr_between(0, 1, 10), mdr_between(11, 1, 10));
printf("%d %d %d\n", mdr_cmp_date(2024, 3, 1, 2024, 2, 29),
mdr_cmp_date(1999, 12, 31, 2000, 1, 1),
mdr_cmp_date(2024, 2, 29, 2024, 2, 29));
return 0;
}2
3
4
5
6
7
8
9
-1 0 1
1 1 1 0 0
1 -1 02
3
最后回答:mdr_cmp_int 如果直接返回 a > b,取值范围是什么,为什么不能满足「小于、等于、大于」三种情况的要求?把两个比较结果相减,为什么恰好得到三种取值?
下面的四个函数都写错了,编译后运行输出为 0 0、1 1 1、0 0 1、1 1 1:
#include <stdio.h>
int is_zero(int x) {
if (x = 0) {
return 1;
}
return 0;
}
int in_range(int value, int lo, int hi) {
return lo <= value <= hi;
}
int is_pass(int score) {
return score > 60;
}
int is_nonzero(int x) {
if (x =! 0) {
return 1;
}
return 0;
}
int main(void) {
printf("%d %d\n", is_zero(1), is_zero(0));
printf("%d %d %d\n", in_range(50, 0, 100), in_range(-5, 0, 100),
in_range(150, 0, 100));
printf("%d %d %d\n", is_pass(59), is_pass(60), is_pass(61));
printf("%d %d %d\n", is_nonzero(-5), is_nonzero(0), is_nonzero(3));
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
对每个函数:
- 说明它现在实际在算什么,以及为什么这些测试用例暴露了错误;
- 改成正确的写法;
- 记下
gcc -std=c17 -Wall -Wextra对错误版本的诊断,说明每条警告提示的是哪一类问题。
改完后输出应为 0 1、1 0 0、0 1 1、1 0 1。再说明 is_pass 的边界:分数为 60 的学生应算及格,改成 score >= 60 后,与原来的 score > 60 相差几个输入值?
4.3 综合与挑战
(1) [3.2] 写 int nearly_equal_abs(double a, double b, double eps),当 |a - b| < eps 时返回 1,否则返回 0。先打印 0.1 + 0.2 与 0.3 的 %.17g 值,再解释为什么 0.1 + 0.2 == 0.3 为假,而 eps 取 1e-9 时判断成功。验收:
nearly_equal_abs(0.1 + 0.2, 0.3, 1e-9) -> 1
nearly_equal_abs(1e9, 1e9 + 1e-6, 1e-9) -> 0
nearly_equal_abs(1.0, 1.0 + 1e-12, 1e-9) -> 12
3
(2) [5.3] 第二行返回 0,说明绝对误差在参与运算的数很大时不够用:两个相差 1e-6 的数在 1e9 的量级上已经很接近,可绝对误差仍超过 1e-9。写 int nearly_equal_rel(double a, double b, double rel_eps),用 rel_eps * max(|a|, |b|) 作容差。验收:
nearly_equal_rel(1e9, 1e9 + 1e-6, 1e-12) -> 1
nearly_equal_rel(1.0, 1.001, 1e-12) -> 0
nearly_equal_rel(0.0, 1e-12, 1e-9) -> 02
3
第三行说明相对容差在 0 附近失效:两个数都接近 0 时 max(|a|, |b|) 也接近 0,容差随之缩到 0。写一个同时使用两种容差的版本,使上面六行结果全部符合,并说明为什么要先比绝对误差、再比相对误差。
(3) [4.2] 下面一行的第二个比较结果出乎意料:
printf("%d %d\n", -1 < 1, -1 < 1u);写出两个结果,编译时加上 -Wall 抄下诊断,解释 -1 < 1u 为什么为假。再改写成一个结果符合直觉、又不触发警告的版本。
(4) [5.2] 下面一行输出 1:
printf("%d\n", 1e16 == 1e16 + 1.0);用 %.17g 打印 1e16 + 1.0 - 1e16 的值,说明 double 在 1e16 这个量级上相邻两个可表示值相差多少,并据此说明判断两个浮点数是否相等时为什么不能依赖 ==。