3.6 三元运算符 ? :
条件运算符 (conditional operator) 有三个操作数,是 C 语言中唯一的三元运算符,因此也常被称为三元运算符 (ternary operator)。它可以看作 if-else 语句的一种简洁写法。
1. 语法结构
条件运算符的形式如下:
condition ? value_if_true : value_if_false求值过程如下:
- 先对
condition求值。 - 如果
condition为真(不等于 0),对value_if_true求值,其值就是整个表达式的值,value_if_false不会被求值。 - 如果
condition为假(等于 0),对value_if_false求值,其值就是整个表达式的值,value_if_true不会被求值。
2. 示例
2.1 求两个数的最大值
下面两种写法都会令 max 得到 20:
int a = 10;
int b = 20;
int max;
if (a > b) {
max = a;
} else {
max = b;
}
// 此时 max 的值为 202
3
4
5
6
7
8
9
10
int a = 10;
int b = 20;
// 如果 a > b 为真,表达式的值为 a;否则,值为 b。
// 然后将这个值赋给 max。
int max = (a > b) ? a : b;
// 此时 max 的值为 202
3
4
5
6
7
8
2.2 根据条件赋值
条件运算符适合根据某个条件从两个值中选择一个来赋值。
int score = 59;
// 如果 score >= 60 为真,result 指向 "及格";否则,指向 "不及格"。
char* result = (score >= 60) ? "及格" : "不及格";
printf("考试结果:%s\n", result); // 输出:考试结果:不及格2
3
4
5
6
输出:
考试结果:不及格
note
2.2.1 三元运算符 vs. if-else 语句
虽然三元运算符可以看作是 if-else 的简写,但它们之间有一个关键区别:
- 三元运算符构成表达式 (expression):它产生一个值,可以用于赋值、作为函数实参,或出现在更大的表达式中。
if-else是语句 (statement):它选择执行哪一条语句,本身不产生值,分支中可以包含多条语句。
需要根据条件在两个值之间选择时,可以使用三元运算符;需要根据条件执行不同的操作时,应使用 if-else。
谨慎嵌套
条件运算符可以嵌套使用,但嵌套会明显降低代码的可读性。除非逻辑非常简单,否则应避免嵌套。
下面两段代码都会求出三个数的最大值,后者更容易阅读和修改:
int a = 10, b = 30, c = 20;
int max = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);2
int a = 10, b = 30, c = 20;
int max;
if (a > b) {
if (a > c) {
max = a;
} else {
max = c;
}
} else {
if (b > c) {
max = b;
} else {
max = c;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
3. 习题
3.1 巩固
下面的程序有三处条件运算符:
#include <stdio.h>
int main(void) {
int a = 5;
int b = (a > 3) ? a++ : ++a;
printf("a = %d, b = %d\n", a, b);
int x = 0;
int y = (x != 0) ? x++ : ++x;
printf("x = %d, y = %d\n", x, y);
int n = -7;
printf("|n| = %d\n", n > 0 ? n : -n);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
(1) 先写出三行输出,再编译运行核对。注意 b 的值是 5 而不是 6:说明为什么。
(2) 第二处的 x++ 一次都没有执行,据此说明条件运算符与 if-else 在「只求值被选中的分支」这一点上的一致之处。
(3) 把第二处的条件改成 x == 0,重新写出 x、y 的值并运行验证。这次哪个分支被执行了?
下面的程序用三条 if-else 语句完成三次二选一:
#include <stdio.h>
int main(void) {
int score = 72;
int bonus = 8;
int limit = 75;
int total;
if (bonus > 0) {
total = score + bonus;
} else {
total = score;
}
int capped;
if (total > limit) {
capped = limit;
} else {
capped = total;
}
char *label;
if (capped == limit) {
label = "capped";
} else {
label = "normal";
}
printf("total = %d\n", total);
printf("capped = %d\n", capped);
printf("label = %s\n", label);
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
33
(1) 把三条 if-else 全部改写成条件运算符,每个对象都在声明时用一条语句初始化。
(2) 编译运行,输出应与改写前完全相同:
total = 80
capped = 75
label = capped2
3
(3) 把 score 改成 60、bonus 改成 0,再运行。改写前后是否仍然一致?
(4) label 那一条如果保留 if-else 也完全正确。说明这一类「从两个值里选一个」的场合为什么适合用条件运算符,而「选一条语句执行」的场合不适合。
3.2 编程
写一个程序 ternary_demo.c,用条件运算符实现下面三个函数,并在 main 里按下表逐行打印:
int clamp(int value, int lo, int hi); /* 小于 lo 取 lo,大于 hi 取 hi,否则原值 */
int sign(int value); /* 负数返回 -1,零返回 0,正数返回 1 */
char *parity(int value); /* 偶数返回 "even",奇数返回 "odd" */2
3
每个函数体只允许一条 return 语句,不允许写 if。
测试数据与输出:
x = -7 clamp = 0 sign = -1 parity = odd
x = 0 clamp = 0 sign = 0 parity = even
x = 59 clamp = 59 sign = 1 parity = odd
x = 72 clamp = 72 sign = 1 parity = even
x = 95 clamp = 95 sign = 1 parity = odd2
3
4
5
用 -7 0 59 72 95 五个值循环调用,一次 printf 输出一行。要求 gcc -std=c17 -Wall -Wextra 无警告。
另做两个对照实验并记录结果:
clamp的value < lo ? lo : value > hi ? hi : value没有括号,先写出它实际等价于哪种加括号的形式,再说明这样写为什么仍然正确。- 写
int a = 3, b = 4; (a > b ? a : b) = 10;并编译。把编译错误原文抄下来,说明条件运算符的结果为什么不能放在赋值号左边;若本意是把较大的那个对象改成 10,应该怎么写?
3.3 综合与挑战
下面的程序本应输出每个月在平年和闰年里的天数,但 1、3、5、7、8、10、12 月不管闰年与否都得到 28:
#include <stdio.h>
/* 意图:month 月有多少天,leap 非 0 表示闰年 */
static int days_in_month(int month, int leap) {
return (month == 4 || month == 6 || month == 9 || month == 11) ? 30
: (leap && month == 2) ? 29
: 28;
}
int main(void) {
for (int m = 1; m <= 12; m++) {
printf("%2d: %2d %2d\n", m, days_in_month(m, 0), days_in_month(m, 1));
}
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
(1) [4.2] 找出缺陷并改正。改正后 1 月和 3 月应是 31 天,4、6、9、11 月是 30 天,2 月平年 28、闰年 29。改正时仍只用一条 return 和条件运算符。
(2) [4.1] 这条链里三个 ?: 排在一起,?: 右结合决定了哪个 ? 与哪个 : 配对。写出加括号后的等价形式,标出改正后的版本在 month = 2, leap = 0 时依次问过哪些条件、最后一步取到哪个值。
(3) [5.2*] 条件运算符的结果类型由两个分支共同决定。用下面的程序验证:
#include <stdio.h>
int main(void) {
printf("%.1f\n", (1 ? 1 : 2.0) / 2);
printf("%d\n", (1 ? 1 : 2) / 2);
printf("%d\n", (1 ? -1 : 1u) > 0);
printf("%d\n", (1 ? -1 : 1) > 0);
return 0;
}2
3
4
5
6
7
8
9
四行分别输出什么?第三行编译时会出现符号变化的提示,一并记下。为什么第一行是 0.5 而第二行是 0,第三行是 1 而第四行是 0?把结论写成一句话:条件运算符的结果类型由什么决定,与哪个分支实际被选中是否有关系。