## 引言
这是第三章「流程控制」的最后一篇,收卷篇。前九篇的递进路线是:分支(第 1 篇 `if`/`elif`/`else`、第 2 篇 `match`)→ 循环(第 3 篇 `while`、第 4 篇 `for` 与 `range`、第 5 篇 `break`/`continue`/`else`)→ 组合(第 6 篇嵌套、第 7 篇推导式)→ 实战(第 8 篇 FizzBuzz 与猜数字、第 9 篇图案打印与交互式菜单)。本篇做四件事:十道选择题把每个知识点的「精确版本」逼出原形;三道编程题给出可运行的参考解答;一个综合任务(成绩统计系统)要求把分数段分支、重复录入循环与统计汇总写进同一个程序;最后一份学习自检清单供你逐项画勾。测验范围严格限于前九篇;函数(`def`)、异常处理(`try/except`)与类分别属于第四、七、八章,这里只在参考解答的旁注里以预告形式出现,不作为考点。作答方式与第二章相同:先闭卷写答案与理由,再对照解析,理由比答案重要——答对但理由写错,说明对应篇目需要回读。
## 测验说明
十题均为单选,每题标注出处与考点,难度从记忆级到综合级递增。代码片段都已在本机(CPython 3.12)实测,输出真实可复现。建议每题限时两分钟。
### 第 1 题(出处:第 1 篇 分支结构)
```python
n = 75
if n >= 90:
grade = "A"
elif n >= 60:
grade = "C"
elif n >= 70:
grade = "B"
print(grade)
```
输出是?(A)`"A"` (B)`"B"` (C)`"C"` (D)报错
### 第 2 题(出处:第 2 篇 模式匹配)
```python
x = 9
match x:
case 1 | 2:
r = "小"
case _ if x > 5:
r = "大"
case _:
r = "中"
print(r)
```
输出是?(A)`"小"` (B)`"大"` (C)`"中"` (D)报错
### 第 3 题(出处:第 3 篇 while 循环)
```python
i = 0
total = 0
while i < 10:
i += 1
if i % 3 == 0:
continue
total += i
print(total)
```
输出是?(A)37 (B)40 (C)45 (D)55
### 第 4 题(出处:第 4 篇 for 与 range)
```python
print(list(range(2, 11, 3)))
```
输出是?(A)`[2, 5, 8]` (B)`[2, 5, 8, 11]` (C)`[3, 5, 8]` (D)报错
### 第 5 题(出处:第 5 篇 循环控制三件套)
```python
n = 7
for d in range(2, n):
if n % d == 0:
print("合数")
break
else:
print("质数")
```
输出是?(A)`"合数"` (B)`"质数"` (C)什么都没有输出 (D)报错
### 第 6 题(出处:第 6 篇 嵌套与组合)
```python
for i in range(1, 4):
for j in range(1, 4):
if j == 2:
break
print(i, j)
```
程序打印出的行是?(A)三行:`1 1`、`2 1`、`3 1` (B)三行:`1 1`、`1 2`、`1 3` (C)六行:`1 1`、`1 2`、`2 1`、`2 2`、`3 1`、`3 2` (D)一行:`1 1`
### 第 7 题(出处:第 7 篇 推导式)
```python
print({x * x for x in range(1, 4)})
```
输出是?(A)`{1, 4, 9}` (B)`[1, 4, 9]` (C)`{1, 4, 9, 16}` (D)报错
### 第 8 题(出处:第 8 篇 实战:猜数字的循环骨架)
```python
n, c = 0, 0
while True:
n += 2
c += 1
if n > 10:
break
print(c)
```
输出是?(A)4 (B)5 (C)6 (D)7
### 第 9 题(出处:第 9 篇 实战:图案打印)
```python
for i in range(1, 4):
print("*" * i)
```
输出是?(A)三行星号,个数依次 1、2、3 (B)三行星号,个数依次 3、2、1 (C)只有一行 3 个星号 (D)报错
### 第 10 题(出处:综合,第 7 篇生成器表达式)
```python
print(sum(x for x in range(1, 5) if x % 2 == 0))
```
输出是?(A)6 (B)4 (C)10 (D)报错,生成器表达式不能参与求和
### 计分与回读表
每题 10 分。先按下表判断分数段:8 分以下,把错题对应的篇目重读一遍再返场;9~10 分,直接挑战后面的编程题。分数只说明哪些点没建起来,「回读」才是补齐动作——错题对应篇目一览:
| 题号 | 考点 | 回读篇目 |
|---|---|---|
| 1 | `elif` 链从上到下首个命中即结束 | 第 1 篇「分支的执行流程」小节 |
| 2 | `match` 的或模式、守卫(guard)与 `_` 兜底 | 第 2 篇「结构化模式」小节 |
| 3 | `continue` 跳过本轮剩余语句 | 第 3 篇 + 第 5 篇「continue」小节 |
| 4 | `range` 三参数:含头不含尾、步长 | 第 4 篇「range 的三种形态」 |
| 5 | `for-else`:未 break 结束时执行 else | 第 5 篇「else 子句」小节 |
| 6 | `break` 只终止最近一层循环 | 第 6 篇「嵌套与作用域」 |
| 7 | 集合推导式与序列推导式的区别 | 第 7 篇「字典与集合推导式」 |
| 8 | `while True` 必须配合显式 `break` | 第 8 篇「猜数字的循环骨架」 |
| 9 | 字符串乘法与行号驱动输出 | 第 9 篇「直角三角形」小节 |
| 10 | 生成器表达式按需产出、`sum` 逐个累加 | 第 7 篇「生成器表达式」小节 |
## 解析:每题为什么
**第 1 题:C。** `elif` 链从上到下检查,命中第一个为真的分支后整条链结束,后面不再看。`n = 75` 先命中 `n >= 60` 的 `"C"` 分支,位于其后的 `n >= 70` 分支永远轮不到——这正是第 1 篇强调的「先宽后窄陷阱」:区间倒序(90 以上 → 60 以上 → 70 以上)会让中间档位永久不可达。若把条件改为 `70 <= n < 90` 这类窄区间并用倒序排列,三个分支才各得其所。
**第 2 题:B。** `match` 自上而下尝试各 `case`:先试或模式(OR pattern)`1 | 2`,9 不匹配;再试带守卫(guard)的 `case _ if x > 5`——守卫是附加在模式上的布尔条件,`_` 匹配任何值但守卫 `9 > 5` 为真才进入,于是 `r = "大"`;`case _` 兜底分支根本没机会执行。容易错在把守护条件当成 `case` 前的 `if`,或忘了「守卫为假时继续往下试」的规则。
**第 3 题:A。** 循环把 1 到 10 逐个加进 `total`,`i % 3 == 0` 时 `continue` 跳过本轮剩余的 `total += i`,因此 3、6、9 三个数被剔除:1 到 10 的总和 55 减去 18(3+6+9)得 37。`continue` 在 `while` 里的语义与 `for` 一致:跳过本轮剩余语句,直接回到条件判断(`for` 则是取下一个元素)。
**第 4 题:A。** `range(2, 11, 3)` 从 2 开始、每次加 3、不含 11:2、5、8。`range` 的「含头不含尾」与负步长(`range(10, 3, -2)` 得 10、8、6、4)是第 4 篇的两个核心边界,步长与停止临界点差一即错。`list()` 只是把 `range` 对象显形,不是考点本身。
**第 5 题:B。** `for-else` 的 `else` 在循环**没有被 break 打断**、正常耗尽时执行。7 在 2 到 6 之间没有因子,内层循环完整走完,`else` 输出 `"质数"`。这是第 5 篇「循环后收尾」的经典应用:把「是否找到」的布尔旗标省掉,语义直接写在结构里。若 n 换成 9,找到因子 d = 3 后 `break`,else 不执行。
**第 6 题:A。** 内层循环在 `j == 2` 时 `break`,break 只终止**最近一层**循环,外层不受影响继续推进 i。于是每一行 i 都只打印 `j = 1` 一项,共三行。对 C 语言的 `break` 经验尤其要警惕:Python 没有「标注跳出哪一层」的语法,逃出多层嵌套只能靠旗标变量或结构重排,这正是第 6 篇嵌套主题的易错核心。
**第 7 题:A。** `{x * x for x in range(1, 4)}` 是集合推导式(set comprehension),产出集合 `{1, 4, 9}`。集合无序,但打印内容确定且与 `[1, 4, 9]` 的字面顺序一致(CPython 对小型整数集合保持插入序);集合还会自动去重。选项 B 是列表推导式的产物,类型错误;选项 C 的 16 是 `range(1, 4)` 到达不到 `x = 4` 的幻觉项——`range` 含头不含尾再次作案。
**第 8 题:C。** `while True` 没有退出条件,必须靠循环体内的 `break` 主动终止。每轮 `n` 加 2:2、4、6、8、10、12,`n > 10` 时 break,共 6 轮。第 8 篇猜数字把这一骨架用在「猜中才退出」上;本题提醒:`while True` 本身没有错,错误永远是「忘写或错写 break」——写前先问自己「哪条路径退出」。
**第 9 题:A。** 字符串乘法 `"*" * i` 产生 i 个星号,`for i in range(1, 4)` 让 i 依次取 1、2、3,输出三行递增星号。这是第 9 篇图案打印的最小单元:行号驱动行内容(行-列建模的退化形式)。常见错因是把 `range(1, 4)` 误记成 `range(4)` 或忘记含头不含尾——本題的 i 永远取不到 4。
**第 10 题:A。** 生成器表达式(generator expression)`(x for x in range(1, 5) if x % 2 == 0)` 按需产出 2 与 4,`sum` 逐个累加得 6。它与列表推导式的差别在第 7 篇讲过:推导式立刻建出整个列表,生成器表达式惰性产出、一次只给一个值,适合喂给 `sum`、`len` 这类「消费完就丢」的目标。选项 D 是典型幻觉——生成器完全可以求和,只是不能索引、不能重复遍历。
三道易错共性再强调一遍:**一是差一(range 的含头不含尾、`n - 1` 的边界);二是作用域(break 只跳出本层、continue 只跳过本轮);三是类型(input 的字符串、集合与列表的分野)**。这三条正是第三章全部易错点的收敛。
## 编程题:三道与参考解答
三道题按难度递进,全部只用前九篇的知识即可完成;参考解答同样未使用函数与异常处理,以便你对照自己的写法。
**第 1 题(嵌套 + break + else):打印 2~100 的全部质数。** 用双层循环:外层遍历候选数 n,内层试探因子;找到因子立即 `break`,循环耗尽未 break 则 n 是质数。
```python
for n in range(2, 101):
for d in range(2, n):
if n % d == 0:
break
else:
print(n, end=" ")
print()
```
```text
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
```
内层 `for-else` 直接表达「没找到因子」;`print(n, end=" ")` 让质数在同一行输出——`end` 参数控制 print 的行尾字符,默认是换行。可以顺手做个复杂度分析:内层最坏试探到 `n/2`,整体量级约 O(n²) 次取余,对 100 以内的规模完全够用;后续课程会学到更快的筛法。
**第 2 题(while True + break + 输入守卫):循环录入整数求和。** 反复读取整数并累加,输入 `-1` 结束;非数字输入给出提示、不计入统计;结束后报告有效个数与总和。
```python
total = 0
count = 0
while True:
s = input("输入整数(-1 结束):").strip()
if s == "-1":
break
if not s.isdigit():
print("无效输入,请重新输入")
continue
total += int(s)
count += 1
print(f"共 {count} 个有效数,总和 {total}")
```
```text
输入整数(-1 结束):5
输入整数(-1 结束):0
输入整数(-1 结束):12
输入整数(-1 结束):abc
无效输入,请重新输入
输入整数(-1 结束):-1
共 3 个有效数,总和 17
```
一个真实的坑:`"-1"` 含负号,`"-1".isdigit()` 是 `False`,所以退出条件必须**先于** isdigit 判断,否则永远退不出去。`isdigit` 是第 9 篇引入的字符串方法,这里是它的第二个实战场景;异常处理(如 `try/except ValueError`)要到第七章才正式登场,届时两种守卫方式的取舍会有完整讨论。
**第 3 题(行-列建模):输出 n 行数字金字塔。** 第 i 行先打印 `n - i` 个空格,再打印数字 i 重复 i 次(1、22、333……),完全复用第 9 篇的公式三步法。
```python
n = 5
for i in range(1, n + 1):
print(" " * (n - i) + str(i) * i)
```
```text
1
22
333
4444
55555
```
边界自检:i = 1 时 4 个空格加一个 `"1"`;i = 5 时 0 空格加五个 `"5"`,底边打满。`str(i) * i` 的奥妙在于:字符串乘法要求整数乘数,这里恰好用行号当乘数,字符与数字顺手统一了。
## 综合编程任务:成绩统计系统
把本章所有技能装进一个程序。需求:① 反复录入成绩(0~100 的整数),输入 `q` 结束;② 等级划分 A(90~100)、B(80~89)、C(70~79)、D(60~69)、F(<60);③ 非数字、超范围输入要拦截并提示,不能崩溃;④ 结束后输出记录数、平均分(保留 1 位小数)、最高分、最低分与各等级人数;⑤ 一条记录都没有时给出单独提示。参考实现(Python 3.12 实测):
```python
print("===== 成绩统计 =====")
print("逐个录入成绩(0~100),输入 q 结束")
total = 0
count = 0
highest = -1
lowest = 101
n_a = n_b = n_c = n_d = n_f = 0
while True:
s = input("成绩:").strip()
if s == "q":
break
if not s.isdigit():
print("无效输入,请输入 0~100 的整数或 q")
continue
score = int(s)
if score > 100:
print("成绩不能超过 100")
continue
total += score
count += 1
if score > highest:
highest = score
if score < lowest:
lowest = score
if score >= 90:
n_a += 1
elif score >= 80:
n_b += 1
elif score >= 70:
n_c += 1
elif score >= 60:
n_d += 1
else:
n_f += 1
if count:
print(f"共 {count} 条记录,平均分 {total / count:.1f}")
print(f"最高 {highest},最低 {lowest}")
print(f"A(90-100):{n_a} B(80-89):{n_b}")
print(f"C(70-79):{n_c} D(60-69):{n_d} F(<60):{n_f}")
else:
print("没有录入任何成绩")
```
一次完整会话(真实运行):
```text
===== 成绩统计 =====
逐个录入成绩(0~100),输入 q 结束
成绩:85
成绩:92
成绩:57
成绩:60
成绩:101
成绩不能超过 100
成绩:abc
无效输入,请输入 0~100 的整数或 q
成绩:75
成绩:88
成绩:q
共 6 条记录,平均分 76.2
最高 92,最低 57
A(90-100):1 B(80-89):2
C(70-79):1 D(60-69):1 F(<60):1
```
程序把本章知识用得滴水不漏,逐段点评:**录入循环**是第 8/9 篇的 `while True + input + break` 骨架,`q` 的退出判断先于 isdigit(编程题 2 的坑在这里再次出现);**两重守卫**一前一后:isdigit 拦截非数字(配合 `continue` 跳过本轮),超范围判断拦截越界成绩,`int(score)` 永远只处理纯数字串,这正是第 9 篇输入管线(strip → isdigit → int)的展开版;**等级链**是测验第 1 题分支顺序的正向例子——A 到 F 的条件互斥且区间递减,任意成绩恰好落入一档;**最高/最低分**用两个初始哨兵值(-1 与 101)加比较更新实现,刻意不用 `max`/`min` 内置函数,为的是检验「比较 + 变量更新」的循环基本功——你把哨兵换成 `None` 的写法会在第四章学完比较后自然浮现;**输出**用 f-string 的 `.1f` 控制平均分精度,这是第二章的收尾技能;**空数据分支** `if count:` 是真值测试(第二章布尔篇)在统计类程序的标配收尾。均值 76.2 可以用心算复核:六条成绩总和 457,除以 6 得 76.17 进位为 76.2。
## 学习自检清单
逐项自测:能**不看任何参考资料**完成就画勾;画不出勾的项,回读对应篇目再试,不要往下走。
- 能默写 `if/elif/else` 骨架,并讲清「elif 链命中即止」与三个独立 `if` 的差别(第 1 篇)。
- 能说出 `match` 的四个要素:`case` 顺序、或模式 `1 | 2`、守卫 `if` 与兜底 `_`,各自何时触发(第 2 篇)。
- 能解释 `while` 的条件在做真值测试:空字符串、0、空容器为什么能当退出哨兵(第 3 篇 + 第二章布尔篇)。
- 能写出 `range` 的三种形态,并立刻说出 `range(10, 3, -2)` 的结果(第 4 篇)。
- 能现场演示 `break` 跳出本层、`continue` 跳过本轮,并说清 `for-else` 的 else 在什么条件下执行(第 5、6 篇)。
- 能在白纸上先推导图案公式、再用 10 行内代码画出等腰三角形与空心矩形(第 9 篇)。
- 能徒手写出 `[x * x for x in range(5)]` 与 `sum(x for x in ... if ...)`,并说出两者对内存的不同态度(第 7 篇)。
- 能不用复制粘贴写出「while True + input + 分发 + break」的菜单骨架,并解释为什么 break 的缩进是致命的(第 9 篇)。
- 能处理「用户输入的不是数字」:strip → isdigit → int 的管线顺序,以及负号必须先于 isdigit 判断(第 9 篇 + 本篇编程题 2)。
- 能完整复现本篇综合任务:不查资料、一次运行通过全部测试输入。
## 小结
第三章在此闭环。十篇的路线图值得最后回望一次:`if/elif/else` 与 `match` 回答「程序往哪走」,`while` 与 `for` 回答「走多少遍」,`break`/`continue`/`else` 回答「怎么提前收场」,嵌套与推导式回答「结构如何组合」,FizzBuzz、猜数字、图案工坊与成绩统计则证明这些结构能拼出真实可用的程序。全章没有出现函数、类与异常处理——不是它们不重要,而是流程控制必须先独立站住:第四章将把 `def` 扶正,让本测验里反复出现的「重复代码」第一次拥有合法的收纳容器;第七章再用 `try/except` 接管「用户输入不可信」这类本测验只能靠 isdigit 硬扛的场景。拿着这份自检清单回读错题,我们第四章见。