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

变量是贴了名字的盒子,代入就是往盒子里放东西。 数字和字符串抄的是值,列表和字典抄的是地址——这是初学最容易栽的一跤。 没有变量就没法描述算法:算法要「记住中间结果」,只有盒子能记。

📝 本章和前面几章的关系

上一篇讲数据长什么样,这一篇讲数据放在哪。 教材把变量比作盒子,这个比方够用到第 3 章讲链表为止。 这一篇会补一句教材没细说的:盒子里的东西可能是值,也可能是「东西在哪儿」。这一句不补, 后面列表、链表全会绕。

一、盒子:名字挂在一个能换内容的容器上

score = 90 的意思是:找一个盒子,贴上 score 这个标签,把 90 放进去。 演示里打印了盒子的编号(id)。换一个值之后,编号变了。 所以更准确的说法是:盒子上的标签换指向了另一个东西。「同一个盒子换了内容」是方便的说法,够用, 但会在后面误导你。

什么时候必须知道这个区别?看下一节那个列表的例子。

二、代入是抄值,不是拴绳子

演示里 a = 1、b = a、b = 2,结果 a 还是 1。 改 b 不影响 a,因为 b 拿到的是 1 的一份拷贝,不是「跟 a 连着」。 这一条很重要:如果代入是「拴绳子」,那改一个变量会影响另一个,所有计算都没法推理了。

数字、字符串、真假值都是这样——抄一份,互不干扰。

三、但列表会拴在一起

演示里 x = [1,2,3]、y = x、y.append(4),结果 x 也变成 [1,2,3,4]。 为什么?因为 x 里装的是「这张表在哪儿」这个地址,y = x 抄的是地址。 两个名字指着同一张表,谁改都看得见。 这是初学最容易栽的一跤,而且它不报错。你以为是两份数据,实际是一份。

要两份怎么办?显式复制:y = list(x) 或者 y = x[:]。复制要写出来,不能靠赋值。 这条在后面的排序里会变成实打实的坑:排序会改动原数组,想让原数据不变,就得先复制一份。

四、为什么算法离不开变量

算法要「一边算一边记」:记住当前的最大值、记住累加到多少、记住走到第几个。 这些中间结果没有名字就记不住,没有盒子就放不下。 第 4 章那个累加器三件套——total = 0、循环、total += x——就是盒子最典型的用法:一个盒子, 被反复改写。

所以「变量是描述算法的必要条件」这句话,不是夸它,是事实: 没有可改写的存储,就没有「过程」这个概念。

五、动手 Demo:盒子、抄值、拴绳子:三个实验

先看赋值之后盒子编号怎么变。 再验证数字是抄值、列表是抄地址。 最后看一眼这个区别会给排序带来什么麻烦。

score = 90
print(id(score))
score = 75
print(id(score))       # 编号变了:标签换了指向

a = 1; b = a; b = 2
print(a, b)            # 1 2   抄的是值

x = [1, 2, 3]; y = x; y.append(4)
print(x, y)            # 都变成 [1,2,3,4]  抄的是地址

实跑输出:

--------------------------------------------------------------------
变量:贴了名字的盒子
--------------------------------------------------------------------


  ① 把值放进盒子
  --------------------------------------------------------------------
  score = 90
  盒子 score 里现在装着 90
  这个盒子在内存里的编号:140704800714440
  「变量」这个名字容易让人以为它会变,其实变的是盒子里的东西,盒子本身一直在那儿。

  ② 换一个值,盒子不变
  --------------------------------------------------------------------
  score = 75 之后
  盒子里的值:75
  装着的对象编号变了:140704800714440 -> 140704800713960
  从现象看是「同一个盒子换了内容」。更准确的说法是:盒子里的标签换指向了另一个对象。
  教材用「盒子」打比方,够用;写代码时知道它是标签,就不会被「两个变量指向同一块」绕晕。

  ③ 代入是复制值,不是把两个盒子拴在一起
  --------------------------------------------------------------------
  a = 1
  b = a
  b = 2
  结果:a = 1,b = 2
  改了 b,a 没变。所以 b = a 是「把 a 里的值抄一份放进 b」,不是「b 从此跟着 a 走」。

  ④ 反过来:列表就会拴在一起
  --------------------------------------------------------------------
  x = [1, 2, 3]
  y = x
  y.append(4)
  结果:x = [1, 2, 3, 4],y = [1, 2, 3, 4]
  这次 x 跟着变了。因为 x 里装的是「列表在哪儿」这个地址,y = x 抄的是地址,两个名字指着同一张表。
  ! 这是初学最容易栽的一跤:数字和字符串抄的是值,列表和字典抄的是地址。

①第二段那两个编号不一样了。这说明「同一个盒子换内容」只是表面现象。 ②第三段 a 还是 1,b 变成 2。数字之间互不影响。 ③第四段 x 跟着变了——两个名字指着同一张表。这个 Demo 里 x 和 y 打印出来一模一样,就是证据。 ④最后那句「要两份就显式复制」是这一篇最实用的一句:y = list(x) 或 y = x[:]。

30 秒实验

  1. 把 y = x 改成 y = list(x),重跑,看 x 还变不变。
  2. 试一下 a = [1,2]; b = a; b = [9],看 a 变不变,想清楚为什么。
  3. 在第 4 章那个累加器里找出「哪个是盒子、它被改了几次」。

常见误区

  1. 「变量就是数学里的未知数。」数学里 x 一旦定了就不变,代码里它天天被覆盖。
  2. 「b = a 之后 b 就跟着 a 变。」数字是抄值。只有列表、字典这类才是抄地址。
  3. 「复制一份写 y = x 就行。」对列表来说那只是多了一个名字,要 list(x) 或 x[:]。
自测三问(点开对答案) 1. 变量到底是什么? —— 贴了名字的容器。赋值就是把值放进去;再赋一次就是换内容(更准确地说是换指向)。 2. 为什么改 b 不影响 a,但改 y 会影响 x? —— 数字抄的是值,列表抄的是地址。地址相同就是同一份数据。 3. 想让列表有两份独立的数据,怎么写? —— 显式复制:`y = list(x)` 或 `y = x[:]`。光写 `y = x` 只是起了第二个名字。

盒子有了,标签怎么起? 下一节讲命名,顺带讲清 x = x + 1 这句在数学里无解、在代码里天天出现的话。

相关笔记