变量是贴了名字的盒子,代入就是往盒子里放东西。 数字和字符串抄的是值,列表和字典抄的是地址——这是初学最容易栽的一跤。 没有变量就没法描述算法:算法要「记住中间结果」,只有盒子能记。
上一篇讲数据长什么样,这一篇讲数据放在哪。 教材把变量比作盒子,这个比方够用到第 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 秒实验
- 把
y = x改成y = list(x),重跑,看 x 还变不变。 - 试一下
a = [1,2]; b = a; b = [9],看 a 变不变,想清楚为什么。 - 在第 4 章那个累加器里找出「哪个是盒子、它被改了几次」。
常见误区
- 「变量就是数学里的未知数。」数学里 x 一旦定了就不变,代码里它天天被覆盖。
- 「
b = a之后 b 就跟着 a 变。」数字是抄值。只有列表、字典这类才是抄地址。 - 「复制一份写
y = x就行。」对列表来说那只是多了一个名字,要list(x)或x[:]。
自测三问(点开对答案)
1. 变量到底是什么? —— 贴了名字的容器。赋值就是把值放进去;再赋一次就是换内容(更准确地说是换指向)。 2. 为什么改 b 不影响 a,但改 y 会影响 x? —— 数字抄的是值,列表抄的是地址。地址相同就是同一份数据。 3. 想让列表有两份独立的数据,怎么写? —— 显式复制:`y = list(x)` 或 `y = x[:]`。光写 `y = x` 只是起了第二个名字。盒子有了,标签怎么起?
下一节讲命名,顺带讲清 x = x + 1 这句在数学里无解、在代码里天天出现的话。
- ☐ (本篇完,下一篇 2.3 变量命名与赋值语句)