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
为什么程序需要「岔路口」?
想象你在工厂流水线的质检站:每块巧克力经过时,工人要判断——合格装箱、轻微瑕疵降级、严重不合格丢弃。同一条传送带,不同情况走不同处理路径。程序也一样:分数高低给不同等级、菜单选项触发不同功能、传感器读数决定报警还是正常——都需要「根据条件选一条路走」。
- 2
if / else if / else:互斥分支链
当只有「是 / 否」两条路时,加 else :
- 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 体,为假则跳过或走 else。 是最基本的分支:条件为真执行花括号里的代码,为假则跳过。
if (score >= 60) {
printf("及格\n");
}当只有「是 / 否」两条路时,加 ↡与 if 配对的 else 分支:if 条件为假时执行 else 体。if 和 else 二选一。:
if (score >= 60) {
printf("及格\n");
} else {
printf("不及格\n");
}超过两档?用 ↡在 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 不再检查。这就是 ↡if 与多个 else if 以及可选 else 组成的互斥选择结构:同一时刻最多执行其中一个代码块。。
猜一猜:score = 85 时,会执行几个赋值语句?用 Stepper 一步步走。
第一步:检查 if (score >= 90)
85 >= 90 为假——不执行 grade = 'A',流程落到下一个 else if。
switch-case:离散值的多路跳转
当要根据离散整数值在多个固定选项间跳转时,↡先对控制表达式做整数提升,再跳到值相等的 case 标签处执行的多路选择语句。 比一长串 else if 更清晰。控制表达式必须具有整数类型;char、short 和枚举也能使用,是因为它们会先做整数提升。每个 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,具体策略取决于标签数量和稠密程度,语言本身不承诺某一种实现。
执行流程:先求 choice 的值,与每个 case 标签比较,命中则跳入该 case 处继续向下执行,直到遇到 break 跳出 switch,或跑到右花括号。都不匹配时走 default(可选但菜单程序强烈建议写上)。
↡switch 中某个 case 缺少 break 时,代码会继续执行紧接着的下一个 case 的语句,称为贯穿或穿透。有时故意利用,多数情况是 bug。 是 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 的差别。
第一步:求值 switch(choice)
先计算括号里的表达式并完成整数提升。结果必须具有整数类型;浮点数和字符串不能作为 switch 的控制表达式。
break 与 continue:循环内的跳转
上一章学了三种循环。循环体里有时需要「提前收工」或「跳过本轮剩余步骤」——靠 ↡立即终止所在的最内层 switch 或循环(for/while/do-while),跳到 switch/循环之后的语句。 和 ↡跳过当前循环迭代中 continue 之后的剩余语句,直接进入下一轮迭代(先执行 for 的 update 或 while 的条件判断)。:
/* 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 后先执行迭代表达式再检查条件;while 和 do-while 则直接转到条件判断。在 switch 嵌套于循环时,switch 内的 break 只离开 switch,而 continue 会继续最内层循环。
猜一猜:下面循环打印哪些 i 的值?
第一步:正常迭代
循环按 usual 方式推进:i=0,1,2 时条件满足,循环体完整执行。
C 还有 goto,它能跳到同一函数内可见的标签。结构化分支和函数返回应是默认选择;当多个资源需要按逆序集中清理,或极少数深层嵌套需要统一退出时,单向跳到 cleanup 标签可以减少重复清理代码。goto 不能跳到另一个函数,也不应拿来拼接普通业务流程。
字符输入、字符分类与条件运算符
本章把分支应用到字符处理。↡从标准输入读取一个字节并以 unsigned char 转换后的 int 返回;到达输入末尾或出错时返回 EOF。 返回 int,不是 char,因为结果既要容纳所有无符号字符值,也要容纳负值宏 EOF。↡把参数按 unsigned 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> 的 isalpha、isdigit、isspace、toupper 等函数只接受 EOF 或可表示为 unsigned char 的值。getchar() 的非 EOF 结果天然满足;若数据来自可能为负的普通 char,传入前先转换为 unsigned char,否则行为未定义。不要用手写 ASCII 范围代替字符分类函数,因为执行字符集和 locale 可能不同。
↡C 的三目运算符:先求 condition;非零时只求 expr1,为零时只求 expr2,并产生所选表达式的值。 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)。↡&& 两侧都为真结果才为真;|| 任一侧为真结果即为真。求值时若左侧已能决定最终结果,则不再计算右侧,称为短路求值。 不仅做真假组合,还有 ↡表达式求值时,一旦结果已确定就不再计算后续操作数。&& 左假则右不算;|| 左真则右不算。可用于避免除零、空指针解引用等。:
/* 安全: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。