02 · 流程控制(Control Flow)

📅 预计 60 分钟 | ⭐ = 高频考点
✍️ 本讲配套练习:本地 python grader.py 02,或网页练习


2.1 条件语句 if ⭐

想象你站在一个岔路口:天气好就走小路,下雨就走大路,天黑了干脆回家。程序要「看情况走不同的路」,靠的就是条件语句。Python 的 if 语法核心只有两个东西:冒号 + 缩进。冒号告诉 Python「下面是被这个条件管住的代码」,缩进告诉 Python「哪些行归这个条件管」。

score = 85
if score >= 90:
    print("优秀")          # 只有 >=90 才打印
elif score >= 60:          # else if 的缩写
    print("及格")          # 第一个为真的分支才会执行
else:
    print("不及格")
# 输入: score=85 → 输出: 及格

规则三句话记住:

  • if / elif / else 后面跟条件表达式,结果必须是布尔值(或能转成布尔值的值)。
  • 一个 if 可以有多个 elif,但只会从上到下执行第一个为真的分支,后面的分支全部跳过。有点像自动售货机:投了 5 元,先看能不能买可乐,能就出可乐,绝不再问你要不要雪碧。
  • else 可以省略;elif 必须跟在 if 之后,不能单独出现。

条件表达式除了比较大小,还能做逻辑组合和成员判断:

x = 5
age = 20
has_id = True
is_rain = False
name = "小明"
scores = {"小明": 90}

# 数值判断
if x > 0: ...                      # 大于
if x == 0: ...                     # 相等——注意是 ==,不是 =
# 逻辑组合
if age >= 18 and has_id: ...       # 且:两个都满足才通过
if x < 0 or x > 100: ...           # 或:满足一个就通过
if not is_rain: ...                # 非:取反
# 判断是否在容器里
if name in ["小明", "小红"]: ...    # in 运算符 ⭐
if key in scores: ...              # 字典里有没有这个键
if ch in "aeiou": ...              # 字符串里有没有这个字符

⚠️ 常见错误

  1. if x = 5= 是赋值,条件里要判断相等必须写 ==。这一处写错,程序不报错但行为完全不对,是改错题最爱出的一处。
  2. 漏掉行尾冒号:if x > 0 少写 :,直接 SyntaxError,程序跑不起来。
  3. 缩进混用 Tab 和空格:PEP 8 规定用 4 个空格,混用会报 TabError: inconsistent use of tabs
  4. elif 写成 else if:那是 C 语言的写法,Python 里没有 else if 这个关键字。

💡 记忆口诀

「if 和 elif 是单选题,从上往下选第一个真的;else 是兜底,谁都不选就选它。」


2.2 for 循环 + range() ⭐

for 循环像是工厂流水线:传送带把一件件货物送过来,你一件件处理,直到全部处理完。在 Python 里,for 用来遍历一个序列(列表、字符串、range…),把每个元素轮流取出来用。

for i in range(5):         # 只给一个数 = 从 0 开始
    print(i, end=" ")      # 输入: range(5) → 输出: 0 1 2 3 4

range() 是 Python 生成数字序列的函数,三种写法对应三种含义:

range(5)            # 0,1,2,3,4         从 0 开始,到 5 前停
range(1, 6)         # 1,2,3,4,5         指定起点和终点
range(0, 10, 2)     # 0,2,4,6,8         第三个参数是步长 step
# 输入: 上面三行 → 输出: 各自注释里的数字序列

⭐ 全讲最重要的一个坑:range(start, stop, step)左闭右开——start 包含,stop 不包含。range(1, 5) 是 14,不是 15;想取到 5,得写 range(1, 6)。这一条几乎每次考试都考。

遍历列表和字符串,直接 for 过去就行:

names = ["小明", "小红", "小刚"]
for name in names:
    print(name)              # 输出: 小明 / 小红 / 小刚

for ch in "Python":
    print(ch)                # 输入: "Python" → 输出: P y t h o n(每个字母一行)

如果既想要元素,又想知道它在第几个位置,用 enumerate() ⭐:

for i, name in enumerate(names):   # i 从 0 开始
    print(i, name)
# 输出:
# 0 小明
# 1 小红
# 2 小刚

enumerate 的中文是「枚举」:一边数数,一边取值,一次性把索引和元素都给你。

⚠️ 常见错误

  1. 混淆 range(1, 5)range(1, 6):左闭右开,stop 这个数本身不进循环。
  2. range(5) 以为是从 1 到 5——其实从 0 到 4。很多同学打印出来看到 0 很惊讶。
  3. enumerate(names) 忘了写 i, :写成 for name in enumerate(names),拿到的是 (0, '小明') 这样的元组,不是名字本身。
  4. 遍历列表时顺手改列表(比如边遍历边删)容易出错,初学阶段先只读不改。

💡 记忆口诀

「range 像半开的门:进门(左闭)欢迎,出门(右开)再见;步长就像跨台阶,一格两格随意跨。」


2.3 while 循环

while 是「条件满足就一直做」的循环,更像一台 ATM 取款机:只要账户里还有钱(条件为真),就能继续取。它不关心「取多少次」,只关心「现在还能不能取」。

n = 1
while n <= 5:
    print(n)          # 输出: 1 2 3 4 5
    n += 1            # 千万别忘了让条件最终为假!

条件判断在每轮循环开始时做:为真就进循环体,为假就退出。所以 while 的循环体里必须有一句让条件逐渐变假的代码(上面的 n += 1),否则条件永远为真,就成了死循环。

while 特别适合「不知道要循环多少次」的场景——比如不停地取数字的每一位,直到取完:

# 求一个整数各位数字之和(例如 123 → 1+2+3 = 6)
n = 123
total = 0
while n > 0:
    total += n % 10   # 取出最后一位,累加
    n //= 10          # 去掉最后一位
print(total)          # 输入: n=123 → 输出: 6

这里的两个运算符是本节重头戏 ⭐:

  • % 取余:123 % 10 得 3,取出个位。
  • // 整除:123 // 10 得 12,把个位抹掉。

两者配合,就能一位一位地把数字「剥开」,这叫拆位

如果确实想让循环永远跑、直到某个条件从内部退出,就用 while True:,靠内部的 break 跳出(见 2.4 节)。注意:while True 本身是个「无限循环」,全靠 break 兜底,写不好就是死循环。

⚠️ 常见错误

  1. 忘记更新循环变量:while n <= 5: 里漏写 n += 1,n 永远是 1,死循环。跑 grader 时如果某题「超时」,多半就是死循环。
  2. 边界写反:拆位用 while n > 0 写成了 while n >= 0,而 n 最终会变成 0,条件永远为真,死循环。
  3. %// 方向搞反:n % 10 是取个位,n // 10 是去个位,两个都记成除就乱了。

💡 记忆口诀

「for 管次数(已知循环几遍),while 管条件(未知何时停);while 循环像上发条,每圈记得松一扣。」


2.4 break / continue ⭐

循环跑着跑着,可能需要提前退场,或者跳过某一次。这两个关键词就是干这个的:

  • break立刻跳出整个循环,后面的循环轮次统统不执行了。
  • continue跳过本次循环的剩余代码,直接进入下一轮。

打个比方:你在公交车上报站。break 是「司机到站直接收车下班,后面站都不去了」;continue 是「这一站没停好,直接滑到下一站,但线路还要继续跑」。

for i in range(1, 10):
    if i == 3:
        continue        # 跳过 3,不打印
    if i == 7:
        break           # 到 7 就彻底结束循环
    print(i)            # 输出: 1 2 4 5 6

break 最经典的用法:在循环里找第一个符合条件的对象,找到就立刻收工,不用遍历完整个序列:

nums = [3, 8, 14, 21, 30]
for x in nums:
    if x % 7 == 0:
        print("第一个能被 7 整除的数:", x)   # 输出: 第一个能被 7 整除的数: 14
        break                              # 找到就退出,不再往后看

continue 则常用于「把不符合条件的元素过滤掉」——比如只打印奇数:

for i in range(1, 8):
    if i % 2 == 0:
        continue      # 偶数直接跳过
    print(i, end=" ") # 输入: range(1,8) → 输出: 1 3 5 7

注意看:continue 不是「从头开始」,而是「跳过本轮 continue 之后剩下的代码,直接进入下一轮」。

⚠️ 常见错误

  1. breakcontinue 用反:想跳出整个循环却用了 continue,结果一直跳过不退出;想跳过当前轮却用了 break,提前把循环结束了。
  2. continue 之后的代码不会执行:如果打印写在 continue 之后,被跳过的元素永远不会打印(这符合预期,但初学容易困惑)。
  3. break 只跳出最近的一层循环:嵌套循环里想跳出外层,需要外层自己判断(2.5 节会讲)。

💡 记忆口诀

「break 是急刹车(整辆车停),continue 是这一站踩油门(跳过这站继续跑)。」


2.5 嵌套循环

循环里再套循环,叫嵌套循环。想象一张课程表:外层循环是星期几,内层循环是第几节课——每天都要把 4 节课走完,才会到第二天。所以内层跑完一轮,外层才 +1

for i in range(1, 4):        # 外层: 星期几
    for j in range(1, 4):    # 内层: 第几节
        print(i, j)          # 共 3×3 = 9 次
# 输入: 上面循环 → 输出: 1 1 / 1 2 / 1 3 / 2 1 / 2 2 / 2 3 / 3 1 / 3 2 / 3 3

嵌套循环最经典的两种题型:打印三角形九九乘法表 ⭐。核心技巧是:让内层循环的次数跟着外层走,就能画出渐变的形状。

# 直角三角形: 第 i 行打印 i 个 *
for i in range(1, 4):
    for j in range(i):       # 内层循环次数 = 外层当前的 i
        print("*", end="")   # end="" 让星号在同一行连续打印
    print()                  # 打完一行再换行
# 输出:
# *
# **
# ***
# 九九乘法表(核心考点,背下来)
for i in range(1, 10):
    for j in range(1, i + 1):      # 第 i 行只打印到 i
        print(f"{j}x{i}={i*j}", end="  ")
    print()                        # 每行结束换行
# 输出前两行:
# 1x1=1
# 1x2=2  2x2=4

这里两个细节是考点 ⭐:

  • print(..., end=""):默认 print 每打一次就换行,用 end 参数可以改成「不打换行、接空格或别的字符」。
  • f"{j}x{i}={i*j}":格式化字符串,把变量填进花括号里,f 开头、内容用引号包住。

⚠️ 常见错误

  1. 内层循环范围写错:乘法表里 range(1, i+1) 写成 range(1, 10),每行都打印 9 项,三角形也画歪了。
  2. 忘记在每行结束后 print() 换行,所有内容挤成一坨。
  3. end="" 打印三角形后忘记换行,第一行 * 和第二行 ** 会连成 ***
  4. 混淆外层变量和内层变量:内层条件该用 i 的地方用了 j,图形就画歪了。

💡 记忆口诀

「外层跑得慢,内层跑得快;内层一轮完,外层进一步。谁管行、谁管列,画图先分清。」


2.6 常见计数套路(考试爱考)

循环配条件,能解决一大类「统计」问题。下面三个套路必须背下来:

# 套路一: 统计个数(数偶数)
nums = [3, 8, 1, 6, 10]
count = 0
for x in nums:
    if x % 2 == 0:
        count += 1
print(count)                 # 输入: nums=[3,8,1,6,10] → 输出: 3(8、6、10 三个偶数)

# 套路二: 求和(1..100 累加,高斯公式验证 = 5050)
total = 0
for i in range(1, 101):
    total += i
print(total)                 # 输入: 1..100 → 输出: 5050

# 套路三: 找最值(不用内置 max)
nums = [3, 1, 4, 1, 5]
mx = nums[0]                 # 先假设第一个最大
for x in nums[1:]:           # 从第二个开始逐个比较
    if x > mx:
        mx = x
print(mx)                    # 输入: nums=[3,1,4,1,5] → 输出: 5

三个套路长得像,但目的不同:计数count求和total找最值用「先假设、再逐个挑战」。考试时先看清题目要的是「个数」「总和」还是「最大/最小值」,别拿错变量。

找最值的另一种写法(把初始化放进循环里)容易出错,推荐上面的「先取第一个」写法,它天然能处理除空列表外的所有情况。

⚠️ 常见错误

  1. 计数时 count 忘记初始化成 0,直接用会报 NameError
  2. 找最值时初始化 mx = 0:如果列表全是负数,最大值永远找不到(0 比所有负数都大)。正确做法是 mx = nums[0]
  3. 累加 += 写成了 =+,变成每次赋值而非累加。
  4. 循环内忘记缩进:count += 1 放到循环外,只加了一次,答案全错。

💡 记忆口诀

「计数加一 count += 1,累加 total += x,找最大先拿第一个再比。」


2.7 数字拆位与取模技巧(% 和 //)⭐

处理数字题时,「取出一位」「丢掉一位」是两个高频动作,核心是 %// 这对搭档:

n = 123
print(n % 10)    # 输出: 3   (% 10 取个位)
print(n // 10)   # 输出: 12  (// 10 丢掉个位)
print(n // 100)  # 输出: 1   (// 100 丢掉后两位)
# 输入: n=123 → 输出: 3 / 12 / 1

配合 while,就能把任意长度的数字一位一位剥开(各位数字之和、数位个数、数字反转,全靠这套动作):

# 求各位数字之和: 123 → 1+2+3 = 6
n = 123
total = 0
while n > 0:
    total += n % 10    # 取走个位
    n //= 10           # 去掉个位
print(total)           # 输出: 6

另一个经典应用是辗转相除法求最大公约数(gcd)⭐。思路很巧妙:gcd(a, b) 等于 gcd(b, a % b),一直套用直到余数为 0,剩下那个数就是答案:

# 求 48 和 36 的最大公约数
a, b = 48, 36
while b != 0:
    a, b = b, a % b
print(a)               # 输出: 12
# 过程: 48,36 → 36,12 → 12,0 → 余数为 0 结束,答案是 12

a, b = b, a % b 这行是同时赋值:右边的 ba % b 先算好,再一起赋给 ab,不会出现「用旧 a 算 b」的错位。

考试里「数字题」十有八九要用 %//,练熟这套拆位动作,很多题就是套模板。

⚠️ 常见错误

  1. n % 10n // 10 方向记反:% 10 拿个位,// 10 去个位,别搞混。
  2. 拆位循环写成 while n >= 0,而 n 会被减到 0,就永远不结束(死循环)。应该用 while n > 0
  3. 处理 0 或负数时的边界:0 没有个位可拆(拆出来全是 0);负数取模结果和直觉不同,先转成正数再处理。

💡 记忆口诀

% 10 剪指甲(拿个位),// 10 砍一刀(去个位)。一个取、一个去,数字一位位剥光。」


2.8 综合实战:循环 + 条件组合

把前面所有招数拼起来,就是考试里最后几道「大题」的样子。这里练两个经典综合题,正好对应本讲练习里最后的综合题。

例一:求区间内所有质数之和(嵌套循环 + 判断 + 累加三合一)

def sum_primes(n):
    """返回 2 到 n 之间所有质数之和。"""
    total = 0
    for x in range(2, n + 1):      # 外层: 检查每个数
        is_prime = True
        for i in range(2, x):      # 内层: x 能被谁整除?
            if x % i == 0:
                is_prime = False
                break              # 找到一个因子就够了,提前退出
        if is_prime:
            total += x             # 只有质数才累加
    return total

print(sum_primes(10))   # 输入: n=10 → 输出: 17(2+3+5+7)
print(sum_primes(20))   # 输入: n=20 → 输出: 77

思路拆解:外层循环负责「过一遍 2..n 每个数」,内层循环负责「判断这个数是不是质数」,最后只把质数累加。每个数字都有自己的「任务分工」,这就是嵌套循环的意义——一层管一件事。

例二:考拉兹猜想(Collatz)步数(while + 分支 + 计数)

规则:给一个正整数 n,偶数就除以 2,奇数就乘 3 加 1,反复操作直到变成 1,问要几步。

def collatz_steps(n):
    """返回 n 通过偶数÷2、奇数×3+1 到达 1 所需的步数。"""
    steps = 0
    while n != 1:          # 没到 1 就继续
        if n % 2 == 0:
            n //= 2        # 偶数分支
        else:
            n = 3 * n + 1  # 奇数分支
        steps += 1         # 每操作一次记一步
    return steps

print(collatz_steps(6))   # 输入: n=6 → 输出: 8(6→3→10→5→16→8→4→2→1)
print(collatz_steps(1))   # 输入: n=1 → 输出: 0(本来就是 1,一步都不走)

这类综合题的通用解题顺序:① 读题确定「每轮做什么」→ ② 选 for 还是 while(次数已知用 for,条件未知用 while)→ ③ 决定循环里用不用 break/continue → ④ 想好要累计/统计哪个变量。按这个顺序写,不慌。

⚠️ 常见错误

  1. 把 while 写成 for:考拉兹循环次数不确定,得用 while;用 for 就得先算出步数,那就本末倒置了。
  2. 质数判断内层范围写错:range(2, x) 写成 range(2, x+1)x % x == 0 恒成立,所有数都被判成「不是质数」。
  3. 综合题变量名重复使用:外层变量 x 和内层变量 i 混用,逻辑就乱了。
  4. 忘记给累计变量初始化(total = 0 / steps = 0),直接 NameError

💡 记忆口诀

「先想每轮干啥,再选 for 还是 while;break 提前收工,continue 跳过这轮;计数求和找最值,变量先归零再累加。」


📌 双语术语表(本讲)

中文 English
条件语句 conditional / if statement
循环 loop
迭代 iteration
迭代器 iterator
死循环 / 无限循环 infinite loop
跳出 break
跳过 continue
嵌套 nested
索引 index
步长 step
取余 / 模 modulo / remainder
整除 floor division
最大公约数 greatest common divisor (GCD)
质数 prime number

⭐ 本讲考点清单

  1. range(start, stop, step) 左闭右开
  2. == vs =、冒号、缩进
  3. break(跳出)vs continue(跳过)
  4. while 必须能终止,防死循环
  5. enumerate() 同时拿索引和值
  6. 嵌套循环(乘法表、三角形)
  7. in 判断元素是否存在
  8. 计数/求和/找最值三大套路
  9. % 取个位、// 去个位,拆位套路与辗转相除法
  10. 综合题:质数之和、考拉兹步数(for/while + 条件组合)