程序为什么需要分支
到目前为止,每个程序都是从头一路执行到尾:第 1 行、第 2 行、第 3 行。真正的程序需要做判断。密码对吗?这个数是负数吗?文件打开了吗?if 语句就是 C 提出问题的方式,只有答案为“是”时才执行代码。
条件 temperature > 30 只判断一次。它成立,所以会打印这条消息。最后一行在花括号外面,所以无论如何都会打印。把 31 改成 18 再运行一次,第一条消息消失了,第二条仍然保留。
语法:圆括号、花括号,没有分号
C 中的 if 有固定的形式:
if (condition) {
// runs when condition is nonzero
}
有三条规则值得尽早记住。条件外面的圆括号是必需的。没有 then 关键字。而且右圆括号后面没有分号,多写一个分号,语句就在那里结束了:
// BUG: the if does nothing; the printf always runs
if (x > 10);
{
printf("big\n");
}
这段代码能顺利编译。; 是一个空语句,成为了 if 的语句体,下面的花括号只是一个独立的代码块,总会执行。用 gcc -Wall 编译就能发现这个问题。
只有一条语句时,花括号本身是可选的,但还是建议保留。没有花括号的 if 后来多加了一行,是一个典型的 bug 来源,因为在 C 中缩进并不能把语句组合在一起。
else 和 else if
else 提供另一条路径:
结果多于两种时,用 else if 串联起来。C 会按顺序检查每个条件,在第一个成立的条件处停下:
这里的顺序非常重要。因为链在第一个匹配处就停止,第二个判断只会遇到低于 90 的分数,所以 score >= 80 不需要再写 && score < 90。要按从最严格到最宽松的顺序编写条件链,否则每个值都会落入第一个分支。
末尾的 else 是可选的。只要这条链打算覆盖所有情况,就加上它;没有 else 意味着“如果都不匹配,就什么也不做”,这没有问题,但应该是有意为之。
真值:0 为假,其他都为真
在条件中,C 的核心语言没有单独的布尔类型。if 只判断表达式是否不为零。
最后两行展示了规则的另一半:比较运算符是普通的表达式,结果为真时产生 1,为假时产生 0。这就是 if (5 > 2) 能工作的原因:它实际上是 if (1)。
这也是为什么在 C 中 -3 为真。规则是“不为零”,而不是“为正数”。如果你想知道一个数是不是正数,要写 if (n > 0),而不是 if (n)。
C99 新增了 <stdbool.h>,提供真正的 bool 类型以及 true 和 false,在新代码中可读性更好。它们底层仍然是 1 和 0,所以上面的规则不会改变,详见 C 语言中的布尔值。
在条件中会用到的比较运算符和逻辑运算符:
== equal to && logical AND (both sides true)
!= not equal to || logical OR (either side true)
< less than ! logical NOT
> greater than
<= less than or equal
>= greater than or equal
&& 和 || 会短路求值:如果 a 为假,a && b 永远不会计算 b;如果 a 为真,a || b 永远不会计算 b。这是语言的保证,而不是优化,所以可以放心地写 if (n != 0 && total / n > 5),n 为零时除法不会执行。
= 与 == 错误
这是 C 条件语句中最常见的错误:
// BUG: assigns 5 to x, then tests 5 (nonzero), so this ALWAYS runs
if (x = 5) {
printf("x is five\n");
}
C 允许在表达式中赋值,所以这一行是合法的。它把 5 存入 x,产生值 5,而 5 不为零,所以这个分支每次都会执行,x 也被悄悄覆盖了。
有两种防御办法。第一,编译时始终开启警告:
gcc -Wall -Wextra program.c -o program
GCC 和 clang 都会提示 "suggest parentheses around assignment used as truth value"。第二,有些程序员把常量写在变量前面,写成 if (5 == x),这样误写成的 if (5 = x) 会直接编译失败,而不是变成一个悄无声息的 bug。这种写法见仁见智,但开启警告是必须的。
嵌套
一个 if 里可以包含另一个 if。有些条件只有在前一个条件成立后才有意义,这时就用嵌套来表达:
两层嵌套还算好读,四层就不行了。嵌套变深时,想办法把它展平:通常可以用 && 合并条件,或者先处理失败的情况并提前返回:
// flatter: deal with the exceptions, then get on with the work
if (age < 18) { printf("Adults only.\n"); return 0; }
if (!hasTicket) { printf("Please buy a ticket.\n"); return 0; }
printf("Welcome in.\n");
悬空 else
当一个没有花括号的 if 嵌套在另一个 if 里时,else 属于哪个 if?C 的规则是:else 与最近的、尚未配对的 if 配对,与代码的缩进无关。
// The else belongs to the INNER if, despite the indentation
if (a > 0)
if (b > 0)
printf("both positive\n");
else
printf("a is not positive\n"); // WRONG - runs when a > 0 and b <= 0
缩进看起来是 else 与外层的 if 配对,编译器却把它和内层的配对。花括号可以彻底消除这种歧义,这才是始终使用花括号的真正理由:
if (a > 0) {
if (b > 0) {
printf("both positive\n");
}
} else {
printf("a is not positive\n");
}
三元运算符
当一个分支只是为了选出一个值时,条件运算符 ? : 用一个表达式就能表达:
把 cond ? x : y 读作“如果 cond 成立就是 x,否则就是 y”。两个分支中只有一个会被求值,而且整个式子会产生一个值,所以它可以用于赋值或作为参数传递,而语句不行。
只在简单的取值选择中使用它。嵌套的三元运算符(a ? b : c ? d : e)是合法的,但几乎总是比它所替代的 else if 链更难读。
常见问题
C 语言中 if else 语句怎么写?
把条件写在圆括号里,把代码写在花括号里:if (score >= 60) { printf("Pass\n"); } else { printf("Fail\n"); }。只有条件为假时才会执行 else 代码块,C 中也没有 then 关键字。
在 C 的 if 语句中,什么算作真?
任何不为零的值。C 的核心语法中没有专门的布尔类型,所以只要 n 不是 0,if (n) 就会执行;只有 n 恰好是 0 时,if (!n) 才会执行。==、< 这样的比较运算符,结果为真时产生 1,为假时产生 0。
C 语言中 = 和 == 有什么区别?
== 是比较,= 是赋值。写成 if (x = 5) 会先把 5 赋给 x,再判断 5,它不为零,所以这个分支总会执行。这段代码能正常编译,所以它是 C 条件语句中最常见的新手错误,大多数编译器在开启 -Wall 时会给出警告。
C 语言的三元运算符怎么用?
condition ? valueIfTrue : valueIfFalse 是一个表达式,会产生两个值中的一个,所以它可以出现在赋值语句或 printf 的参数中:int max = a > b ? a : b;。用它来选择一个值;需要执行语句时,就用普通的 if。