跳到主要内容
🔍
1.2.4已发布beginner · 约 3 课时 · P0

控制结构

熟练使用分支、循环与跳转,覆盖边界条件

  • 来源
  • 补充

标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原

完成标准(本章)

  • 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 180
  • ✏️ 已练习:小练习正确率 ≥ 60%
  • 📝 已通过测验:分数 ≥ 60
  • 🛠️ 已掌握还需完成实践任务
学习状态:未开始

1.2.4 控制结构

本章来源:if/else、switch、三种循环、break/continue/goto/return 的语法与语义来自《第一阶段讲义》16.x 节【来源】,经重新组织表述;四个可运行示例与全部练习为新编【补充】并本机实测。平台适用性 universal(语法与语义三平台一致)。

① 学习目标

  1. 用 if / else if / else 写出多分支判断,并说明判断顺序与边界值的含义;
  2. 用 switch 改写整型多分支,说清 case 穿透(fall-through)与 default 的作用;
  3. 按场景选择 for / while / do-while,并写出边界正确的循环条件(<<=、循环变量更新);
  4. 正确使用 break / continue / return 跳转,区分三者作用域;
  5. 说出 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.程序运行时卡死不动(疑似死循环),最该先排查哪两项?(单选)

    ◌ 未作答

⑨ 章节测验

章节测验

5 题题库 · 随机抽 5 题 · 及格线 60 分 · 前端判分(学习自测)
开始测验 →

⑩ 实战任务

实践任务

独立编写四个控制结构小程序(含边界分支自测)

不照抄示例,独立编写四个程序:① 成绩分层(覆盖 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)
小练习 / 章节测验 / 实践任务 / 面试问题 / 延伸阅读补充原资料该章无成体系练习,全部新编