控制结构
熟练使用分支、循环与跳转,覆盖边界条件
- 来源
- 补充
标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原
完成标准(本章)
- 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 180 秒
- ✏️ 已练习:小练习正确率 ≥ 60%
- 📝 已通过测验:分数 ≥ 60 分
- 🛠️ 已掌握还需完成实践任务
1.2.4 控制结构
本章来源:if/else、switch、三种循环、break/continue/goto/return 的语法与语义来自《第一阶段讲义》16.x 节【来源】,经重新组织表述;四个可运行示例与全部练习为新编【补充】并本机实测。平台适用性 universal(语法与语义三平台一致)。
① 学习目标
- 用 if / else if / else 写出多分支判断,并说明判断顺序与边界值的含义;
- 用 switch 改写整型多分支,说清 case 穿透(fall-through)与 default 的作用;
- 按场景选择 for / while / do-while,并写出边界正确的循环条件(
<与<=、循环变量更新); - 正确使用 break / continue / return 跳转,区分三者作用域;
- 说出 for(;;) 死循环在嵌入式里的合法用途,并能写出"条件可控"的死循环退出方式。
② 前置知识
- 必选:1.2.3 运算符与优先级(条件表达式、逻辑与比较运算);1.2.2 数据类型(整型与浮点比较)。
③ 核心概念【来源】
| 概念 | 说明 | 要点 |
|---|---|---|
| if / else if / else | 顺序判断,命中即停止 | 条件从上到下依次求值;把最可能命中的条件放前面(可读性);else 就近配对(悬挂 else) |
| switch | 整型表达式的多分支 | case 后必须是整型常量表达式;命中后一直执行到 break,漏写 break 就"穿透"进下一个 case;default 兜底(不是必须放最后,但建议放最后) |
| for | 已知次数/带计数器的循环 | for (初始化; 条件; 更新) 三段式;条件在每次循环体执行前判断(可能一次都不执行) |
| while | 先判断后执行 | 条件为假时循环体一次都不执行;适合"读到文件尾"这类未知次数场景 |
| do-while | 先执行后判断 | 至少执行一次;最适合"菜单"(先显示再等输入) |
| break / continue | 循环内跳转 | break 跳出最近一层循环/switch;continue 跳过本轮剩余语句进入下一轮条件判断(for 里会先执行"更新"段) |
| goto / return | 函数内跳转 | goto 只在"跳出多层嵌套循环"这类场景谨慎使用;return 直接结束函数 |
| for(;;) 死循环 | 条件为空的无限循环 | 嵌入式主循环(while(1)/for(;;))是合法且常见写法;必须配合 break/return 提供退出通道,否则只能外部终止 |
④ 通俗解释【补充】
- if 是查户口:按条件一本户口簿从上往下翻,第一本对上了就盖章走人(后面不看)——所以条件顺序要"从最特别到最普通"。
- switch 是拨号盘:一个数字对应一条专线;但每条专线之间没有墙——不写 break,人就顺着走到了隔壁房间(穿透)。
- 三种循环是三种吃饭方式:for 是"吃 3 碗饭"(次数已知);while 是"吃到不饿为止"(条件判断在先,可能一口没吃);do-while 是"先尝一口,不饿就停"(至少吃一口——菜单就是先摆出来)。
- break 是"摔门走人"(这层楼不待了),continue 是"这道菜不吃"(换下一道,人还在餐桌上)。
- 死循环不是坏东西:嵌入式主程序"上电后永远转圈等事做",就是死循环;坏的是没有出口的死循环——记得留 break 这道门。
⑤ 示例代码
代码示例与验证记录
- examples/ex1-grade.c成绩分层(if / else if / else 与边界值)✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-16)编译:
gcc ex1-grade.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)macOS展开预期输出(实测)
85 分:良好
差异说明:编译 0 警告;把 score 改成 59/60/89/90 可分别命中"不及格/及格/良好/优秀"四个边界分支(正文 ① 学习目标要求逐一边界自测)
完整源码见 /code 代码示例页
- examples/ex2-multiplication-table.c九九乘法表(嵌套 for 与循环边界)✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-16)编译:
gcc ex2-multiplication-table.c -o ex2 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)macOS展开预期输出(实测)
1x1=1 1x2=2 2x2=4 1x3=3 2x3=6 3x3=9 1x4=4 2x4=8 3x4=12 4x4=16 1x5=5 2x5=10 3x5=15 4x5=20 5x5=25 1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36 1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49 1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64 1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
差异说明:编译 0 警告;%-3d 左对齐补空格(行尾空白已省略);把内层条件 j<=i 改成 j<=9 可得完整矩形表
完整源码见 /code 代码示例页
- examples/ex3-prime.c质数判断(for + break + 试除边界 i*i<=n)✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-16)编译:
gcc ex3-prime.c -o ex3 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)macOS展开预期输出(实测)
29 是质数(试除 2..5 均不能整除)
差异说明:编译 0 警告;n=9 时输出"9 不是质数(找到因子 3)"(另一分支);n<2 走"不是质数"边界分支
完整源码见 /code 代码示例页
- examples/ex4-calculator.c简易计算器(switch + do-while 菜单循环 + 除零边界)✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-16)编译:
gcc ex4-calculator.c -o ex4 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)macOS展开预期输出(实测)
菜单: 1=加 2=减 3=乘 4=除 0=退出 请选择: 12 + 5 = 17 菜单: 1=加 2=减 3=乘 4=除 0=退出 请选择: 12 - 5 = 7 菜单: 1=加 2=减 3=乘 4=除 0=退出 请选择: 12 * 5 = 60 菜单: 1=加 2=减 3=乘 4=除 0=退出 请选择: 12 / 5 = 2.4 菜单: 1=加 2=减 3=乘 4=除 0=退出 请选择: 无效选项 5 菜单: 1=加 2=减 3=乘 4=除 0=退出 请选择: 退出
差异说明:编译 0 警告;输入序列 1 2 3 4 5 0 依次演示四种运算、default 分支与退出;scanf 返回非 1(输入流结束)时 break 防卡死
完整源码见 /code 代码示例页
⑥ 编译与运行方法
四个示例均为独立单文件程序,编译命令统一 -std=c11 -Wall -Wextra -Wpedantic(见示例卡):
- ex1-ex3:编译后直接运行;
- ex4(计算器)需要标准输入,按示例卡给出的管道命令输入
1 2 3 4 5 0体验完整菜单循环(1-4 四种运算、5 无效选项、0 退出)。
⑦ 常见错误
| 症状 | 原因 | 解决 |
|---|---|---|
| 写了两个 case 结果连续打印两段 | switch 漏写 break,发生穿透 | 每个 case 末尾补 break;确实想穿透时用注释显式声明(如 /* fall through */) |
if (a = 5) 永远为真 | 把 == 写成 =(赋值表达式值是 5,非 0 即真) | 常量放左边 5 == a 让编译器帮你报错(-Wall 也会警告) |
| 循环多跑/少跑一轮 | 边界条件写错(< 与 <=、n 与 n-1) | 先在纸上代入最小值与最大值各走一遍;下标类循环记住"0 到 n-1 共 n 个" |
| 程序卡死不动 | 死循环:条件恒真或循环变量忘了更新 | 检查三要素:初始化、条件、更新;排查时临时打印循环变量 |
| switch 变量用 float / case 后写变量 | switch 只接受整型;case 必须是整型常量表达式 | 浮点分段用 if/else if;case 后写字面量或枚举常量 |
| 菜单程序只显示一次就退出 | 用 while 代替 do-while 且初值不满足 | 菜单场景用 do-while(至少显示一次) |
⑧ 小练习
小练习
学习自测:提交后才显示答案与解析(前端判分,不作为正式考试)ex-1-2-4-1.switch 中某个 case 末尾漏写 break 会发生什么?(单选)
◌ 未作答ex-1-2-4-2.循环变量 i 从 0 开始,要依次处理 n 个元素(第 0..n-1 个),循环条件应写?(单选)
◌ 未作答ex-1-2-4-3.程序运行时卡死不动(疑似死循环),最该先排查哪两项?(单选)
◌ 未作答
⑨ 章节测验
章节测验
⑩ 实战任务
实践任务
独立编写四个控制结构小程序(含边界分支自测)
不照抄示例,独立编写四个程序:① 成绩分层(覆盖 59/60/89/90 四个边界);② 九九乘法表; ③ 质数判断(覆盖 n=1、n=2、n=9、n=29);④ 简易计算器(覆盖四种运算、除 0、非法输入、退出)。 用本章的 if/switch/三种循环/break 分别实现,体会选型。
输入与输出
无固定输入输出。交付物:四个 .c 文件(可放在自己的练习目录)+ 每个程序的运行截图/文字记录(含边界分支输出)。
功能要求
- 四个程序全部独立编写(可参考示例思路,不得整段照抄)
- 至少一处使用 switch、一处使用 do-while、一处使用 for 循环边界判断
- 计算器必须处理除数为 0 与非法输入两种情况
- 记录每个程序的编译命令(-std=c11 -Wall -Wextra -Wpedantic)与运行输出
限制条件
- 编译 0 警告(-Wall -Wextra -Wpedantic)
- 不使用教材示例之外的库函数(stdio 即可)
验收步骤(自检清单 0/4)
验收标准
- 四程序全部编译 0 警告且运行输出正确(验收步骤 2)
- 边界分支输出与预期一致(验收步骤 3)
- 输出记录完整可复核(验收步骤 4)
常见失败原因
- case 漏写 break 导致穿透
- 循环条件写 i <= n 多跑一轮
- 除 0 未判断直接计算(浮点除 0 得 inf 而非报错,容易漏)
- scanf 未检查返回值,输入结束/非法时程序卡死
可选扩展
- 给质数判断加计数:统计 1..100 内的质数个数(答案 25 个)
- 用 switch 重写成绩分层(int 等级 4/3/2/1),对比两种写法可读性
完成必要清单后才能计入"已完成实践"(学习状态自动推导,不提供一键完成)
⑪ 面试问题
面试问题
else if 链和 switch 各适合什么场景?为什么 switch 的分支值有类型限制?高频C/C++ · easy
要点:连续区间/复杂条件用 else if;离散整型值的多分支用 switch(更清晰、编译器可优化为跳转表)。switch 的表达式必须是整型,case 必须是整型常量表达式。
else if 适合区间判断(成绩分层)与任意表达式条件;switch 适合"一个整型值对应一个动作" (菜单、状态机),可读性更好,编译器对连续 case 可生成跳转表。限制来源:case 标签是编译期 常量,需要与表达式做精确整型比较;浮点有精度问题且区间语义不清晰,C 标准只允许整型。 补充:漏写 break 的穿透是高频考点,默认行为就是穿透。
追问:- 追问:状态机(如网络协议状态)用 switch 怎么写更稳?(default 兜底 + 每个状态显式 break)
评分要点:- 选型依据(区间 vs 离散)
- switch 类型限制的原因
- 提到穿透陷阱
do-while 和 while 有什么本质区别?举一个 do-while 最自然的应用场景。高频C/C++ · easy
要点:do-while 先执行后判断、至少执行一次;while 先判断后执行、可能一次不执行。最自然场景:交互菜单(先显示再等输入)。
区别在判断时点:while 在循环体前判断(可能 0 次),do-while 在循环体后判断(至少 1 次)。 菜单是典型场景——必须先显示选项,再读取用户输入,输入为 0 才退出。 加分:结合输入校验(scanf 返回非 1 时 break)说明循环出口设计。
追问:- 追问:如何保证 do-while 不会因为输入流结束而卡死?(检查 scanf 返回值并在循环内 break)
评分要点:- 判断时点差异
- 至少执行一次
- 菜单场景
循环条件写 `<` 还是 `<=` 有什么区别?结合数组遍历说说典型的越界事故。高频C/C++ · medium
要点:n 个元素下标为 0..n-1,遍历条件应写 i < n;写 i <= n 会访问第 n 个位置,是数组越界(未定义行为)。
边界事故的根因是把"元素个数"当成"最大下标"。写循环前先在纸上代边界值:i=0 时访问第一个、 i=n-1 时访问最后一个、i=n 时已经越界。防御性做法:固定用"下标从 0 到 n-1,条件 i < n"的 习惯;遍历范围不是从 0 开始时单独声明起止。越界读取是未定义行为(可能读到垃圾、可能崩溃, 结果不可依赖)——1.2.5 详细展开。
追问:- 追问:如何验证自己的循环边界正确?(用最小规模 n=0/1/2 的输入实测所有分支)
评分要点:- 0..n-1 与 i<n 的对应
- 越界 = 未定义行为
- 边界代入法
⑫ 延伸阅读
- 《C 程序设计语言(K&R)》第 3 章 控制流——只引书名与章名,不复制内容;
- 《C 语言程序设计:现代方法》第 5/6 章(选择语句/循环);
- 力扣交叉引用:本章不设力扣题(控制流是基础语法,需配合下一章数组才有算法题);LC 283(移动零)的主要教学归属为 1.3.1(已完成);
- 下一章预告:1.2.5 数组——把本章循环的"逐个处理"升级为"成组存取"。
迁移训练(migration training)
控制结构语法与语义在 Windows / Linux / Mac 三平台完全一致;嵌入式场景的两个注意点:
for(;;)与while(1)是裸机/RTOS 主循环的标准写法(7.x 嵌入式章节会实际使用);- 循环里做超时判断时用
volatile修饰的计时变量(volatile 的正确用法在 1.3.11 展开,避免 7.2 类"volatile 当锁用"的误区)。
内容来源映射
| 内容部分 | 资料 | 位置 | 标记 | 说明 |
|---|---|---|---|---|
| if/else、switch、for/while/do-while、break/continue/goto/return 语法与语义 | 第一阶段讲义 | 16.x 节 | 【来源】 | 正文在原资料基础上重新组织表述,未大段复制原文 |
| 四个可运行示例(成绩分层/乘法表/质数/计算器)与本机实测输出 | 无 | 【补充】 | 原资料无成体系可运行示例,全部新写并本机实测(gcc 15.2.0) | |
| 小练习 / 章节测验 / 实践任务 / 面试问题 / 延伸阅读 | 无 | 【补充】 | 原资料该章无成体系练习,全部新编 |