C控制语句:分支与跳转

掌握 if/else/else if 多路分支、switch-case 多路选择、break/continue/goto 跳转语句,能编写菜单驱动和状态机式程序

学习目标

  • 能区分 if/else if/else 分支链与 switch-case 多路选择,并选出适合区间判断、离散菜单选项的场景
  • 能改出带 break/continue 的循环筛选程序,并解释 break 跳出循环与 continue 跳过本轮的区别
  • 能利用 &&/|| 的短路求值保护除零或空指针访问,并回答:在 if (x != 0 && y / x > 2) 中,当 x 为 0 时右侧表达式会被求值吗?

机制总览

C控制语句:分支与跳转:机制路径

  1. 1

    为什么程序需要「岔路口」?

    想象你在工厂流水线的质检站:每块巧克力经过时,工人要判断——合格装箱、轻微瑕疵降级、严重不合格丢弃。同一条传送带,不同情况走不同处理路径。程序也一样:分数高低给不同等级、菜单选项触发不同功能、传感器读数决定报警还是正常——都需要「根据条件选一条路走」。

  2. 2

    if / else if / else:互斥分支链

    当只有「是 / 否」两条路时,加 else :

  3. 3

    switch-case:离散值的多路跳转

    当要根据离散整数值在多个固定选项间跳转时, switch 语句 比一长串 else if 更清晰。控制表达式必须具有整数类型; char 、 short 和枚举也能使用,是因为它们会先做整数提升。每个 case 都必须是整数常量表达式,转换后不能与同一 switch 中的其他标签重复。

先按顺序建立机制,再进入实验切换阶段并检查失效证据。

章级决策实验

C控制语句:分支与跳转:机制与证据

切换《C控制语句:分支与跳转》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。

选择推理阶段

当前阶段 · 为什么程序需要「岔路口」?

想象你在工厂流水线的质检站:每块巧克力经过时,工人要判断——合格装箱、轻微瑕疵降级、严重不合格丢弃。同一条传送带,不同情况走不同处理路径。程序也一样:分数高低给不同等级、菜单选项触发不同功能、传感器读数决定报警还是正常——都需要「根据条件选一条路走」。

可核验证据

用 -Wall -Wextra -pedantic 构建本节示例,再以正常值、边界值和失败输入核对「为什么程序需要「岔路口」?」的实际行为。

学完《C控制语句:分支与跳转》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。

失效—证据矩阵

C控制语句:分支与跳转:失效与核验

为什么程序需要「岔路口」?

典型失效

若只记语法而忽略「为什么程序需要「岔路口」?」的类型范围、求值顺序或资源边界,输入变化后可能出现未定义行为或错误结果。

核验证据

用 -Wall -Wextra -pedantic 构建本节示例,再以正常值、边界值和失败输入核对「为什么程序需要「岔路口」?」的实际行为。

if / else if / else:互斥分支链

典型失效

若只记语法而忽略「if / else if / else:互斥分支链」的类型范围、求值顺序或资源边界,输入变化后可能出现未定义行为或错误结果。

核验证据

用 -Wall -Wextra -pedantic 构建本节示例,再以正常值、边界值和失败输入核对「if / else if / else:互斥分支链」的实际行为。

switch-case:离散值的多路跳转

典型失效

若只记语法而忽略「switch-case:离散值的多路跳转」的类型范围、求值顺序或资源边界,输入变化后可能出现未定义行为或错误结果。

核验证据

用 -Wall -Wextra -pedantic 构建本节示例,再以正常值、边界值和失败输入核对「switch-case:离散值的多路跳转」的实际行为。

每个判断都必须能落到观测、测试或产物,不能只凭代码表面推测。

为什么程序需要「岔路口」?

想象你在工厂流水线的质检站:每块巧克力经过时,工人要判断——合格装箱、轻微瑕疵降级、严重不合格丢弃。同一条传送带,不同情况走不同处理路径。程序也一样:分数高低给不同等级、菜单选项触发不同功能、传感器读数决定报警还是正常——都需要「根据条件选一条路走」。

如果没有分支呢? 所有代码只能从上到下顺序执行,无法响应不同输入。菜单程序无法区分「新建」和「退出」,成绩程序无法区分及格与不及格。分支语句就是 C 语言给你的「岔路口标志」——判断条件,选一条路径执行。

这一章教你四类岔路口:if 链、switch 多路选择、循环内的 break/continue 跳转,以及逻辑运算符的短路求值。

if / else if / else:互斥分支链

是最基本的分支:条件为真执行花括号里的代码,为假则跳过。

if (score >= 60) {
    printf("及格\n");
}

当只有「是 / 否」两条路时,加

if (score >= 60) {
    printf("及格\n");
} else {
    printf("不及格\n");
}

超过两档?用 串成链——从上到下依次检查,第一个为真的分支执行后,其余全部跳过

if / else if / else 分支链① if (score >= 90)grade = 'A'真 → 执行后跳出链假 ↓② else if (score >= 60)grade = 'P'真 → 执行后跳出链全假 ↓③ else grade = 'F'汇合:继续执行后续代码从上到下依次检查,第一个为真的分支执行后,其余分支全部跳过
if / else if / else 构成互斥分支链:最多执行其中一个代码块,执行完毕后跳到链尾继续往下走。
if (score >= 90) {
    grade = 'A';
} else if (score >= 80) {
    grade = 'B';
} else if (score >= 60) {
    grade = 'P';
} else {
    grade = 'F';
}

score = 75 时:第一个条件 >= 90 假,第二个 >= 80 假,第三个 >= 60 真——执行 grade = 'P',后面的 else 不再检查。这就是

猜一猜:score = 85 时,会执行几个赋值语句?用 Stepper 一步步走。

分步1 / 3

第一步:检查 if (score >= 90)

if / else if / else 分支链① if (score >= 90)grade = 'A'真 → 执行后跳出链假 ↓② else if (score >= 60)grade = 'P'真 → 执行后跳出链全假 ↓③ else grade = 'F'汇合:继续执行后续代码从上到下依次检查,第一个为真的分支执行后,其余分支全部跳过
if / else if / else 构成互斥分支链:最多执行其中一个代码块,执行完毕后跳到链尾继续往下走。

85 >= 90 为假——不执行 grade = 'A',流程落到下一个 else if。

switch-case:离散值的多路跳转

当要根据离散整数值在多个固定选项间跳转时, 比一长串 else if 更清晰。控制表达式必须具有整数类型;charshort 和枚举也能使用,是因为它们会先做整数提升。每个 case 都必须是整数常量表达式,转换后不能与同一 switch 中的其他标签重复。

switch (choice) {
    case 1:
        printf("新建文件\n");
        break;
    case 2:
        printf("打开文件\n");
        break;
    case 0:
        printf("退出\n");
        break;
    default:
        printf("无效选项\n");
}

多个 case 可以连续指向同一段语句,这叫多标签共享处理

switch (choice) {
    case 1:
    case 2:
        printf("普通菜单项\n");
        break;
    case 0:
        printf("退出\n");
        break;
    default:
        printf("无效选项\n");
}

case 1:case 2: 之间没有语句,贯穿是明确设计;这与遗漏 break 后意外执行下一项不同。编译器可以用顺序比较、决策树或跳转表实现 switch,具体策略取决于标签数量和稠密程度,语言本身不承诺某一种实现。

switch-case 与 fall-through① switch (choice)case 1:② choice==1 命中,跳入③ printf(...); break;break 跳出 switchcase 2:printf("B");case 3:printf("C"); break;④ 缺 break → 穿透 fall-through所有分支汇合,继续执行 switch 之后的代码case 标签只做跳转,不自动隔离代码块;break 负责跳出,漏写则继续执行下方 case
switch 根据表达式的值跳转到匹配的 case;break 阻止继续向下执行。忘记 break 会导致 fall-through,连跑多个 case 的代码。

执行流程:先求 choice 的值,与每个 case 标签比较,命中则跳入该 case 处继续向下执行,直到遇到 break 跳出 switch,或跑到右花括号。都不匹配时走 default(可选但菜单程序强烈建议写上)。

是 switch 最常见的坑——case 标签只做「跳转标记」,不会自动隔离代码块

switch (n) {
    case 1:
        printf("一\n");
        /* 故意不写 break —— fall-through */
    case 2:
        printf("二\n");
        break;
}

n = 1 时输出「一」和「二」两行——因为 case 1 没有 break,执行完 printf("一") 后继续滑入 case 2。

猜一猜:下面 switch 在 choice=2 时会打印几行?Stepper 带你看 break 与 fall-through 的差别。

分步1 / 4

第一步:求值 switch(choice)

switch-case 与 fall-through① switch (choice)case 1:② choice==1 命中,跳入③ printf(...); break;break 跳出 switchcase 2:printf("B");case 3:printf("C"); break;④ 缺 break → 穿透 fall-through所有分支汇合,继续执行 switch 之后的代码case 标签只做跳转,不自动隔离代码块;break 负责跳出,漏写则继续执行下方 case
switch 根据表达式的值跳转到匹配的 case;break 阻止继续向下执行。忘记 break 会导致 fall-through,连跑多个 case 的代码。

先计算括号里的表达式并完成整数提升。结果必须具有整数类型;浮点数和字符串不能作为 switch 的控制表达式。

break 与 continue:循环内的跳转

上一章学了三种循环。循环体里有时需要「提前收工」或「跳过本轮剩余步骤」——靠

break —— 直接跳出整个循环for (int i = 0; i < 10; i++)① i=0..2 正常② i=3 breaki=4..9 跳过break —— 跳出循环体循环之后的代码continue —— 跳过本轮,进入下一轮for (int i = 0; i < 5; i++)i=0..2 正常③ i=3 continuei=4 继续执行continue —— 回到循环体顶部break = 永久退出 continue = 跳过本轮剩余代码
break 一刀切断循环,之后的迭代永不执行;continue 只跳过当前这一轮循环体的剩余代码,循环本身继续跑下一轮。
/* break:找到目标就停 */
for (int i = 0; i < 100; i++) {
    if (arr[i] == target) {
        found = i;
        break;          /* 找到了,不必再扫 */
    }
}
 
/* continue:跳过偶数,只处理奇数 */
for (int i = 0; i < n; i++) {
    if (i % 2 == 0)
        continue;       /* 本轮剩余代码不执行 */
    sum += arr[i];
}

break 终止它所在的最内层循环或 switch;continue 只跳过最内层循环的当前这一轮,不能作用于 switch。for 遇到 continue 后先执行迭代表达式再检查条件;whiledo-while 则直接转到条件判断。在 switch 嵌套于循环时,switch 内的 break 只离开 switch,而 continue 会继续最内层循环。

猜一猜:下面循环打印哪些 i 的值?

分步1 / 3

第一步:正常迭代

break —— 直接跳出整个循环for (int i = 0; i < 10; i++)① i=0..2 正常② i=3 breaki=4..9 跳过break —— 跳出循环体循环之后的代码continue —— 跳过本轮,进入下一轮for (int i = 0; i < 5; i++)i=0..2 正常③ i=3 continuei=4 继续执行continue —— 回到循环体顶部break = 永久退出 continue = 跳过本轮剩余代码
break 一刀切断循环,之后的迭代永不执行;continue 只跳过当前这一轮循环体的剩余代码,循环本身继续跑下一轮。

循环按 usual 方式推进:i=0,1,2 时条件满足,循环体完整执行。

C 还有 goto,它能跳到同一函数内可见的标签。结构化分支和函数返回应是默认选择;当多个资源需要按逆序集中清理,或极少数深层嵌套需要统一退出时,单向跳到 cleanup 标签可以减少重复清理代码。goto 不能跳到另一个函数,也不应拿来拼接普通业务流程。

字符输入、字符分类与条件运算符

本章把分支应用到字符处理。 返回 int,不是 char,因为结果既要容纳所有无符号字符值,也要容纳负值宏 EOF 适合逐字符输出:

#include <ctype.h>
#include <stdio.h>
 
int main(void) {
    int ch;
    while ((ch = getchar()) != EOF) {
        if (isalpha((unsigned char)ch))
            putchar(toupper((unsigned char)ch));
        else
            putchar(ch);
    }
    return ferror(stdin) ? 1 : 0;
}

<ctype.h>isalphaisdigitisspacetoupper 等函数只接受 EOF 或可表示为 unsigned char 的值。getchar() 的非 EOF 结果天然满足;若数据来自可能为负的普通 char,传入前先转换为 unsigned char,否则行为未定义。不要用手写 ASCII 范围代替字符分类函数,因为执行字符集和 locale 可能不同。

condition ? expr1 : expr2 是有结果值的二选一表达式,且未选中的分支不会求值:

int max = a > b ? a : b;
const char *label = score >= 60 ? "pass" : "fail";

它适合简短、对称的值选择;涉及多条语句、副作用或嵌套条件时,if/else 更容易审查。不要把“代码短”误当成“意图清楚”。

逻辑运算符与短路求值

分支条件常组合多个判断:score >= 60 && score <= 100(区间)、ch == 'y' || ch == 'Y'(大小写都算 yes)。 不仅做真假组合,还有

逻辑运算符短路求值a && b — 全真才真求值 a&&求值 ba 为假时跳过a==0 → 直接得 0,b 不算a || b — 有真即真求值 a||求值 ba 为真时跳过a!=0 → 直接得 1,b 不算短路求值:结果已定时不再计算右侧——可用来保护除零、空指针等危险操作例:if (ptr != NULL && *ptr > 0) /* ptr 空则不会解引用 */
`&&` 左侧为假时整个表达式为假,不再求值右侧;`||` 左侧为真时整个表达式为真,同样跳过右侧。这叫短路求值。
/* 安全:x 为 0 时不会执行 y/x */
if (x != 0 && y / x > 2) {
    printf("比值足够大\n");
}
 
/* 菜单:y 或 Y 都算确认 */
if (ch == 'y' || ch == 'Y') {
    confirmed = 1;
}

若写成 y / x > 2 && x != 0,x 为 0 时先算除法——未定义行为,可能崩溃。短路求值的顺序很重要:把 cheap 且能保护后续的检查放左边

代码详解:菜单驱动与状态筛选

场景一:if 链——成绩分档

#include <stdio.h>
int main(void) {
    int score;
    char grade;
 
    printf("分数 = ");
    if (scanf("%d", &score) != 1 || score < 0 || score > 100)
        return 1;
 
    if (score >= 90)
        grade = 'A';
    else if (score >= 80)
        grade = 'B';
    else if (score >= 60)
        grade = 'P';
    else
        grade = 'F';
 
    printf("等级 %c\n", grade);
    return 0;
}

单语句分支可以省略花括号,但多行或嵌套时务必写花括号——否则只有紧跟 if 的一行属于分支体。

场景二:switch 菜单——离散选项

#include <stdio.h>
int main(void) {
    int choice;
 
    printf("1.加  2.减  0.退出\n");
    if (scanf("%d", &choice) != 1)
        return 1;
 
    switch (choice) {
        case 1:
            printf("执行加法\n");
            break;
        case 2:
            printf("执行减法\n");
            break;
        case 0:
            printf("再见\n");
            break;
        default:
            printf("无效选项\n");
    }
    return 0;
}

菜单选项是离散整数——switch 比 else if 链更易读。每个 case 末尾的 break 防止 fall-through 到下一个选项。

场景三:break + continue——筛选数组

#include <stdio.h>
int main(void) {
    int data[] = {3, -1, 7, 0, 5, -2, 8};
    int n = 7, sum = 0, i;
 
    for (i = 0; i < n; i++) {
        if (data[i] < 0)
            continue;       /* 跳过负数 */
        if (data[i] == 0)
            break;          /* 遇到 0 停止累加 */
        sum += data[i];
    }
 
    printf("正数累加(遇 0 止)= %d\n", sum);  /* 3+7+5=15 */
    return 0;
}

continue 让负数不参与 sum;break 让 0 成为「哨兵式」的停止标记——两者配合实现「只加正数,遇零收工」。

场景四:短路求值——安全除法

#include <stdio.h>
int main(void) {
    int a, b;
 
    printf("a b = ");
    if (scanf("%d %d", &a, &b) != 2)
        return 1;
 
    if (b != 0 && (double)a / b > 1.5) {
        printf("商大于 1.5\n");
    } else if (b == 0) {
        printf("除数不能为 0\n");
    } else {
        printf("商不超过 1.5\n");
    }
    return 0;
}

b != 0 放在 && 左侧——b 为 0 时短路,右侧 (double)a / b 不会执行,避免除零。

容易踩的坑

小结

  • if / else if / else 构成互斥分支链,从上到下第一个为真的块被执行
  • switch 适合离散整型/字符/枚举的多路选择;case 末尾 break 防 fall-through
  • break 终止最内层循环或 switch;continue 跳过本轮剩余、进入下一轮
  • &&/|| 短路求值:把安全检查和廉价判断放左侧,保护除零与空指针
  • getchar/putchar 以 int 表示字符与 EOF;ctype.h 参数必须是 EOF 或 unsigned char 可表示的值
  • 条件运算符适合简短的值选择;复杂控制流仍用 if/else
  • 菜单程序 = switch + break + default;区间分档 = if 链;循环筛选 = continue/break 组合

练习

问题 1(改代码型) 下面 switch 想实现 1=加、2=减、0=退出,但选 1 时会连打两行。找出问题并修正。

switch (op) {
    case 1:
        printf("加\n");
    case 2:
        printf("减\n");
        break;
    case 0:
        printf("退出\n");
        break;
}

问题 2(问答型) 读程序,写出输出。说明 continue 跳过了哪些语句。

#include <stdio.h>
int main(void) {
    int i;
    for (i = 0; i < 5; i++) {
        if (i == 2)
            continue;
        if (i == 4)
            break;
        printf("%d ", i);
    }
    return 0;
}

问题 3(综合实现型) 用 switch 写简易计算器:读入两个 double 和操作符字符(+ - * /),输出结果;除数为 0 时提示错误。操作符不是四则之一时走 default。

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

if 语句

条件为真才执行其后语句或语句块的选择结构。可单独使用,也可与 else、else if 组合成多档分支。是 C 程序做决策的基本工具。

else

与 if 配对的 else 分支:if 条件为假时执行 else 后面的语句或语句块。if 与 else 二选一,不能同时执行。

else if

接在 if 之后、else 之前的附加条件分支。前面所有条件都为假时才检查当前 else if。与 if、else 一起构成从上到下依次匹配的分支链。

if / else if / else 分支链

多个互斥条件依次检查,第一个为真的分支执行后其余全部跳过,最多执行一个代码块。适合成绩分档、区间判断等连续条件场景。详见 IfElseChainDiagram。

switch 语句

根据整型、字符或枚举表达式的值跳转到匹配的 case 标签执行。比长 else if 链更适合菜单、状态码等离散选项。语法:switch(expr) { case 常量: ... break; default: ... }

fall-through(贯穿/穿透)

switch 中某 case 缺少 break 时,执行完该 case 代码后继续执行下一个 case 的语句。多数情况是 bug;少数场景故意让多个 case 共享同一段代码。详见 SwitchCaseDiagram 第四步。

break

立即跳出所在的最内层 switch 或循环(for/while/do-while),继续执行 switch/循环之后的代码。用于提前终止搜索、退出菜单循环等。详见 BreakContinueDiagram。

continue

跳过当前循环迭代中 continue 之后的语句,直接进入下一轮(for 会先执行 update 再判条件)。循环本身不结束,只跳过本轮剩余步骤。详见 BreakContinueDiagram。

逻辑与(&&)和逻辑或(||)

逻辑与 &&:两侧都为真结果才为真;逻辑或 ||:任一侧为真结果即为真。常用于组合多个条件。与短路求值配合时,左侧结果已能决定最终真假则不再求值右侧。

短路求值(short-circuit evaluation)

逻辑与 && 左侧为假时整个表达式为假,不再求值右侧;逻辑或 || 左侧为真时整个表达式为真,同样跳过右侧。可用来避免除零、空指针解引用等危险操作。详见 ShortCircuitDiagram。

讨论

评论区加载中…