周银辉
SICP,即Structure and Interpretation of Computer Programs, 计算机程序的构造和解释,是MIT(麻省理工学院)一门经典课程,相信很多程序爱好者都读过,最近我也抽空读了读,挺有意思的。算是对大学知识(高数,线代,编译,算法....)的“温故而知新”吧。
这里是我在学习1.1.1~1.1.3节时做的一些笔记。
1,基本工具
教材:如果要看英文版的教材,请到这里 http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-4.html#%_toc_start , 中文版的《 计算机程序的构造和解释》没有找到在线版本,自己购买一本吧,45元还是很超值的。
语言:当然是 Lisp,在其众多方言中,我选择的是Scheme,关于Scheme的教程,王咏刚的R5RS试译稿不错,这里 下载
IDE : 推荐PLTScheme (或者叫DrScheme),很好用。
2,表达式
中缀表达式(Infix Expression )
比如
A * ( B + C ) / D
比较符合我们的思维习惯,但其一个很明显的缺点是,其需要额外的信息来比较操作符的优先级,如果需要打破这种优先级关系的话,我们这需要用到括号,对于计算机而言,计算一个中缀表达式比计算前缀或后缀表达式要来的麻烦, 况且一元运算符没有中缀形式。
后缀表达式(Postfix Expression)
也称逆波兰式,比如
A B C + * D /
,这个执行起来非常简单:从左到右依次读取操作数和操作符,当遇到操作符时,只需将该操作符左边(或者说先与该操作符读取的)N个操作数拿来就行该操作符对应的运算就是了,并将计算结果作为新的操作数。这里的N,取决于操作符是几元操作符。
前缀表达式(Prefix Expression)
也称波兰式,比如
/ * A + B C D
,
和逆波兰式比较一下,就可以发现,如果我们从右向左读取前缀表达式的话,那么计算方式就和后缀表达式差不多了,只不过得注意下不对称运算的顺序(满足交换律的运算我们称之为对称运算,反之则不对称运算),Scheme采用的是前缀方式而非后缀,给出的解释是说,其可以适用于带任意个实参的形式,比如(* A B C )则表示A*B*C,但与之相对的是,为了不产生歧义,则需要用括号来界定表达式的范围对于这三种形式的相互转换:
如果你是在草稿纸上手动转换的话,可以使用下面的简单方法:
比如A+B转成后缀,很简单A B +,观察一下就发现了,后缀就是把操作符调整到“两个操作数的后边”,相应的前缀形式就是 把操作符调整到“两个操作数的前边,所以 A*(B+C)/ D , 则中缀形式为 ((A * (B + C) ) / D) , 后缀形式为 ( (A (B C +) *) D /) 前缀形式为 (/ (* A (+ B C) ) D) , 其中的多余的括号是为了辅助我们将子表达式理解成操作数。
关于如何使用计算机程序构造ParseTree并实现表达式转换,我将在接下来的一篇随笔中详细解释。
3,特殊形式(Special Forms)
我们将“数”,“内部运算符”以及其中相同级别的名字称为”基本表达式“,比如 123,+ ,- 等,“数”作为基本表达式,其值是其所表示的数值,内部运算符的值是其完成该操作所对应的机器指令,其他名字的值则是环境中与该名字相对应的那个对象。
由“基本表达式”复合而成的称为“组合式”(或“复合表达式”),比如 (1 2 +)
而类似于(define a 100)这种看上去像组合式,但却不是组合式的,称为”特殊形式“,之所以特殊,是因为你不会按照普通表达式的求值规则进行求值,其有着自己的求值规则。其中一些特殊形式是程序设计语言的基本需要,而另外一些则是为了提高易读性,影响控制逻辑,实现抽象与模块化等等,更多的,可以参考这篇论文,而Scheme语法层面的可以参考这里。
注:这是一篇读书笔记,所以其中的内容仅属个人理解而不代表SICP的观点,并随着理解的深入其中的内容可能会被修改