这是一个表示符号a 及其在(setq a '(1 2)) 之后的值的图表。方框是基本数据结构(符号和conses),箭头是指针(一个数据引用另一个数据的地方)。 (我正在简化一点。)
symbol cons cons
+-------+----------+ +------+------+ +------+------+
|name: |variable: | |car: |cdr: | |car: |cdr: |
| a | | | | 1 | | | | 2 | nil |
+-------+----|-----+ +------+--|---+ +------+------+
| ↑ | ↑
+-------------+ +-------+
表达式'(1 2) 构建右边的两个cons,组成一个二元素列表。表达式(setq a '(1 2)) 创建符号a,如果它不存在,则使其“变量槽”(包含符号值的部分)指向新创建的列表。 setq 是一个内置宏,(setq a '(1 2)) 是 (set 'a '(1 2)) 的简写。 set 的第一个参数是要修改的符号,第二个参数是要设置符号变量槽的值。
(add-to-list 'a 3) 在这里等价于(set 'a (cons 3 a)),因为 3 不在列表中。这个表达式做了四件事:
- 创建一个新的 cons 单元格。
- 将新 cons 单元的 car 字段设置为
3。
- 将新的 cons 单元格的 cdr 字段设置为
a 以前的(并且仍然是当前的)值(即复制 a 的变量槽的内容)。
- 将
a的变量槽设置为新的cons单元格。
调用之后,所涉及的数据结构如下所示:
symbol cons cons cons
+-------+----------+ +------+--|---+ +------+------+ +------+------+
|name: |variable: | |car: |cdr: | |car: |cdr: | |car: |cdr: |
| a | | | | 3 | | | | 1 | | | | 2 | nil |
+-------+----|-----+ +------+--|---+ +------+--|---+ +------+------+
| ↑ | ↑ | ↑
+-------------+ +-------+ +-------+
对setcar 的调用不会创建任何新的数据结构,并且不会作用于符号a,而是作用于其值,即car 当前包含3 的cons 单元格。在@987654341 之后@,数据结构如下所示:
symbol cons cons cons
+-------+----------+ +------+--|---+ +------+------+ +------+------+
|name: |variable: | |car: |cdr: | |car: |cdr: | |car: |cdr: |
| a | | | | 4 | | | | 1 | | | | 2 | nil |
+-------+----|-----+ +------+--|---+ +------+--|---+ +------+------+
| ↑ | ↑ | ↑
+-------------+ +-------+ +-------+
push 是一个宏;这里,(push 5 a) 等价于(set 'a (cons 5 a))。
setq 和push 是宏(setq 是一种“特殊形式”,就我们而言,这里指的是一个宏,其定义内置在解释器中,而 Lisp 中没有提供)。宏接收未评估的参数,并且可以选择是否扩展它们。 set、setcar 和 add-to-list 是函数,它们接收评估的参数。评估符号会返回其变量槽的内容,例如在初始 (setq a '(1 2)) 之后,符号 a 的值是其 car 包含 1 的 cons 单元格。
如果您仍然感到困惑,我建议您尝试使用(setq b a),并亲自查看当您对a 进行操作时哪些表达式会修改b(那些对符号a 起作用的表达式)以及哪些不要(对符号a 的值起作用的那些)。