🧮 第2章 变量和数组
📄 一句话概括

判据只有一条:同一类数据要「一起处理」,就该用数组。 不是「数据多到几十条才用」,而是「我要对每一个都做同一件事」的时候用。 反过来,几条数据各有各的用途,硬塞进数组反而看不出谁是谁。

📝 本章和前面几章的关系

上一篇讲了数组长什么样、下标怎么算。这一篇只回答一个问题:什么时候值得开一排盒子。 教材在这里打的比方是储物柜:一排格子,编号清楚,想开第 7 格不用从头开。 这也是全书第一次让「数据条数」进入代码——它决定了后面所有循环怎么写。

一、不用数组的日子:五个变量,五行代码

五个学生,五个分数。不开数组的话只能这样写:s1 = 88、s2 = 92,一直到 s5 = 97。 求平均写成 (s1 + s2 + s3 + s4 + s5) / 5。看着也还行,五个加号而已。 问题出在第六个学生转来的时候:得再加一行 s6 = 78,再把求平均那一行改成六个加号、除数改成 6。

演示里的表把这件事量出来了:5 条数据要写 7 行,加一条要改 2 处。 20 条要写 22 行,100 条要写 102 行——行数跟着数据条数一起长。

二、改两处听着不多,可怕的是它会繁殖

两处是「加一行声明」和「改求平均那一行」。只求平均的话,确实不算负担。 但统计口径从来不止一个。求总分要改一处,求最高分要改一处,求方差、求及格人数、求排名,各要改一处。 每加一个统计口径,就多一处要跟着数据条数一起改。五条数据时要改十处,一百条数据时要改两百处。

更致命的是漏改。漏一处不报错,只是算出来的数不对——这种错最难查,因为它看起来跑得好好的。 所以不用数组的代价,不在写的时候,在改的时候。

三、换成数组,代码里就没有「几条」这个数了

scores = [88, 92, 75, 60, 97],一行装下五条。 求平均 sum(scores) / len(scores) 得 82.40,求最大 max(scores) 得 97。 注意这里没有出现 5,也没有出现 97——它问的是数组本身。 转来第六个学生,只要 scores.append(78)。平均变成 81.67,一个字都没改。

这就是数组真正的价值:它把「数据有几条」从代码里搬进了数据里。代码只描述「怎么处理每一条」, 不描述「一共有几条」。 这句话换个说法就是: 循环的上界由 len() 给,不由你手写。后面第 4 章那些「用数组处理大量数据」的写法, 根子都在这里。

四、判据只有一条:要不要一起处理

教材给的标准很短:需要「一起处理」的时候用数组。 「一起处理」翻成人话就是:我要对每一条都做同一件事。那件事只能写在循环里, 而循环只能按下标取——所以必须先有下标。

情况 用什么 为什么
一个数、一个名字、一句话 普通变量 就一个,没必要开一排
同一类数据,两条以上一起处理 数组 要循环就必须能按下标取
几条数据各有各的用途 分别起名 硬塞进数组反而看不出谁是谁

五、数组也不是万能的

第三种情况值得多说一句。 假设你要存一个人的姓名、年龄、身高,硬塞成 [u'张三', 28, 175.5],看着挺紧凑。 但读代码的人看到 p[1] 根本不知道是年龄还是身高, 而且这个数组里的三个格子类型都不一样——数组本来是为「同一类数据」准备的。 这种场合该用「结构体」或者字典,让每个值都有名字。 第 3 章会讲到为什么「同类型」这件事对数组这么重要。

所以判据要连起来读:同一类 数据、要 一起处理——两个条件都满足,才换数组。

六、动手 Demo:不用数组要多写几行,用数组一行搞定

先摆出不用数组的写法,数一数五个、二十个、一百个数据各要写多少行。 再把同一件事用数组写一遍,看「加一条数据」时两者差在哪。 最后给一张三种情况的判据表。

# 五条数据,不用数组
s1, s2, s3, s4, s5 = 88, 92, 75, 60, 97
print((s1 + s2 + s3 + s4 + s5) / 5)

# 同样五条,用数组
scores = [88, 92, 75, 60, 97]
print(sum(scores) / len(scores), max(scores))
scores.append(78)
print(len(scores), sum(scores) / len(scores))

实跑输出:

--------------------------------------------------------------------
什么时候该用数组
--------------------------------------------------------------------


  ① 先看看不用数组有多笨
  --------------------------------------------------------------------
  五个学生要存分数,只能这样:
    s1 = 88   s2 = 92   s3 = 75   s4 = 60   s5 = 97
  求平均:avg = (s1 + s2 + s3 + s4 + s5) / 5
  来了第六个学生:要加 s6,还得改求平均那一行

| 数据条数 | 不用数组要写几行 | 加一条要改几处 |
|---:|---:|---:|
| 5 | 7 | 2 |
| 20 | 22 | 2 |
| 100 | 102 | 2 |

  改两处听着不多,但这两处会随着「求最大」「求最小」「求方差」一起长。
  每加一个统计口径,就多一处要跟着改。

  ② 换成数组,全是一行
  --------------------------------------------------------------------
  scores = [88, 92, 75, 60, 97]
  平均:sum(scores) / len(scores) = 82.40
  最大:max(scores) = 97
  追加一个:scores.append(78)
  现在 6 个,平均 81.67
  数据从 5 条变成 6 条,代码一个字没改。
  这就是数组的价值——它把「数据有几条」变成变量,而不是写死在代码里。

  ③ 判断标准就一条
  --------------------------------------------------------------------

| 情况 | 用什么 | 为什么 |
|---|---|---|
| 一个数、一个名字、一句话 | 普通变量 | 就一个,没必要开一排 |
| 同一类数据,两条以上一起处理 | 数组 | 要循环就必须能按下标取 |
| 几条数据各有各的用途 | 分别起名 | 硬塞进数组反而看不出谁是谁 |

  教材的判据更短:要「一起处理」的时候用数组。
  一起处理的意思就是「我要对每一件都做同一件事」——那件事只能写在循环里,循环只能按下标取。

①第一段是「不用数组」的样子:变量名自带序号,加法要一个个列出来。 ②第二段的 sum(scores) / len(scores) 里没有出现 5, 也没有出现任何具体分数——它问的是数组本身有几条、加起来多少。 ③append 之后平均从 82.40 变成 81.67,代码没动一行。这就是「数据条数不写死在代码里」。 ④表里那个「加一条要改 2 处」是手数出来的,不是跑出来的:一处加声明,一处改求平均那一行。

30 秒实验

  1. 把 scores 换成 100 条随机分数([random.randint(0, 100) for _ in range(100)]), 看求平均那行要不要改。
  2. 用循环求最高分(不许用 max),看循环的上界怎么写才不会写死。
  3. 想一个你手上「几条数据各有各用途」的例子,试试硬塞数组会乱成什么样。

常见误区

  1. 「数据多到几十条才该用数组。」判据不是条数,是有没有「一起处理」这回事。三条要循环也该用。
  2. 「用了数组,代码就短了。」短是副作用。真正的收益是加数据时不用改代码。
  3. 「一堆相关的东西都能塞进一个数组。」类型不同的值塞进去,每个下标代表什么就说不清了。
自测三问(点开对答案) 1. 教材给的那条判据,原话是什么? —— 需要「一起处理」的时候用数组。翻成人话:要对每一条做同一件事。 2. 为什么不用数组时,「加一条数据」的成本会越滚越大? —— 每加一个统计口径(平均、总分、最大、方差)就多一处要跟着改,漏改不报错只算错。 3. 什么情况下不该用数组? —— 几条数据各有各的用途、类型还不一样时。该用结构体或字典,让每个值有名字。

数组能装一行数,那一张表格怎么装? 下一节把数组摞起来变成二维,顺手把字符串也拉进「一排格子」这个框里。

相关笔记