Lisp 入門/第二章 表、CAR、CDR

维基教科书,自由的教学读本

第二章 表、CAR、CDR[编辑]

表和原子[编辑]

Lisp的全名叫“表處理語言”,LISt Procesor 。可見表在Lisp中的重要性。

簡單說來,用小括號括起來的表达式式就叫表。

比如:

(+ 1 2)

就是一個表。

而表里面的东西,就是原子。比如上面的这个表里,+ 是原子,1 是原子,2 也是原子。

原子就是不包含空格的符号,可以是字符,也可以是数字。比如,下面的这个表里,有两个原子。

'(hello world)

这两个原子分别是 hello 和 world。

其实,上面的那个式子,也是 LISP 语言中的 hello world 程序。很简单,对吧。不过别忘记括号前面的单引号,否则会报错。

表里不仅可以包含原子,也可以包含另一个表。举个例子:

(or (> 3 4) (> 4 2))

在上表中,or是原子,而 (> 3 4) 、(> 4 2) 都是表,不是原子。当然了,这两个小表里,包含的东西都是元素。

也就是说:表是可以嵌套的。

表的大小並沒有限制,最小的表就是空表:

()

程序和数据[编辑]

编程这项工作,是在写一个程序,而写程序的目的是为了处理数据。程序与数据是编程中的两大要素,而在 Lisp 中,这两者都用 来表示。

如果你在解譯器中輸入一個表,那麼Lisp會對這個表求值。所以在解释器中输入

(+ 1 2)

会打印3。

说明这个表的值是3。

我们再看一个例子:

(1 2 3)

如果你在解释器中输入上面的表达式,会得到一个错误:Error: 1 is invalid as a function. 意思是1不是一個可用函數。

在所有的表中,第一個原子总是函數,代表操作、指令、命令。而之後原子(或表)是參數,意即對操作的說明。所以 (+ 1 2) 表示 1 + 2,而 (- 4 3) 表示 4 -3。

例外是這個

()

这是一个空表,会返回

NIL

NIL是一个原子,表示逻辑假,但它同时也是空表。它是LISP语言中唯一一个既是表又是原子的东西。

程序是表,那數據該如何表示? 数据也是用表來表示。

Lisp會對所有的表求值,但如果我们想使用表本身(作为数据),這反而会成为一件麻煩事。只要用一個简单的操作符就可以防止求值,那就是 ' 操作符(單引號)。

'(+ 1 2)

這次解譯器不對这个表其求值了,結果不再是3這個原子,而是一個表,原封未動的表。

(+ 1 2)

还记得我们的 hello world 程序吗?

'(Hello world!)

会返回

(HELLO WORLD!)

各种程序语言的入门书籍都喜欢一个hello, world程序。在此鄭重告訴大家, Lisp 也有自己的 Hello,world!

上面我们输入的是小写的字母,输出的却是大写的字母。这是因为在 Lisp 中大小寫無所謂。

不過 ' 這個東西其实只是一个语法糖(为了让程序员少打几个字符而创造的语法)。它其实是一種簡寫,全称是quote操作,意思是引用。上面的hello world 程序如果写全了,就是:

(quote (Hello world!))

CAR 操作符[编辑]

CAR操作符的作用是取出表的第一個元素。

(car '(1 2 3 4 5))

上表会返回 1。

CAR操作符的作用是取出表的第一個元素,注意,我說的是元素不是原子,所以car的返回值也可能是个表。

让我们来試試

(car '((1 2) 3))

返回的值就是一个表。CAR 的作用就是取出第一个元素,至于第一个元素是表还是原子,它并不关心。

不过,CAR 这个名称真的是很古老了,我们可以用 first 操作符来替代它。實際上,first 是 CAR 的別名。

注意到,我们上面的代码在参数表是由一个单引号(引用操作)引导的。这是非常重要的。如果我们去掉单引号。

(car (1 2 3 4 5))

系统将会报错: error: 1不是可用的函數

如果大家还记得我们之前的报错,就会知道,这是因为系统试图执行内表中的代码。是的,在 LISP 中,表就是程序。 如果没有特殊说明系统会对一切看得到的表求值。所以,我们要加上单引号,表示想要以数据的形式使用这个表。

还有一个事情是值得注意的,CAR 操作只对表管用,不可以把它用在原子上。比如:

(car 1)

系统会返回错误: error: 1不是“表”類型

CDR 操作符[编辑]

CDR 操作符和 CAR 的作用是相反的,它的作用是去除表的第一個元素。

(cdr '(1 2 3 4 5))

上面表达式的返回值是

(2 3 4 5)

CDR 总是返回一个表。它的意義,就是取出表中除了第一個元素之外的所有元素,然后返回这些元素組成的表。

CDR 的別名是 rest,这也是非常形象的名字,意思是其余,即除了第一个以外的其余。

我們可以用CDR操作符取出函數的參數,比如

(cdr '(+ 1 2 3))

这行代码可以取出(+ 1 2 3)的参数(1 2 3)。

如果表中只有一个元素,那么对这个表进行 CDR 操作,将会发生什么事情呢?让我们来试验一下:

(cdr '(1))

将会返回 NIL。不要忘记,NIL 也代表一个空表。所以,上面的表达式返回的其实是一个空表。

通过了解 CAR 和 CDR操作符,你会有种感觉,第一个元素竟然和其他所有元素平等。是的,这确实是 LISP 的世界观,至于为什么,你读下去就会知道。

CXR 組合操作符[编辑]

問你一個問題,如何取出第二個元素?

先不要急着往下看,好好想想。

我们至今只接触了两个运算符,一个是CAR,可以取出第一个,另一个是CDR,可以取出其余的。那么如何用这两个运算符取出第二个元素呢?

答案在下面:

(car (cdr '(1 2 3)))

第二个元素就是去掉第一个元素之后的第一个元素。我们先用CDR取出除第一个元素外的其他元素(本质上就是去掉了第一个元素),然后就可以在这个新表中取出第一个元素了,这样,我们得到的就是原表的第二个元素。

哇,聪明如你,是不是悟到了一种方法,可以取任何第幾個元素。

那思考一下如何取第三個元素吧,寫出來,在解譯器上試驗一下吧

恭喜你,你的第一個原創Lisp代碼實現了。如果还没实现,那么恭喜你,下面是代码:

(car (cdr (cdr '(1 2 3))))

实际上,LISP 中由专门的操作符来表示这些操作,比如 CADR

试试下面的代码:

(cadr '(1 2 3))

你也可以自己寻找更多类似的操作符。