Skip to content
On this page

第五课:列表

在 Lisp 中,最常用的数据结构就是列表。列表顾名思义就是一系列值的组合,类似于 Python 中的 list,C++ 中的 std::vector

构建一个列表的方法是使用 list 过程:

scheme
> (list 1 4 2 8)
'(1 4 2 8)

解释器返回的结果 '(1 4 2 8) 就代表一个列表——用一个单引号,外加一对括号;内部是空格分隔的值。

TIP

这种表示方法很像组合式——其实这不是巧合,我们之后再讲。

如何访问一个列表的元素呢?事实上 Lisp 没有直接提供访问元素的方法。Lisp 提供两个过程 car cdr,可以分别获取列表的首元素除去首元素外的其余部分构成的新列表

scheme
> (define x (list 1 4 2 8))
> (car x)
1
> (cdr x)
'(4 2 8)

如果列表只有一个元素,那么将 cdr 作用在其上会得到空表 '()。此外,(list) 的结果也是 '()。空表是一个特殊值,不能再被 carcdr 作用。

scheme
> (cdr (list 1))
'()
> (list)
'()

其实这一课关于语法的内容只有这么多了。(如果你着急的话,可以直接翻到 下一课。)但是感觉什么都没讲?来看看下面的代码:

scheme
; 从列表 l 获取第 i 个元素(0 起始)
(define (at l i)
        (if (equal? i 0)
            (car l)
            (at (cdr l) (- i 1))))

这里用到了一个没见过的内置过程 equal?,我稍微解释一下,就是比较两个操作元素是否相等。从而,整段程序可以解读为:

  • 定义过程 at:接受两个实参 li(其中 l 是列表,i 是整数,意思是取 l 的第 i 个元素)。过程是这样执行的:
    • 判断 i 是否等于零;
      • 如果是零的话,就返回 (car l):也就是 l 的首个元素;
      • 否则,就返回 (at (cdr l) (- i 1)) ——就是 (cdr l) 的第 i - 1 个元素。

有感觉了吗?这里用到的是对 at 的递归。由于 (cdr l) 会“删除” l 的首个元素,因此 l 的第 i 个元素就相当于 (cdr l) 的第 i - 1 个元素。这样递归做下去,就可以遇到 i 为零的时刻,随后用 car 作用一下就可以了。

scheme
> (define my-list (list 1 4 2 8))
> (at my-list 0)
1
> (at my-list 3)
8

通过这个小例子,我们对 Lisp 这种编程语言的运作方式有了一个感性的认识。比如最直观的,在 Lisp 中,通常使用递归来表示循环的逻辑。