5.4 构建 cons 单元与列表

列表是 Lisp 语言的核心,因此有许多函数用于构建列表。cons 是最基础的列表构建函数;但值得注意的是,在 Emacs 源代码中,list 的使用次数比 cons 更多。

Function: cons object1 object2

该函数是构建新列表结构最基础的函数。它会创建一个新的 cons 单元,将 object1 设为 CARobject2 设为 CDR,然后返回这个新的 cons 单元。参数 object1object2 可以是任意 Lisp 对象,但 object2 通常是一个列表。

(cons 1 '(2))
     ⇒ (1 2)
(cons 1 '())
     ⇒ (1)
(cons 1 2)
     ⇒ (1 . 2)

cons 常用来向列表头部添加单个元素。这一操作被称为 consing the element onto the list将元素 cons 到列表上6。 例如:

(setq list (cons newelt list))

注意:本例中名为 list 的变量与下文描述的 list 函数之间无冲突 —— 任何符号都可同时作为变量和函数名使用。

Function: list &rest objects

该函数创建一个以 objects 为元素的列表,生成的列表始终以 nil 结尾。若未传入任何 objects,则返回空列表。

(list 1 2 3 4 5)
     ⇒ (1 2 3 4 5)
(list 1 2 '(3 4 5) 'foo)
     ⇒ (1 2 (3 4 5) foo)
(list)
     ⇒ nil
Function: make-list length object

该函数创建一个长度为 length 的列表,其中每个元素都是 object。可将 make-listmake-string 对比(see 创建字符串)。

(make-list 3 'pigs)
     ⇒ (pigs pigs pigs)
(make-list 0 'pigs)
     ⇒ nil
(setq l (make-list 3 '(a b)))
     ⇒ ((a b) (a b) (a b))
(eq (car l) (cadr l))
     ⇒ t
Function: append &rest sequences

该函数返回一个包含所有 sequences 元素的列表。sequences 可以是列表、向量、布尔向量或字符串,但最后一个参数通常应为列表。除最后一个参数外,其余所有参数都会被复制,因此不会修改任何传入的参数。(若需无复制地拼接列表,可参见 “重排列表的函数” 中的 nconc。)

更普通地,append 的最后一个参数可以是任意 Lisp 对象。该参数不会被复制或转换,而是成为新列表最后一个 cons 单元的 CDR。若最后一个参数本身是列表,则其元素会成为结果列表的元素;若最后一个参数不是列表,结果会是一个点对列表(因为其最终 CDR 不符合规范列表要求的 nil,see 列表与 Cons 单元)。

以下是 append 的使用示例:

(setq trees '(pine oak))
     ⇒ (pine oak)
(setq more-trees (append '(maple birch) trees))
     ⇒ (maple birch pine oak)

trees
     ⇒ (pine oak)
more-trees
     ⇒ (maple birch pine oak)
(eq trees (cdr (cdr more-trees)))
     ⇒ t

可通过框图理解 append 的工作原理。变量 trees 指向列表 (pine oak),变量 more-trees 指向列表 (maple birch pine oak)。但 trees 仍指向原始列表:

more-trees                trees
|                           |
|     --- ---      --- ---   -> --- ---      --- ---
 --> |   |   |--> |   |   |--> |   |   |--> |   |   |--> nil
      --- ---      --- ---      --- ---      --- ---
       |            |            |            |
       |            |            |            |
        --> maple    -->birch     --> pine     --> oak

空序列不会对 append 的返回值产生任何影响。因此,若最后一个参数为 nil,会强制复制前一个参数:

trees
     ⇒ (pine oak)
(setq wood (append trees nil))
     ⇒ (pine oak)
wood
     ⇒ (pine oak)
(eq wood trees)
     ⇒ nil

copy-sequence 函数被发明前,这曾是复制列表的常用方式。See 序列、数组与向量

以下示例展示向量和字符串作为 append 参数的用法:

(append [a b] "cd" nil)
     ⇒ (a b 99 100)

将字符串转换为字符列表的方法:

(append "abcd" nil)
     ⇒ (97 98 99 100)

string-to-list 函数是上述操作的便捷简写。

借助 apply(see 调用函数),我们可以将一个 “列表的列表” 中的所有列表 append拼接 起来:

(apply 'append '((a b c) nil (x y z) nil))
     ⇒ (a b c x y z)

若未传入 sequences,返回 nil

(append)
     ⇒ nil

以下示例展示最后一个参数非列表的情况:

(append '(x y) 'z)
     ⇒ (x y . z)
(append '(x y) [z])
     ⇒ (x y . [z])

第二个示例表明:若最后一个参数是序列但非列表,其元素不会成为结果列表的元素。而是像其他非列表参数一样,直接作为最终 CDR

例外情况:若除最后一个参数外其余均为 nil,且最后一个参数非列表,则返回值为该最后一个参数本身(即此时返回值不是列表):

(append nil nil "abcd")
     ⇒ "abcd"
Function: copy-tree tree &optional vectors-and-records

该函数返回 tree 的副本。若 tree 是 cons 单元,则创建一个新的 cons 单元(CARCDR 与原单元相同),再递归地以同样方式复制其 CARCDR

默认情况下,若 tree 不是 cons 单元,copy-tree 直接返回 tree;但若 vectors-and-records 为非-nil,则同时复制向量和记录(并递归处理其元素)。注意 tree 参数不能包含循环引用。

Function: flatten-tree tree

该函数返回 tree 的 “flattened扁平化” 副本,即一个包含以 tree 为根的 cons 单元树中所有非-nil 终端节点(叶子节点)的列表,且叶子节点顺序与原树 tree 中一致。

(flatten-tree '(1 (2 . 3) nil (4 5 (6)) 7))
    ⇒(1 2 3 4 5 6 7)
Function: ensure-list object

该函数将 object 转换为列表返回。若 object 已是列表,则直接返回;否则返回一个包含 object 的单元素列表。

此函数常用于处理 “可能是列表、也可能不是列表” 的变量,例如:

(dolist (elem (ensure-list foo))
  (princ elem))
Function: number-sequence from &optional to separatio

该函数返回一个数字列表,起始值为 from,步长为 separation,终止于 toto 之前。separation 可正可负,默认值为 1。 若 tonil 或与 from 数值相等,返回单元素列表 (from); 若 separation 为正但 to 小于 from,或 separation 为负但 to 大于 from,返回 nil(因参数指定了空序列);

separation 为 0,且 to 非-nil 且与 from 数值不等,number-sequence 则触发错误(因参数指定了无限序列)。

所有参数均为数字。浮点参数需注意:浮点运算存在精度问题。例如,不同机器上 (number-sequence 0.4 0.6 0.2) 可能返回单元素列表 (0.4),而 (number-sequence 0.4 0.8 0.2) 可能返回三个元素的列表。列表的第 n 个元素通过精确公式 (+ from (* n separation)) 计算。因此若需确保 to 被包含在列表中,可将符合该精确公式的表达式作为 to 的值传入;或者,也可将 to 替换为一个略大的值(若步长 separation 为负数,则替换为一个略小的负值)。

示例:

(number-sequence 4 9)
     ⇒ (4 5 6 7 8 9)
(number-sequence 9 4 -1)
     ⇒ (9 8 7 6 5 4)
(number-sequence 9 4 -2)
     ⇒ (9 7 5)
(number-sequence 8)
     ⇒ (8)
(number-sequence 8 5)
     ⇒ nil
(number-sequence 5 8 -1)
     ⇒ nil
(number-sequence 1.5 6 2)
     ⇒ (1.5 3.5 5.5)

Footnotes

(6)

没有严格等价的方法向列表尾部添加元素。可使用 (append listname (list newelt)),复制 listname 并在尾部添加 newelt,生成全新列表;或使用 (nconc listname (list newelt)),遍历 listname 的所有 CDR,替换末尾的 nil,直接修改 listname。对比之下,用 cons 向列表头部添加元素时,既不复制也不修改原列表。


emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© 2025 Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike