列表的精彩之處在於,它不僅僅是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中列表的一些基本操作。
不過列表的操作遠不止這些,更多的列表操作我們在以後的文章中討論。