表达式解析趣谈

来源:互联网 发布:金数据登录注册 编辑:程序博客网 时间:2024/05/16 12:33
代码的编译是计算机科学的一大命题,其博大精深,难以尽数。这里,我们捡着一个小命题娱乐一下。
程序代码中,总是少不了数学运算,其实对于我们来说很熟悉的数学计算,在计算机里也是要做一些编译处理的。
例如,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就是语法解析树的前缀表达……
以此文向他致敬!
原创粉丝点击