car 与 cdr ¶一个列表的 CAR,简单来说就是列表中的第一个元素。因此列表 (rose violet daisy buttercup) 的 CAR 是 rose。
如果你正在 GNU Emacs 的 Info 中阅读本文,可以通过执行下面的表达式看到结果:
(car '(rose violet daisy buttercup))
执行该表达式后,rose 会出现在回显区。
car 并不会从列表中移除第一个元素,它只是返回该元素的值。对一个列表使用 car 之后,列表本身保持不变。用行话来说,car 是“非破坏性(non-destructive)”的。这一特性非常重要。
一个列表的 CDR 是列表除第一个元素外的剩余部分,也就是说,cdr 函数返回列表中紧跟在第一个元素之后的部分。因此,列表 '(rose violet daisy buttercup) 的 CAR 是 rose,而由 cdr 函数返回的列表剩余部分则是 (violet daisy buttercup)。
你可以照常执行下面的表达式来验证:
(cdr '(rose violet daisy buttercup))
执行后,回显区会显示 (violet daisy buttercup)。
和 car 一样,cdr 不会从列表中移除任何元素——它只返回第二个及后续元素组成的列表。
顺便一提,本例中的花卉列表被加了引用。如果不加引用,Lisp 解释器会尝试把 rose 当作函数调用,去执行整个列表,而这并不是我们想要的效果。
对于列表操作,first(第一个)和 rest(剩余部分)这两个名称比 car 和 cdr 更易懂。事实上,有些程序员会将 first 和 rest 定义为 car 和 cdr 的别名,然后在代码中使用 first 和 rest。
不过,Lisp 中的列表是通过一种更低层的结构 “序对节点(cons cells)”(see 列表的实现方式)构建的,在这种结构里并没有“第一个”或“剩余部分”的概念,CAR 与 CDR 是对称的。Lisp 不会隐藏序对节点的存在,程序也会将它们用于列表之外的用途。因此,这两个名称有助于提醒程序员:car 和 cdr 本质上是对称的,只是在列表用法上表现得不对称。
当把 car 和 cdr 应用到由符号组成的列表(例如 (pine fir oak maple))时,car 返回的列表元素是不带任何括号的符号 pine,它是列表的第一个元素。而该列表的 CDR 本身仍是一个列表 (fir oak maple),你可以照常执行下面的表达式观察结果:
(car '(pine fir oak maple)) (cdr '(pine fir oak maple))
而在由列表组成的列表中,第一个元素本身就是一个列表。car 会以列表形式返回这个第一个元素。例如,下面的列表包含三个子列表,分别是食肉动物、食草动物和海洋哺乳动物:
(car '((lion tiger cheetah)
(gazelle antelope zebra)
(whale dolphin seal)))
在本例中,列表的第一个元素(即 CAR)是食肉动物列表 (lion tiger cheetah),而列表剩余部分是 ((gazelle antelope zebra) (whale dolphin seal))。
(cdr '((lion tiger cheetah)
(gazelle antelope zebra)
(whale dolphin seal)))
值得再次强调:car 和 cdr 都是非破坏性的 — 也就是说,它们不会修改所操作的列表。这对它们的使用方式至关重要。
另外,在第一章关于原子的讨论中提到过:在 Lisp 里,某些类型的原子(例如数组)可以被拆分成部分,但拆分机制与拆分列表不同。对 Lisp 而言,列表中的原子是不可拆分的。(See Lisp 原子。)car 和 cdr 用于拆分列表,被视为 Lisp 的基础操作。由于它们无法拆分或访问数组的内部组成,数组被看作原子。反过来,另一个基础函数 cons 可以拼接或构造列表,但不能构造数组。(数组由专门的数组函数处理。See Arrays in The GNU Emacs Lisp Reference Manual。)