🧠 第三章 编程语言
📄 一句话概括

range(1, 6) 是 1 到 5,不含 6。 for i in range(100) 套 for j in range(i, 100) 实际只转 5050 次, 按 100×100 算会多算 4950 次。

📝 本节和前面几节的关系

上一节让程序选路,这一节让它重复。循环最容易错的两处:边界差一,和嵌套次数算错。

一、range 的三个参数

range 是最常用的循环来源,三个参数分别管起点、终点、步长: - range(1, 6) 给 [1, 2, 3, 4, 5]——含头不含尾,没有 6。 - range(6) 给 0 到 5,起点省略就是 0。 - range(0, 10, 3) 给 [0, 3, 6, 9],步长 3。 - range(5, 0, -1) 给 [5, 4, 3, 2, 1],步长为负就倒着走。 「含头不含尾」是差一错误(off-by-one)的头号来源。要 1 到 100,就写 range(1, 101)。

二、for 和 while 各管一类事

for 管「已知要转多少次」;while 管「转到条件不成立为止」。 Demo 用两种写法各算一遍 1 加到 100,都给 5050。结果一样,适用场景不同。 for 天然不会死循环;while 忘了更新条件就死循环。 写 while 时先把「什么时候退出」写成注释,再动手。 while True 加 break 也是常见写法——把退出条件写在中间,比塞进 while 后面更清楚。

三、break 和 continue

两个都能改变流程,方向相反: - break 直接跳出整个循环。 Demo 在 1 到 10 里找第一个能被 7 整除的数,看过的数是 [1, 2, 3, 4, 5, 6, 7]——到 7 就停了。 - continue 跳过本次剩下的语句,继续下一轮。Demo 留下奇数 [1, 3, 5, 7, 9]。 差一的是很容易搞混:break 之后循环结束,continue 之后循环继续。 写错一个,要么少处理一半数据,要么多算一轮。

四、嵌套循环的次数不能拍脑袋

这一条最有价值。Demo 写的是:for i in range(100) 里面套 for j in range(i, 100)。 实际转了多少次?5050 次。不是 10000 次。 因为内层的范围随外层缩小,第一次 100 次,第二次 99 次……最后 1 次。 按 100×100 算会多算 4950 次——差了将近一倍。 估算训练耗时、估算标注工时,用的都是这个数,差一倍就是差一倍的钱。 Demo 最后演示了 for-else:循环正常跑完才执行 else,被 break 打断就不执行。 37 被 37 整除,break 掉,else 没跑;97 在 2 到 49 里都不整除,走了 else。 这个语法不常用,但判断「找没找到」很干净:找到了就 break,没找到才走 else, 不用额外加一个标志变量。

五、动手 Demo:range 的边界、break 的时机、嵌套的次数

先打四种 range 的取值;再用 for 和 while 各算一遍 1 加到 100; 然后演示 break 与 continue 的差别;接着实算一个嵌套循环的真实次数并与 100×100 对比; 最后演示 for-else 的两种走向。

print(list(range(1, 6)))        # [1, 2, 3, 4, 5]
print(list(range(0, 10, 3)))    # [0, 3, 6, 9]

for i in range(1, 101):
    s += i                      # 5050

cnt = 0
for i in range(100):
    for j in range(i, 100):
        cnt += 1                # 5050,不是 10000

for target in (37, 97):
    for i in range(2, 50):
        if target % i == 0:
            break
    else:
        print(target, '走 else')

实跑输出:

--------------------------------------------------------------------
循环语句:range 的边界、break 的时机、嵌套的次数
--------------------------------------------------------------------
  range(1, 6)              [1, 2, 3, 4, 5]
  range(6)                 [0, 1, 2, 3, 4, 5]
  range(0, 10, 3)          [0, 3, 6, 9]
  range(5, 0, -1)          [5, 4, 3, 2, 1]
  range 含头不含尾——range(1, 6) 是 1 到 5,没有 6。

  for 累加 1+...+100       5050
  while 累加 1+...+100     5050

  break 与 continue 的差别(在 1~10 里找第一个能被 7 整除的):
  break:看过这些数        [1, 2, 3, 4, 5, 6, 7]
  continue:留下的奇数     [1, 3, 5, 7, 9]

  for i in 100:for j in range(i,100) 5050 次
  直接写 100*100 会算成    10000 次(错了 4950 次)
  嵌套循环的次数必须按内层的实际范围算,别拿外层乘外层。

  for-else:循环正常跑完才执行 else,被 break 打断就不执行。
    37:被 37 整除,break 掉
    97:2~49 都不整除,走 else

四种 range 一眼看完,重点是第一行:range(1, 6) 没有 6。要 1 到 6 得写 range(1, 7)。 for 和 while 都给 5050。 同一个结果两种写法,while 那份多了一个「k 要加 1」的手工活,忘了就死循环。 break 那组看过的数是 [1, 2, 3, 4, 5, 6, 7],到 7 就停; continue 那组留下 [1, 3, 5, 7, 9],偶数的被跳过。 嵌套那一组是本节重点:实算 5050 次,按 100×100 是 10000 次,差 4950 次。 约一半的工作量被错估。 for-else 两行:37 被 37 整除直接 break,else 不执行; 97 走完了 2 到 49 都不整除,else 执行。

30 秒实验

  1. 把 range(i, 100) 改成 range(100),重新数一遍次数,验证是 10000。
  2. 用 for-else 写一个「在列表里找某元素」的函数,不用标志变量。
  3. 想一想:为什么估算训练时间时,嵌套循环的次数必须实算?

常见误区

  1. 「range(1, 6) 是 1 到 6」——含头不含尾,少了最后一个。
  2. 「两层 100 的循环就是一万次」——内层范围随外层变,可能只有一半。
  3. 「break 和 continue 都是跳出」——一个跳整轮,一个只跳本次。
自测三问(点开对答案) 1. `range(1, 6)` 给出哪些数? —— 1 到 5,含头不含尾,没有 6。 2. `break` 和 `continue` 的区别是什么? —— `break` 结束整个循环,`continue` 只跳过本次剩余语句。 3. `for i in range(100)` 套 `range(i, 100)` 一共转多少次? —— 5050 次,等于 100+99+…+1。

语法六件套讲完,下面进实战:五个小实例,每个都带真实运行结果。

相关笔记