这一章在干嘛?

穷尽 C 的全部流程控制语句:if、switch、while、for、do、goto,外加 break/continue 两个「循环内跳转」工具。语法都不难,重点在各自的适用场景和易踩的坑。

4.1 表达式语句与代码块

4.2 分支:if 与 switch

4.3 循环:while / for / do

4.4 goto:能用而不用

4.1 表达式语句与代码块

表达式后面加个分号就成了语句:a = x + 1;func();。没有副作用的表达式语句(比如孤零零的 x + 1;)毫无意义,编译器通常直接优化掉。用 {} 把多条语句包成一个代码块,在语法上等价于一条语句——所以 if、for 的主体想写多行就加花括号。

悬空 else 与裸分号:

else 总是和 最近的未配对 if 结合。多层嵌套时补全花括号最保险;循环体末尾不小心多个 ; (如 for(...); )会让循环体变成空语句——循环白跑。

4.2 分支:if 与 switch

if (x > 0)
    printf("positive\n");
else if (x == 0)
    printf("zero\n");
else
    printf("negative\n");

switch (ch) {
case 'a':
case 'e':                  /* case 落空即共用:元音一起处理 */
    printf("vowel\n");
    break;
case '0': case '1':        /* 数字字符 */
    printf("digit char\n");
    break;
default:
    printf("other\n");
    break;
}

switch 的三条硬规则:① 条件必须是整型表达式(浮点、字符串不行);② case 是常量且必须唯一③ 不 break 就会「贯穿」到下一个 case。贯穿偶尔有用(如上面积累多个 case),但绝大多数时候是漏写 break 的事故——所以每个 case 末尾写 break、实在不需要的加注释说明,是好习惯。

switch 的执行过程:

编译器常把它优化成跳转表,条件值直接算出目标地址,比一串 if-else 链快。case 稠密连续时效果最好。

4.3 循环:while / for / do

循环先判断?适用场景
while (cond) body先判断只知道「继续条件」,不知道次数:读输入、链表遍历
for (init; cond; step)先判断已知次数或有明确步进;init/step 可留空
do body while (cond);后判断至少执行一次:菜单循环、重试逻辑
int ch;
while ((ch = getchar()) != EOF)    /* 赋值的值作为条件:惯用法 */
    putchar(ch);

for (int i = 0; i < n; i++) {      /* C99 起 init 里可声明变量 */
    if (buf[i] == '\0') break;     /* 提前终止 */
    if (buf[i] == ' ') continue;   /* 跳过本次,直接到 step */
    process(buf[i]);
}

break 跳出最近的一层循环或 switch;continue 只结束本轮、跳到步进/条件判断。它们都只影响最内层——想从两层循环里出来,要么加标志位,要么干脆封装成函数用 return。

4.4 goto:能用而不用

goto 能跳到同函数内任意标签处。破坏结构化、难审查,日常代码不该用它。唯二还站得住脚的场景:

/* 1) 深层循环一次跳出 */
for (i = 0; i < N; i++)
    for (j = 0; j < M; j++)
        if (found(i, j))
            goto done;
done:
report_result();

/* 2) 错误集中清理:失败统一跳 cleanup(free(NULL) 无害,正好复用) */
p1 = malloc(A);  if (p1 == NULL) goto cleanup;
p2 = malloc(B);  if (p2 == NULL) goto cleanup;
/* ... 使用 p1/p2 ... */
cleanup:
free(p2);
free(p1);

本章通关标准:

知道三种循环各适合什么场景;写 switch 想得起 break;看到嵌套 if 配 else 能立刻判断归属。