终于开始讨论列表了,列表是Lisp的精华之一,也是学习Lisp的难点之一。
列表的精彩之处在于,它不仅仅是Lisp中的一种数据结构,它也是Lisp语言的构成部分,Lisp语言中的所有语句都是一个列表。
反观我们常见的编程语言,语言中的语句有特定的语法,而这些语法只有编译器可以理解,用编程语言自己去解释自己几乎就成了一个不可能完成的任务。
举个例子,看看下面的java语句:
这句代码给java编译器去解释是很简单的,i的值会等于j加10.
不过,如果给你下面名叫myString这个字符串:
你能否通过java代码解析myString,并准确理解这行代码的意义?
对于一般程序员来讲这个任务几乎是不可能完成的。当然,对于大牛来讲是可以的,不过是用java语言写一个java编译器而已。
而Lisp程序员就幸运多了,不用成为大牛也可以通过Lisp语言解析Lisp语句,因为Lisp语句本身就是一个列表,如下面的代码:
就像下面的代码:
其中(setf i (+ j 10))被轻松地编译执行了。
在这里编译期和运行期的界限被模糊了,我们有时会分不清我们是在写程序还是在编译程序。这也就是为什么Lisp被称之为“可以编写语言的语言”。
如果我们在学习Lisp中的列表时把列表当作一个单纯的数据结构会比较简单一些,所以我们就先从数据结构的角度先了解一下列表。
其实,就是把列表当作一个简单的数据结构,我们也无法在一篇文章中完全介绍它,所以我们分几篇文章来讨论列表。
因为常规语言中没有对应的数据结构,我们首先需要解释一下什么是Lisp语言中列表。
Lisp语言中的列表可以简单地理解为圆括号包围的一堆数据,数据之间用空格隔开,像下面这个就是一个列表:
以上列表包含三个元素,分别是a ,b 和c。
下面还有一个列表的样例:
以上列表包含四个元素,分别是abc, 10 , efg和hi。
列表中单个的元素被称作原子(atom)
列表的元素不一定需要是原子,可以是另一个列表,如下面这样:
以上列表有四个元素,分别是a , b , (abc 10 efg hi)和 c。
原因是Lisp语言解释器把(a b c)当作一行语句,尝试运算它,运算时会把a当作函数名,而b和c当作是参数。
其中的'符号表示后面的列表不需要进行计算。
第一中写法中的'(a b c)其实就是(quote (a b c))的简写。
其中list函数用于创建列表,将元素'a, 'b 和'c组合成一个列表。
以上三个语句可以认为是等价的。
创建了列表就需要访问它,常规的访问某个位置的函数是nth,形式如下:
以上函数返回my-list中的第一个元素,可以发现nth函数同样是以0为下标起始点的,也就是说下面的函数会返回my-list的第8个元素:
如果希望对列表进行遍历,可以使用loop函数,形式如下:
以上函数对my-list进行遍历,逐个输出元素的值。
此外,因为列表的特殊性,Lisp语言还提供了car和cdr两个函数对列表进行操作。
car函数返回列表的第一个元素,如:
以上函数返回my-list中的第一个元素,以原子的方式返回。
结合上面的定义,(car my-list)的返回值是A.
而cdr函数返回列表第一个元素以后的所有元素,返回时是一个列表。如下面的函数:
结合以上的定义,该函数返回(B C)
更为有趣的是cdr函数中的d字符可以重复,如cddr,或者是cdddr。
其中cddr返回第二个元素以后的所有元素,而cdddr返回第三个元素以后的所有元素。d的重复次数最多为四次,cddddr这样是合法的,而cdddddr会报错。
Lisp中还可以通过(cadr my-list)这样的形式表示(car (cdr my-list))
这样返回的是my-list中第二个元素,以原子的形式返回。
同样,其中的d字符可以,最多重复三次,如caddr, cadddr这样。
如果希望往一个列表添加元素,可以使用append函数,形式如下:
结合上面对my-list的定义,my-list的值是(A B C),所以以上函数返回(A B C D)
以上就是lisp中列表的一些基本操作。
不过列表的操作远不止这些,更多的列表操作我们在以后的文章中讨论。
列表的精彩之处在于,它不仅仅是Lisp中的一种数据结构,它也是Lisp语言的构成部分,Lisp语言中的所有语句都是一个列表。
反观我们常见的编程语言,语言中的语句有特定的语法,而这些语法只有编译器可以理解,用编程语言自己去解释自己几乎就成了一个不可能完成的任务。
举个例子,看看下面的java语句:
- int i = j + 10 ;
不过,如果给你下面名叫myString这个字符串:
- String myString = "int i = j + 10 ;"
对于一般程序员来讲这个任务几乎是不可能完成的。当然,对于大牛来讲是可以的,不过是用java语言写一个java编译器而已。
而Lisp程序员就幸运多了,不用成为大牛也可以通过Lisp语言解析Lisp语句,因为Lisp语句本身就是一个列表,如下面的代码:
- (setf i (+ j 10))
以上语句就是一个列表,通过Lisp语言的列表操作函数可以轻易地解析出其中的所有元素。当然,单纯地解析语句还不能算是一个编译器,要做一个Lisp编译器还需要很多工作。
就像下面的代码:
- (defun eval-test ()
- (setf j 5)
- (eval '(setf i (+ j 10)))
- (format *query-io* "i is: ~A ~%" i))
在这里编译期和运行期的界限被模糊了,我们有时会分不清我们是在写程序还是在编译程序。这也就是为什么Lisp被称之为“可以编写语言的语言”。
有关“可以编写语言的语言”,更多的内容我们在讨论宏(macro)的时候深入讨论,在这里只是开始讨论列表而已。
如果我们在学习Lisp中的列表时把列表当作一个单纯的数据结构会比较简单一些,所以我们就先从数据结构的角度先了解一下列表。
其实,就是把列表当作一个简单的数据结构,我们也无法在一篇文章中完全介绍它,所以我们分几篇文章来讨论列表。
因为常规语言中没有对应的数据结构,我们首先需要解释一下什么是Lisp语言中列表。
Lisp语言中的列表可以简单地理解为圆括号包围的一堆数据,数据之间用空格隔开,像下面这个就是一个列表:
- (a b c)
下面还有一个列表的样例:
- (abc 10 efg hi)
列表中单个的元素被称作原子(atom)
列表的元素不一定需要是原子,可以是另一个列表,如下面这样:
- (a b (abc 10 efg hi) c)
- (setf my-list (a b c))
正确的写法应该是:
- (setf my-list '(a b c))
另外一种写法是:
- (setf my-list (quote (a b c)))
再还有一种写法是:
- (setf my-list (list 'a 'b 'c))
以上三个语句可以认为是等价的。
创建了列表就需要访问它,常规的访问某个位置的函数是nth,形式如下:
- (nth 0 my-list)
- (nth 7 my-list)
如果希望对列表进行遍历,可以使用loop函数,形式如下:
- (loop for x in my-list do
- (format *query-io* "~a::" x))
此外,因为列表的特殊性,Lisp语言还提供了car和cdr两个函数对列表进行操作。
car函数返回列表的第一个元素,如:
- (car my-list)
结合上面的定义,(car my-list)的返回值是A.
而cdr函数返回列表第一个元素以后的所有元素,返回时是一个列表。如下面的函数:
- (cdr my-list)
更为有趣的是cdr函数中的d字符可以重复,如cddr,或者是cdddr。
其中cddr返回第二个元素以后的所有元素,而cdddr返回第三个元素以后的所有元素。d的重复次数最多为四次,cddddr这样是合法的,而cdddddr会报错。
Lisp中还可以通过(cadr my-list)这样的形式表示(car (cdr my-list))
这样返回的是my-list中第二个元素,以原子的形式返回。
同样,其中的d字符可以,最多重复三次,如caddr, cadddr这样。
如果希望往一个列表添加元素,可以使用append函数,形式如下:
- (append my-list '(d))
注意函数(append my-list '(d))并不会改变列表my-list的值,它只是返回了一个新的列表,新的列表中包含了my-list中的值和新添加的值。
如果希望操作my-list,让my-list多一个元素D,需要将代码写成这样:- (setf my-list (append my-list '(d)))
以上就是lisp中列表的一些基本操作。
不过列表的操作远不止这些,更多的列表操作我们在以后的文章中讨论。