图 12.10 展示了如何用结构来实现双向链表。将 看成一种结构,它有两个字段:指向数据的 car 和指向下一个元素的 cdr 。要实现一个双向链表,我们需要第三个字段,用来指向前一个元素。图 12.10 中的 defstruct 定义了一个含有三个字段的对象 dl (用于“双向链接”),我们将用它来构造双向链表。dldata 字段对应一个 conscarnext 字段对应 cdrprev 字段就类似一个 cdr ,指向另外一个方向。(图 12.11 是一个含有三个元素的双向链表。) 空的双向链表为 nil ,就像空的列表一样。

    图 12.10: 构造双向链表

    为了便于操作,我们为双向链表定义了一些实现类似 carcdrconsp 功能的函数:dl-datadl-next 和 。 dl->listdl 的打印函数(print-function),其返回一个包含 dl 所有元素的普通列表。

    函数 dl-insert 就像针对双向链表的 cons 操作。至少,它就像 cons 一样,是一个基本构建函数。与 cons 不同的是,它实际上要修改作为第二个参数传递给它的双向链表。在这种情况下,这是自然而然的。我们 cons 内容到普通列表前面,不需要对普通列表的 rest (译者注: restcdr 的另一种表示方法,这里的 rest 是对通过 cons 构建后列表来说的,即修改之前的列表) 做任何修改。但是要在双向链表的前面插入元素,我们不得不修改列表的 rest (这里的 rest 即指没修改之前的双向链表) 的 prev 字段来指向这个新元素。

    几个普通列表可以共享同一个尾端。因为双向链表的尾端不得不指向它的前一个元素,所以不可能存在两个双向链表共享同一个尾端。如果 dl-insert 不具有破坏性,那么它不得不复制其第二个参数。

    函数 dl-list 是对于 dl 的类似 list 的功能。它接受任意多个参数,它会返回一个包含以这些参数作为元素的 dl

    它使用了 reduce 函数 (并设置其 from-end 参数为 trueinitial-valuenil),其功能等价于

    如果将 dl-list 定义中的 #'dl-insert 换成 #'cons,它就相当于 list 函数了。下面是 dl-list 的一些常见用法: