代码的编译是计算机科学的一大命题,其博大精深,难以尽数。这里
,我们捡着一个小命题娱乐一下。
程序代码中,总是少不了数学运算,其实对于我们来说很熟悉的数学计 算,在计算机里也是要做一些编译处理的。
例如,4+9*3+7-2这样一个简单的四则运算 ,对于人来说就是:
4+9*3+7-2
=4+27+7-2
=31+7-2
=38-2
=36
这里面,我们实际上已经在下意识里做了很多思考。首先 ,大脑会按照运算符划分开各个子算式,然后找出运算优先级的子式
,按顺序计算完后,将结果填充给下一级,依次递归
,直至整个算式完成。
计算机的编译/解释过程,其实跟这个很像,也是先找出最高优先级的 子式,然后依次递归构造出一个语法解析树,调用数学运算指令计算每
个节点,返回结果。对于每个单步运算,计算机里面调用一次+、-
、*、/的时候,其实是一次函数操作,每个运算符执行对应的函数
,比如1+1,其实就是+(1, 1)。在编译时,通常会把这样的运算翻译为后缀表达式,+(1, 1)就变成了1 1 -,这样的好处是计算机逐次读入每一个词,遇到操作数就压栈
,遇到操作符就把所需个数的操作数从栈里弹出来计算
,然后再把结果压进去,这个过程以轻松匹配复杂表达式。不过—
—这么看起来是不很累?特别是复杂算式,就看不清层次了
。我们把它用括号包起来,就成了(1 1 -),这样清晰一些了吧,我们现在按这种方式把开头的那个式子写成
:
(((4 (9 3 *)+) 7+) 2 -)
现在,我们用一个[]表示堆栈,左边是栈顶,右边是栈底 。现在我们模拟计算机的解释过程。
(((4 (9 3 *)+) 7+) 2 -)
=>[4] 4入栈
=>[9 4] 9入栈
=>[3 9 4] 3入栈
=>* (9 3) [4] 读到*,弹出最上面两个数3和9——需要注意,因为堆栈的后入先出 特性,实际上栈顶的元素反而在参数表的右边
=>[27 4] 把相乘以后的结果27重新压入栈
=>+ (4 27) [] 读到+,弹出最上面两个数27和4
=>[31] 把相加结果31入栈
=>[7 31] 7入栈
=>+ (31 7)[] 读到+,弹出最上面两个数7和31
=>[38] 把相加以后的结果38入栈
=>[2 38] 2入栈
=>- (38 2)[] 读到-,弹出最上面两个数2和38
=>[36] 把相减结果36入栈
=>36 检测到运算过程已经完成,把结果从堆栈中弹出返回
以上这个过程对计算机是很方便,但是对于我们读起来还是有点别扭 ,把运算符放前面不是更好懂么?编译原理中,前缀表达式也是一种常
见的写法,于是:
(((4 (9 3 *)+) 7+) 2 -)=>(-(+(+ 4 (* 9 3)) 7) 2)
这个么,应该有朋友已经发现了,这不就是一段LISP代码么?!
我有很长时间不能很好的理解LISP代码,直到有位朋友说 ,LISP就是语法解析树的前缀表达……
以此文向他致敬!
程序代码中,总是少不了数学运算,其实对于我们来说很熟悉的数学计
例如,4+9*3+7-2这样一个简单的四则运算
4+9*3+7-2
=4+27+7-2
=31+7-2
=38-2
=36
这里面,我们实际上已经在下意识里做了很多思考。首先
计算机的编译/解释过程,其实跟这个很像,也是先找出最高优先级的
(((4 (9 3 *)+) 7+) 2 -)
现在,我们用一个[]表示堆栈,左边是栈顶,右边是栈底
(((4 (9 3 *)+) 7+) 2 -)
=>[4]
=>[9 4] 9入栈
=>[3 9 4] 3入栈
=>* (9 3) [4] 读到*,弹出最上面两个数3和9——需要注意,因为堆栈的后入先出
=>[27 4] 把相乘以后的结果27重新压入栈
=>+ (4 27) [] 读到+,弹出最上面两个数27和4
=>[31] 把相加结果31入栈
=>[7 31] 7入栈
=>+ (31 7)[] 读到+,弹出最上面两个数7和31
=>[38] 把相加以后的结果38入栈
=>[2 38] 2入栈
=>- (38 2)[] 读到-,弹出最上面两个数2和38
=>[36] 把相减结果36入栈
=>36 检测到运算过程已经完成,把结果从堆栈中弹出返回
以上这个过程对计算机是很方便,但是对于我们读起来还是有点别扭
(((4 (9 3 *)+) 7+) 2 -)=>(-(+(+ 4 (* 9 3)) 7) 2)
这个么,应该有朋友已经发现了,这不就是一段LISP代码么?!
我有很长时间不能很好的理解LISP代码,直到有位朋友说
以此文向他致敬!