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))

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