变量、类型与表达式
复习
- 名字、标签与地址:用名字和标签引用数据与跳转目标,不再手工数地址
- 汇编器:通过两遍扫描,把符号汇编成机器码
- 高级语言:再加一层:从“控制机器”转向“描述问题”
TL;DR
- 变量就是“给一块存储起的名字”
- 类型决定这块存储占多大、比特怎么解释、能参与哪些运算
- 表达式像一棵运算树,先算优先级高的部分,再把结果往上合
- 翻译表达式时,中间结果要用寄存器或临时位置暂存
正文
高级语言里最常见的三样东西,是变量、类型和表达式。我们一个一个看它们到底“是什么”。
变量:给存储起个名字
写 score = 95 时,我们其实在说两件事:
- 找一块存储,把 95 放进去
- 以后就用
score这个名字来指代它
所以, 变量 = 名字 + 一块存储 。这和我们前面给数据贴标签是一回事,只不过这一回是高级语言替我们管理。
名字是给人看的。等程序真正运行,score 会变成某个地址或某个寄存器——这件事在高级语言里被藏了起来,但我们知道它一定发生过。
类型:这块存储的说明书
光有名字还不够。一个叫 age 的变量,装的是整数还是字符?占一个字节还是四个字节?计算机得知道。
这就轮到 类型 (type)出场。类型像一张说明书,至少回答三个问题:
- 占多大:1 个字节?4 个字节?
- 怎么解释:这串比特是整数,还是字符?(还记得前面说过,同一串比特,换个约定就换个意思)
- 能做哪些运算:整数能加能减,字符通常不能直接做算术
比如整数类型,可能占 4 个字节、按补码解释、支持加减乘除;字符类型,可能占 1 个字节、按编码解释(ASCII 或 UTF-8)、支持比较和拼接。类型错了,程序要么算出怪结果,要么干脆报错——这也是替我们挡掉错误的一层保护。
表达式:一棵运算树
表达式看起来只是“一串算式”,但它骨子里是一棵树。看 a + b * c:
+
/ \
a *
/ \
b c
乘法在下面,要先算;算完再把结果和 a 相加。这就是“先乘除、后加减”的由来—— 优先级决定了这棵树谁在底层、谁在上层。
括号的作用,则是强行改变树的样子:(a + b) * c 会让加法先算。
表达式怎么落到机器上
树是要一步一步算的。计算机一次只做一件小事,所以得把树“压平”成若干简单步骤。拿 total = a + b * c 来说:
t = b * c ; 先算下面那棵子树
total = a + t ; 再把顶层加起来
翻译成汇编,大概是这样:
LOAD A, [b]
LOAD B, [c]
MUL R, A, B ; 假想的乘法指令,可由加法器搭出
STORE [t], R ; 中间结果先存起来
LOAD A, [a]
LOAD B, [t]
ADD R, A, B
STORE [total], R
这里的 t 是一个 临时变量 ,专门用来暂存中间结果。你可能会问:为什么中间结果不一直留在寄存器里?因为寄存器数量有限,表达式一长、嵌套一深,就得把暂时不用的结果先放回存储器。翻译程序要做的,正是安排“哪一步用哪个寄存器、哪一步该存回内存”。
表达式、类型、变量,就这样一层层落成了指令。接下来,我们看高级语言里那两样最像“控制”的东西——判断和循环——是怎么落到跳转指令上的。
思考题
a + b * c和(a + b) * c结果通常不同。它们的运算树有什么差别?如果只能按“从左到右”一条线计算,能算对吗?
小结
知识点
- 变量:名字加一块存储
- 类型:占用大小、比特解释、可用运算
- 表达式是一棵按优先级组织的运算树
- 表达式落到机器时,中间结果要用寄存器或临时变量暂存
参考资料
- Wikipedia(zh):变量:程序中的变量
- Wikipedia(zh):数据类型:类型系统的基本概念
- Wikipedia(zh):逆波兰表示法:表达式如何被拆成顺序步骤
思考题答案(仅供参考)
a + b * c 的树里,乘法在底层、先算;(a + b) * c 的树里,加法被括号拉到下层、先算。两棵树的形状不同,计算结果通常也不同。如果只按“从左到右”一条线计算,a + b * c 会被算成 (a + b) * c,结果就错了——所以机器必须先按优先级(或括号)把表达式组织成树,再决定计算顺序。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪