Lisp语言:列表(List)

终于开始讨论列表了,列表是Lisp的精华之一,也是学习Lisp的难点之一。

列表的精彩之处在于,它不仅仅是Lisp中的一种数据结构,它也是Lisp语言的构成部分,Lisp语言中的所有语句都是一个列表。
反观我们常见的编程语言,语言中的语句有特定的语法,而这些语法只有编译器可以理解,用编程语言自己去解释自己几乎就成了一个不可能完成的任务。
举个例子,看看下面的java语句:
[plain]  view plain copy
  1. int i = j + 10 ;  
这句代码给java编译器去解释是很简单的,i的值会等于j加10.
不过,如果给你下面名叫myString这个字符串:
[plain]  view plain copy
  1. String myString = "int i = j + 10 ;"  
你能否通过java代码解析myString,并准确理解这行代码的意义?
对于一般程序员来讲这个任务几乎是不可能完成的。当然,对于大牛来讲是可以的,不过是用java语言写一个java编译器而已。

而Lisp程序员就幸运多了,不用成为大牛也可以通过Lisp语言解析Lisp语句,因为Lisp语句本身就是一个列表,如下面的代码:
[plain]  view plain copy
  1. (setf i (+ j 10))  

以上语句就是一个列表,通过Lisp语言的列表操作函数可以轻易地解析出其中的所有元素。当然,单纯地解析语句还不能算是一个编译器,要做一个Lisp编译器还需要很多工作。


更为幸运的是,Lisp语言中提供了eval函数,把一个列表当参数传递给eval函数,eval函数会解析并运行该参数,就像是在Lisp语言中提供了一个现成的Lisp编译器。
就像下面的代码:
[plain]  view plain copy
  1. (defun eval-test ()  
  2.         (setf j 5)  
  3.         (eval '(setf i (+ j 10)))  
  4.         (format *query-io* "i is: ~A ~%" i))  
其中(setf i (+ j 10))被轻松地编译执行了。
在这里编译期和运行期的界限被模糊了,我们有时会分不清我们是在写程序还是在编译程序。这也就是为什么Lisp被称之为“可以编写语言的语言”。

有关“可以编写语言的语言”,更多的内容我们在讨论宏(macro)的时候深入讨论,在这里只是开始讨论列表而已。



如果我们在学习Lisp中的列表时把列表当作一个单纯的数据结构会比较简单一些,所以我们就先从数据结构的角度先了解一下列表。
其实,就是把列表当作一个简单的数据结构,我们也无法在一篇文章中完全介绍它,所以我们分几篇文章来讨论列表。

因为常规语言中没有对应的数据结构,我们首先需要解释一下什么是Lisp语言中列表。
Lisp语言中的列表可以简单地理解为圆括号包围的一堆数据,数据之间用空格隔开,像下面这个就是一个列表:
[plain]  view plain copy
  1. (a b c)  
以上列表包含三个元素,分别是a ,b  和c。
下面还有一个列表的样例:
[plain]  view plain copy
  1. (abc 10 efg hi)  
以上列表包含四个元素,分别是abc, 10 , efg和hi。
列表中单个的元素被称作原子(atom)

列表的元素不一定需要是原子,可以是另一个列表,如下面这样:
[plain]  view plain copy
  1. (a b (abc 10 efg hi) c)  
以上列表有四个元素,分别是a , b  , (abc 10 efg hi)和 c。



如果我们希望创建一个列表,赋予变量my-list,下面这样是不行的:
[plain]  view plain copy
  1. (setf my-list (a b c))  
原因是Lisp语言解释器把(a b c)当作一行语句,尝试运算它,运算时会把a当作函数名,而b和c当作是参数。


正确的写法应该是:

[plain]  view plain copy
  1. (setf my-list '(a b c))  
其中的'符号表示后面的列表不需要进行计算。


另外一种写法是:

[plain]  view plain copy
  1. (setf my-list (quote (a b c)))  
第一中写法中的'(a b c)其实就是(quote (a b c))的简写。


再还有一种写法是:

[plain]  view plain copy
  1. (setf my-list (list 'a 'b 'c))  
其中list函数用于创建列表,将元素'a, 'b 和'c组合成一个列表。
以上三个语句可以认为是等价的。

创建了列表就需要访问它,常规的访问某个位置的函数是nth,形式如下:
[plain]  view plain copy
  1. (nth 0 my-list)  
以上函数返回my-list中的第一个元素,可以发现nth函数同样是以0为下标起始点的,也就是说下面的函数会返回my-list的第8个元素:
[plain]  view plain copy
  1. (nth 7 my-list)  




如果希望对列表进行遍历,可以使用loop函数,形式如下: 
[plain]  view plain copy
  1.          (loop for x in my-list do  
  2.                 (format *query-io* "~a::" x))  
以上函数对my-list进行遍历,逐个输出元素的值。


此外,因为列表的特殊性,Lisp语言还提供了car和cdr两个函数对列表进行操作。
car函数返回列表的第一个元素,如:
[plain]  view plain copy
  1. (car my-list)  
以上函数返回my-list中的第一个元素,以原子的方式返回。
结合上面的定义,(car my-list)的返回值是A.

而cdr函数返回列表第一个元素以后的所有元素,返回时是一个列表。如下面的函数:
[plain]  view plain copy
  1. (cdr my-list)  
结合以上的定义,该函数返回(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函数,形式如下:
[plain]  view plain copy
  1. (append my-list '(d))  
结合上面对my-list的定义,my-list的值是(A B C),所以以上函数返回(A B C D)


注意函数(append my-list '(d))并不会改变列表my-list的值,它只是返回了一个新的列表,新的列表中包含了my-list中的值和新添加的值。

如果希望操作my-list,让my-list多一个元素D,需要将代码写成这样:
[plain]  view plain copy
  1. (setf my-list (append my-list '(d)))  



以上就是lisp中列表的一些基本操作。
不过列表的操作远不止这些,更多的列表操作我们在以后的文章中讨论。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值