此教程是我花了一點時間和功夫整理出來的,希望能夠幫到喜歡Lisp(Common Lisp)的朋友們。本人排版很爛還望多多海涵!
具體的內容我已經編輯好了,想下載的朋友可以用上面的鏈接。本人水平有限,如有疏漏還望之處(要是有誰幫我排排版就好了)還望指出!資料雖然是我整理的,但都是網友的智慧,如果有人需要轉載,請至少保留其中的“鳴謝”頁(如果能有我就更好了:-))。
Lisp簡明教程
整理人:Chaobs
資料主要來源:http://www.yiibai.com/lisp/
版本:0.1.0
前言
為什么Lisp語言如此先進?
(節選自《黑客與畫家》中譯本)
譯者原文:http://www.ruanyifeng.com/blog/2010/10/why_lisp_is_superior.html
一、
如果我們把流行的編程語言,以這樣的順序排列:Java、Perl、Python、Ruby。你會發現,排在越后面的語言,越像Lisp。
Python模仿Lisp,甚至把許多Lisp黑客認為屬於設計錯誤的功能,也一起模仿了。至於Ruby,如果回到1975年,你聲稱它是一種Lisp方言,沒有人會反對。
編程語言現在的發展,不過剛剛趕上1958年Lisp語言的水平。
二、
1958年,John McCarthy設計了Lisp語言。我認為,當前最新潮的編程語言,只是實現了他在1958年的設想而已。
這怎么可能呢?計算機技術的發展,不是日新月異嗎?1958年的技術,怎么可能超過今天的水平呢?
讓我告訴你原因。
這是因為John McCarthy本來沒打算把Lisp設計成編程語言,至少不是我們現在意義上的編程語言。他的原意只是想做一種理論演算,用更簡潔的方式定義圖靈機。
所以,為什么上個世紀50年代的編程語言,到現在還沒有過時?簡單說,因為這種語言本質上不是一種技術,而是數學。數學是不會過時的。你不應該把Lisp語言與50年代的硬件聯系在一起,而是應該把它與快速排序(Quicksort)算法進行類比。這種算法是1960年提出的,至今仍然是最快的通用排序方法。
三、
Fortran語言也是上個世紀50年代出現的,並且一直使用至今。它代表了語言設計的一種完全不同的方向。Lisp是無意中從純理論發展為編程語言,而Fortran從一開始就是作為編程語言設計出來的。但是,今天我們把Lisp看成高級語言,而把Fortran看成一種相當低層次的語言。
1956年,Fortran剛誕生的時候,叫做Fortran I,與今天的Fortran語言差別極大。Fortran I實際上是匯編語言加上數學,在某些方面,還不如今天的匯編語言強大。比如,它不支持子程序,只有分支跳轉結構(branch)。
Lisp和Fortran代表了編程語言發展的兩大方向。前者的基礎是數學,后者的基礎是硬件架構。從那時起,這兩大方向一直在互相靠攏。Lisp剛設計出來的時候,就很強大,接下來的二十年,它提高了自己的運行速度。而那些所謂的主流語言,把更快的運行速度作為設計的出發點,然后再用超過四十年的時間,一步步變得更強大。
直到今天,最高級的主流語言,也只是剛剛接近Lisp的水平。雖然已經很接近了,但還是沒有Lisp那樣強大。
四、
Lisp語言誕生的時候,就包含了9種新思想。其中一些我們今天已經習以為常,另一些則剛剛在其他高級語言中出現,至今還有2種是Lisp獨有的。按照被大眾接受的程度,這9種思想依次是:
1. 條件結構(即"if-then-else"結構)。現在大家都覺得這是理所當然的,但是Fortran I就沒有這個結構,它只有基於底層機器指令的goto結構。
2. 函數也是一種數據類型。在Lisp語言中,函數與整數或字符串一樣,也屬於數據類型的一種。它有自己的字面表示形式(literal representation),能夠儲存在變量中,也能當作參數傳遞。一種數據類型應該有的功能,它都有。
3. 遞歸。Lisp是第一種支持遞歸函數的高級語言。
4. 變量的動態類型。在Lisp語言中,所有變量實際上都是指針,所指向的值有類型之分,而變量本身沒有。復制變量就相當於復制指針,而不是復制它們指向的數據。
5. 垃圾回收機制。
6. 程序由表達式(expression)組成。Lisp程序是一些表達式區塊的集合,每個表達式都返回一個值。這與Fortran和大多數后來的語言都截然不同,它們的程序由表達式和語句(statement)組成。
區分表達式和語句,在Fortran I中是很自然的,因為它不支持語句嵌套。所以,如果你需要用數學式子計算一個值,那就只有用表達式返回這個值,沒有其他語法結構可用,因為否則就無法處理這個值。
后來,新的編程語言支持區塊結構(block),這種限制當然也就不存在了。但是為時已晚,表達式和語句的區分已經根深蒂固。它從Fortran擴散到Algol語言,接着又擴散到它們兩者的后繼語言。
7. 符號(symbol)類型。符號實際上是一種指針,指向儲存在哈希表中的字符串。所以,比較兩個符號是否相等,只要看它們的指針是否一樣就行了,不用逐個字符地比較。
8. 代碼使用符號和常量組成的樹形表示法(notation)。
9. 無論什么時候,整個語言都是可用的。Lisp並不真正區分讀取期、編譯期和運行期。你可以在讀取期編譯或運行代碼;也可以在編譯期讀取或運行代碼;還可以在運行期讀取或者編譯代碼。
在讀取期運行代碼,使得用戶可以重新調整(reprogram)Lisp的語法;在編譯期運行代碼,則是Lisp宏的工作基礎;在運行期編譯代碼,使得Lisp可以在Emacs這樣的程序中,充當擴展語言(extension language);在運行期讀取代碼,使得程序之間可以用S-表達式(S-expression)通信,近來XML格式的出現使得這個概念被重新"發明"出來了。
五、
Lisp語言剛出現的時候,它的思想與其他編程語言大相徑庭。后者的設計思想主要由50年代后期的硬件決定。隨着時間流逝,流行的編程語言不斷更新換代,語言設計思想逐漸向Lisp靠攏。
思想1到思想5已經被廣泛接受,思想6開始在主流編程語言中出現,思想7在Python語言中有所實現,不過似乎沒有專用的語法。
思想8可能是最有意思的一點。它與思想9只是由於偶然原因,才成為Lisp語言的一部分,因為它們不屬於John McCarthy的原始構想,是由他的學生Steve Russell自行添加的。它們從此使得Lisp看上去很古怪,但也成為了這種語言最獨一無二的特點。Lisp古怪的形式,倒不是因為它的語法很古怪,而是因為它根本沒有語法,程序直接以解析樹(parse tree)的形式表達出來。在其他語言中,這種形式只是經過解析在后台產生,但是Lisp直接采用它作為表達形式。它由列表構成,而列表則是Lisp的基本數據結構。
用一門語言自己的數據結構來表達該語言,這被證明是非常強大的功能。思想8和思想9,意味着你可以寫出一種能夠自己編程的程序。這可能聽起來很怪異,但是對於Lisp語言卻是再普通不過。最常用的做法就是使用宏。
術語"宏"在Lisp語言中,與其他語言中的意思不一樣。Lisp宏無所不包,它既可能是某樣表達式的縮略形式,也可能是一種新語言的編譯器。如果你想真正地理解Lisp語言,或者想拓寬你的編程視野,那么你必須學習宏。
就我所知,宏(采用Lisp語言的定義)目前仍然是Lisp獨有的。一個原因是為了使用宏,你大概不得不讓你的語言看上去像Lisp一樣古怪。另一個可能的原因是,如果你想為自己的語言添上這種終極武器,你從此就不能聲稱自己發明了新語言,只能說發明了一種Lisp的新方言。
我把這件事當作笑話說出來,但是事實就是如此。如果你創造了一種新語言,其中有car、cdr、cons、quote、cond、atom、eq這樣的功能,還有一種把函數寫成列表的表示方法,那么在它們的基礎上,你完全可以推導出Lisp語言的所有其他部分。事實上,Lisp語言就是這樣定義的,John McCarthy把語言設計成這個樣子,就是為了讓這種推導成為可能。
六、
就算Lisp確實代表了目前主流編程語言不斷靠近的一個方向,這是否意味着你就應該用它編程呢?
如果使用一種不那么強大的語言,你又會有多少損失呢?有時不采用最尖端的技術,不也是一種明智的選擇嗎?這么多人使用主流編程語言,這本身不也說明那些語言有可取之處嗎?
另一方面,選擇哪一種編程語言,許多項目是無所謂的,反正不同的語言都能完成工作。一般來說,條件越苛刻的項目,強大的編程語言就越能發揮作用。但是,無數的項目根本沒有苛刻條件的限制。大多數的編程任務,可能只要寫一些很小的程序,然后用膠水語言把這些小程序連起來就行了。你可以用自己熟悉的編程語言,或者用對於特定項目來說有着最強大函數庫的語言,來寫這些小程序。如果你只是需要在Windows應用程序之間傳遞數據,使用Visual Basic照樣能達到目的。
那么,Lisp的編程優勢體現在哪里呢?
七、
語言的編程能力越強大,寫出來的程序就越短(當然不是指字符數量,而是指獨立的語法單位)。
代碼的數量很重要,因為開發一個程序耗費的時間,主要取決於程序的長度。如果同一個軟件,一種語言寫出來的代碼比另一種語言長三倍,這意味着你開發它耗費的時間也會多三倍。而且即使你多雇佣人手,也無助於減少開發時間,因為當團隊規模超過某個門檻時,再增加人手只會帶來凈損失。Fred Brooks在他的名著《人月神話》(The Mythical Man-Month)中,描述了這種現象,我的所見所聞印證了他的說法。
如果使用Lisp語言,能讓程序變得多短?以Lisp和C的比較為例,我聽到的大多數說法是C代碼的長度是Lisp的7倍到10倍。但是最近,New Architect雜志上有一篇介紹ITA軟件公司的文章,里面說"一行Lisp代碼相當於20行C代碼",因為此文都是引用ITA總裁的話,所以我想這個數字來自ITA的編程實踐。 如果真是這樣,那么我們可以相信這句話。ITA的軟件,不僅使用Lisp語言,還同時大量使用C和C++,所以這是他們的經驗談。
根據上面的這個數字,如果你與ITA競爭,而且你使用C語言開發軟件,那么ITA的開發速度將比你快20倍。如果你需要一年時間實現某個功能,它只需要不到三星期。反過來說,如果某個新功能,它開發了三個月,那么你需要五年才能做出來。
你知道嗎?上面的對比,還只是考慮到最好的情況。當我們只比較代碼數量的時候,言下之意就是假設使用功能較弱的語言,也能開發出同樣的軟件。但是事實上,程序員使用某種語言能做到的事情,是有極限的。如果你想用一種低層次的語言,解決一個很難的問題,那么你將會面臨各種情況極其復雜、乃至想不清楚的窘境。
所以,當我說假定你與ITA競爭,你用五年時間做出的東西,ITA在Lisp語言的幫助下只用三個月就完成了,我指的五年還是一切順利、沒有犯錯誤、也沒有遇到太大麻煩的五年。事實上,按照大多數公司的實際情況,計划中五年完成的項目,很可能永遠都不會完成。
我承認,上面的例子太極端。ITA似乎有一批非常聰明的黑客,而C語言又是一種很低層次的語言。但是,在一個高度競爭的市場中,即使開發速度只相差兩三倍,也足以使得你永遠處在落后的位置。
附錄:編程能力
為了解釋我所說的語言編程能力不一樣,請考慮下面的問題。我們需要寫一個函數,它能夠生成累加器,即這個函數接受一個參數n,然后返回另一個函數,后者接受參數i,然后返回n增加(increment)了i后的值。
Common Lisp的寫法如下:
(defun foo (n)
(lambda (i) (incf n i)))
Ruby的寫法幾乎完全相同:
1 2 |
|
Perl 5的寫法則是:
1 2 3 4 |
|
這比Lisp和Ruby的版本,有更多的語法元素,因為在Perl語言中,你不得不手工提取參數。
Smalltalk的寫法稍微比Lisp和Ruby的長一點:
foo: n
|s|
s := n.
^[:i| s := s+i. ]
因為在Smalltalk中,局部變量(lexical variable)是有效的,但是你無法給一個參數賦值,因此不得不設置了一個新變量,接受累加后的值。
Javascript的寫法也比Lisp和Ruby稍微長一點,因為Javascript依然區分語句和表達式,所以你需要明確指定return語句,來返回一個值:
1 2 3 |
|
(實事求是地說,Perl也保留了語句和表達式的區別,但是使用了典型的Perl方式處理,使你可以省略return。)
如果想把Lisp/Ruby/Perl/Smalltalk/Javascript的版本改成Python,你會遇到一些限制。因為Python並不完全支持局部變量,你不得不創造一種數據結構,來接受n的值。而且盡管Python確實支持函數數據類型,但是沒有一種字面量的表示方式(literal representation)可以生成函數(除非函數體只有一個表達式),所以你需要創造一個命名函數,把它返回。最后的寫法如下:
1 2 3 4 5 6 |
|
Python用戶完全可以合理地質疑,為什么不能寫成下面這樣:
def foo (n):
return lambda i: return n += i
或者:
def foo (n):
lambda i: n += i
我猜想,Python有一天會支持這樣的寫法。(如果你不想等到Python慢慢進化到更像Lisp,你總是可以直接......)
在面向對象編程的語言中,你能夠在有限程度上模擬一個閉包(即一個函數,通過它可以引用由包含這個函數的代碼所定義的變量)。你定義一個類(class),里面有一個方法和一個屬性,用於替換封閉作用域(enclosing scope)中的所有變量。這有點類似於讓程序員自己做代碼分析,本來這應該是由支持局部作用域的編譯器完成的。如果有多個函數,同時指向相同的變量,那么這種方法就會失效,但是在這個簡單的例子中,它已經足夠了。
Python高手看來也同意,這是解決這個問題的比較好的方法,寫法如下:
def foo (n):
class acc:
def _ _init_ _ (self, s):
self.s = s
def inc (self, i):
self.s += i
return self.s
return acc (n).inc
或者
class foo:
def _ _init_ _ (self, n):
self.n = n
def _ _call_ _ (self, i):
self.n += i
return self.n
我添加這一段,原因是想避免Python愛好者說我誤解這種語言。但是,在我看來,這兩種寫法好像都比第一個版本更復雜。你實際上就是在做同樣的事,只不過划出了一個獨立的區域,保存累加器函數,區別只是保存在對象的一個屬性中,而不是保存在列表(list)的頭(head)中。使用這些特殊的內部屬性名(尤其是__call__),看上去並不像常規的解法,更像是一種破解。
在Perl和Python的較量中,Python黑客的觀點似乎是認為Python比Perl更優雅,但是這個例子表明,最終來說,編程能力決定了優雅。Perl的寫法更簡單(包含更少的語法元素),盡管它的語法有一點丑陋。
其他語言怎么樣?前文曾經提到過Fortran、C、C++、Java和Visual Basic,看上去使用它們,根本無法解決這個問題。Ken Anderson說,Java只能寫出一個近似的解法:
public interface Inttoint {
public int call (int i);
}
public static Inttoint foo (final int n) {
return new Inttoint () {
int s = n;
public int call (int i) {
s = s + i;
return s;
}};
}
這種寫法不符合題目要求,因為它只對整數有效。
當然,我說使用其他語言無法解決這個問題,這句話並不完全正確。所有這些語言都是圖靈等價的,這意味着嚴格地說,你能使用它們之中的任何一種語言,寫出任何一個程序。那么,怎樣才能做到這一點呢?就這個小小的例子而言,你可以使用這些不那么強大的語言,寫一個Lisp解釋器就行了。
這樣做聽上去好像開玩笑,但是在大型編程項目中,卻不同程度地廣泛存在。因此,有人把它總結出來,起名為"格林斯潘第十定律"(Greenspun's Tenth Rule):"任何C或Fortran程序復雜到一定程度之后,都會包含一個臨時開發的、只有一半功能的、不完全符合規格的、到處都是bug的、運行速度很慢的Common Lisp實現。"
如果你想解決一個困難的問題,關鍵不是你使用的語言是否強大,而是好幾個因素同時發揮作用(a)使用一種強大的語言,(b)為這個難題寫一個事實上的解釋器,或者(c)你自己變成這個難題的人肉編譯器。在Python的例子中,這樣的處理方法已經開始出現了,我們實際上就是自己寫代碼,模擬出編譯器實現局部變量的功能。這種實踐不僅很普遍,而且已經制度化了。舉例來說,在面向對象編程的世界中,我們大量聽到"模式"(pattern)這個詞,我覺得那些"模式"就是現實中的因素(c),也就是人肉編譯器。 當我在自己的程序中,發現用到了模式,我覺得這就表明某個地方出錯了。程序的形式,應該僅僅反映它所要解決的問題。代碼中其他任何外加的形式,都是一個信號,(至少對我來說)表明我對問題的抽象還不夠深,也經常提醒我,自己正在手工完成的事情,本應該寫代碼,通過宏的擴展自動實現。
來源:http://www.cnblogs.com/syeerzy/articles/3548899.html
編者記
我一直都很喜歡Lisp這樣的語言。
很多人會問XX語言流行嗎?XX語言能賺錢嗎?XX語言前景怎么樣?其實,我們需要問的是:
這種語言好用嗎?
這種語言強大嗎?
這種語言的思維方式是什么?
當你能清楚的回答這樣的問題時,這就是一種合適的語言了。Lisp就是這樣的一門語言,具體的觀點各位讀者可以從前面的這篇《為什么Lisp語言如此先進?》窺見一二,但Lisp的真正魅力無疑還須各位親自領略。我學習Lisp以來發現在國內學習Lisp最大的難處就是資料少,目前比較好買的書就是《實用Common Lisp編程》,其它的大多老舊或者是某一特定領域的Lisp。即使是國內規模比較大的Lisp中文社區上,想要找到一份詳盡且適合初學者的Lisp也並不是那么簡單的。我很早就萌發了自己撰寫一部關於Lisp編程的書籍的念頭,正好在易百網(http://yiibai.com/)上發現了這一系列Lisp教程,這可真是雪中送炭。我將它們搜集起來一起編輯成這份文檔,希望能各位熱愛Lisp的朋友提供一點幫助。
在深入學習這份文檔前,容我指出這份文檔的不足:
1.沒有給出Lisp環境搭建的指導,這方面的內容讀者可以參見《實用Common Lisp編程》或 者自行搜索SBCL,GCL等CL實現,在這份文檔的下一版本中我會把這個坑給填上的,第一 版時間緊促就不管實現了:-);
2.對於一些深入的主題沒有初級,畢竟這只是一份“簡易”的教程,想要深入學習的強烈推薦 ANSI的那本Common Lisp手冊,不過只有英文版的,《計算機程序的構造與解釋》,這本書 我沒看過,單據說是經典,還有一本《Common Lisp符號計算引論》太復雜了,喜歡的可以自 己搜;
3.沒有比較系統的案例,這點我覺得《實用Common Lisp編程》已經寫得很好了,下一版本時 我也會補充上的;
4.糟糕的排版,這個全怪我,我也沒學過什么LaTex、Word排版,以前論文排版也是亂七八糟 的,還是別人幫我排的,如果你覺得不爽也請通過郵箱(chaobs@outlook.com)聯系我,幫 我一起排版!
Chaobs
CUCS
2015年10月
鳴謝
一切榮耀屬於網友
長工 http://www.yiibai.com/lisp/lisp_overview.html
YeaWind http://www.yiibai.com/lisp/lisp_program_structure.html
ache038 http://www.yiibai.com/lisp/lisp_basic_syntax.html
逝風123 http://www.yiibai.com/lisp/lisp_data_types.html
曦花 http://www.yiibai.com/lisp/lisp_macros.html
yak http://www.yiibai.com/lisp/lisp_variables.html
黑狗 http://www.yiibai.com/lisp/lisp_constants.html
WiJQ http://www.yiibai.com/lisp/lisp_operators.html
快樂學習 http://www.yiibai.com/lisp/lisp_decisions.html
php小浩 http://www.yiibai.com/lisp/lisp_loops.html
stone-sun http://www.yiibai.com/lisp/lisp_functions.html
sallay http://www.yiibai.com/lisp/lisp_predicates.html
夢醒以后 http://www.yiibai.com/lisp/lisp_numbers.html
劉鑫華 http://www.yiibai.com/lisp/lisp_characters.html
綠水無痕 http://www.yiibai.com/lisp/lisp_arrays.html
kevinG http://www.yiibai.com/lisp/lisp_symbols.html
iTony http://www.yiibai.com/lisp/lisp_vectors.html
hibernate_jss http://www.yiibai.com/lisp/lisp_set.html
如是傳統 http://www.yiibai.com/lisp/lisp_tree.html
鄭小千 http://www.yiibai.com/lisp/lisp_hash_table.html
花田軟件 http://www.yiibai.com/lisp/lisp_input_output.html
Anger_Coder http://www.yiibai.com/lisp/lisp_file_io.html
HerbertYang http://www.yiibai.com/lisp/lisp_structures.html
vigiles http://www.yiibai.com/lisp/lisp_packages.html
楓愛若雪 http://www.yiibai.com/lisp/lisp_error_handling.html
百mumu http://www.yiibai.com/lisp/lisp_clos.html
再次對這些網友的無私貢獻表示最誠摯的感謝!
錯誤反饋
沒有一本書沒有BUG,這篇文檔肯定存在很多知識上的漏洞、錯別字、排版上的不合適,由於水平有限,歡迎指正。如果你發現任何問題或者對內容有補充,請不吝賜教!讓我們一起把這本教程做大!
聯系郵箱:chaobs@outlook.com , q578836573@163.com
Chaobs
CUCS
2015年10月
目錄
-
LISP - 概述介紹
-
LISP – 程序結構
-
LISP – 基本語法
-
LISP – 數據類型
-
LISP – 宏
-
LISP – 變量
-
LISP – 常量
-
LISP – 運算符
-
LISP – 決策
-
LISP – 循環
-
LISP – 函數
-
LISP – 謂詞
-
LISP – 字符
-
LISP – 數組
-
LISP – 符號
-
LISP – 向量
-
LISP – 集合
-
LISP – 樹
-
LISP – 哈希表
-
LISP – 輸入和輸出
-
LISP – 文件I/O
-
LISP – 結構
-
LISP – 包
-
LISP – 錯誤處理
-
LISP – 對象系統(CLOS)
-
附錄:我為什么喜歡Lisp語言
1 LISP - 概述介紹
Lisp是Fortran語言之后第二古老的高級編程語言,自成立之初已發生了很大變化,和一些方言一直存在在它的歷史。今天,最廣為人知的通用的Lisp方言Common Lisp和Scheme。Lisp由約翰·麥卡錫在1958年發明,在麻省理工學院(MIT)。
該參考將帶您通過簡單實用的方法,同時學習Lisp程序設計語言。
Lisp是一門歷史悠久的語言,全名叫LISt Processor,也就是“表處理語言”,它是由John McCarthy於1958年就開始設計的一門語言。和Lisp同時期甚至更晚出現的許多語言如Algo等如今大 多已經消亡,又或者僅僅在一些特定的場合有一些微不足道的用途,到現在還廣為人知的恐怕只剩下了 Fortran和COBOL。但唯獨Lisp,不但沒有隨着時間而衰退,反倒是一次又一次的煥發出了青春,從Lisp分支出來的Scheme、ML等語言 在很多場合的火爆程度甚至超過了許多老牌明星。那么這顆常青樹 永葆青春的奧秘究竟在哪里呢?
如果你只接觸過C/C++、Pascal這些“過程式語言”的話,Lisp可能會讓你覺得十分不同尋常,首先吸引你眼球(或者說讓你覺得混亂的)一定是 Lisp程序中異常多的括號,當然從現在的角度來講,這種設計的確對程序員不大友好,不過考慮到五六十年代的計算機處理能力,簡化語言本身的設計在那時算 得上是當務之急了。
1.1讀者
該參考是不完全是為初學者准備的,只是幫助他們了解基本的到相關LISP編程語言的先進理念。但前提條件是假設你已經知道什么是計算機程序,什么是計算機編程語言,至少已有用一種高級語言編程的經歷,且至少寫過三個程序。
1.2 LISP - 歷史介紹
約翰·麥卡錫發明LISP於1958年,FORTRAN語言的發展后不久。首次由史蒂夫·拉塞爾實施在IBM704計算機上。它特別適合用於人工智能方案,因為它有效地處理的符號信息。Common Lisp的起源,20世紀80年代和90年代,分別接班人Maclisp像ZetaLisp和NIL(Lisp語言的新實施)等開發。
它作為一種通用語言,它可以很容易地擴展為具體實施。編寫Common Lisp程序不依賴於機器的具體特點,如字長等。
1.3 Common Lisp的特點
-
這是機器無關
-
它采用迭代設計方法,且易於擴展。
-
它允許動態更新的程序。
-
它提供了高層次的調試。
-
它提供了先進的面向對象編程。
-
它提供了方便的宏系統。
-
它提供了對象,結構,列表,向量,可調數組,哈希表和符號廣泛的數據類型。
-
它是以表達為主。
-
它提供了一個面向對象的系統條件。
-
它提供完整的I/ O庫。
-
它提供了廣泛的控制結構。
1.4 LISP的內置應用程序
大量成功的應用建立在Lisp語言。
-
Emacs
-
G2
-
AutoCad
-
Igor Engraver
-
Yahoo Store
2 LISP - 程序結構
LISP表達式稱為符號表達式或S-表達式。s表達式是由三個有效對象,原子,列表和字符串。任意的s-表達式是一個有效的程序。Lisp程序在解釋器或編譯的代碼運行。解釋器會檢查重復的循環,這也被稱為讀 - 計算 - 打印循環(REPL)源代碼。它讀取程序代碼,計算,並打印由程序返回值。
2.1 一個簡單的程序
讓我們寫一個s-表達式找到的三個數字7,9和11的總和。要做到這一點,我們就可以輸入在提示符的解釋器 ->:
(+7911)
LISP返回結果:
27
如果想運行同一程序的編譯代碼,那么創建一個名為myprog的一個LISP源代碼文件。並在其中輸入如下代碼:
(write(+7911))
單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
27
2.2 Lisp使用前綴表示法
可能已經注意到,使用LISP前綴符號。在上面的程序中的+符號可以作為對數的求和過程中的函數名。在前綴表示法,運算符在自己操作數前寫。例如,表達式,
a *( b + c )/ d
將被寫為:
(/(* a (+ b c)) d)
讓我們再舉一個例子,讓我們寫的代碼轉換為60o F華氏溫度到攝氏刻度:
此轉換的數學表達式為:
(60*9/5)+32
創建一個名為main.lisp一個源代碼文件,並在其中輸入如下代碼:
(write(+(*(/95)60)32))
當單擊Execute按鈕,或按下Ctrl+ E,MATLAB立即執行它,返回的結果是:
140
2.3 計算Lisp程序
計算LISP程序有兩部分:
-
程序文本由一個讀取器程序轉換成Lisp對象
-
語言的語義在這些對象中的條款執行求值程序
計算過程采用下面的步驟:
-
讀取器轉換字符到LISP對象或S-表達式的字符串。
-
求值器定義為那些從s-表達式內置的Lisp語法形式。
計算第二個級別定義的語法決定了S-表達式是LISP語言形式。求值器可以作為一個函數,它接受一個有效的LISP語言的形式作為參數並返回一個值。這就是為什么我們把括號中的LISP語言表達,因為我們要發送的整個表達式/形式向求值作為參數的原因。
2.4 'Hello World' 程序
學習一門新的編程語言並沒有真正起飛,直到學會如何迎接語言的整個世界,對吧!所以,創建一個名為main.lisp新的源代碼文件,並在其中輸入如下代碼:
(write-line "Hello World")(write-line "I am at 'Tutorials Yiibai'! Learning LISP")
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
HelloWorld I am at 'Tutorials Yiibai'!Learning LISP
3 LISP - 基本語法
3.1 LISP基本構建塊
Lisp程序是由三個基本構建塊:
-
atom
-
list
-
string
一個原子是一個數字連續字符或字符串。它包括數字和特殊字符。以下是一些有效的原子的例子:
hello-from-tutorials-yiibai
name
123008907*hello*Block#221 abc123
列表是包含在括號中的原子和/或其他列表的序列。以下是一些有效的列表的示例:
( i am a list)(a ( a b c) d e fgh)(father tom ( susan bill joe))(sun mon tue wed thur fri sat)()
字符串是一組括在雙引號字符。以下是一些有效的字符串的例子:
" I am a string""a ba c d efg #$%^&!""Please enter the following details :""Hello from 'Tutorials Yiibai'! "
3.2 添加注釋
分號符號(;)是用於表示一個注釋行。
例如,
(write-line "Hello World"); greet the world
; tell them your whereabouts
(write-line "I am at 'Tutorials Yiibai'! Learning LISP")
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
HelloWorld I am at 'Tutorials Yiibai'!Learning LISP
3.3 移動到下一節之前的一些值得注意的要點
以下是一些要點需要注意:
-
在LISP語言的基本數學運算是 +, -, *, 和 /
-
Lisp實際上是一個函數調用f(x)為 (f x),例如 cos(45)被寫入為 cos 45
-
LISP表達式是不區分大小寫的,cos 45 或COS 45是相同的。
-
LISP嘗試計算一切,包括函數的參數。只有三種類型的元素是常數,總是返回自己的值:
-
數字
-
字母t,即表示邏輯真
-
該值為nil,這表示邏輯false,還有一個空的列表。
3.4 稍微介紹一下LISP形式
在前面的章節中,我們提到LISP代碼計算過程中采取以下步驟:讀取器轉換字符到LISP對象的字符串或 s-expressions.求值器定義為那些從s-表達式內置的Lisp語法形式。計算第二個級別定義的語法決定了S-表達式是LISP語言形式。
現在,一個LISP的形式可以是:
-
一個原子
-
空或非名單
-
有符號作為它的第一個元素的任何列表
求值器可以作為一個函數,它接受一個有效的LISP語言的形式作為參數,並返回一個值。這個就是為什么我們把括號中的LISP語言表達,因為我們要發送的整個表達式/形式向求值作為參數的原因。
3.5 LISP命名約定
名稱或符號可以包含任意數量的空白相比,開放和右括號,雙引號和單引號,反斜杠,逗號,冒號,分號和豎線其他字母數字字符。若要在名稱中使用這些字符,需要使用轉義字符()。一個名字可以包含數字,但不能全部由數字組成,因為那樣的話它會被解讀為一個數字。同樣的名稱可以具有周期,但周期不能完全進行。
3.6 使用單引號
LISP計算一切,包括函數的參數和列表的成員。有時,我們需要采取原子或列表字面上,不希望他們求值或當作函數調用。要做到這一點,我們需要先原子或列表中帶有單引號。
下面的例子演示了這一點:
創建一個名為main.lisp文件,並鍵入下面的代碼進去:
write-line "single quote used, it inhibits evaluation")(write '(* 2 3))
(write-line " ")
(write-line "single quote not used, so expression evaluated")
(write (* 2 3))
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
single quote used, it inhibits evaluation
(*23)
single quote not used, so expression evaluated
6
4 LISP - 數據類型
在LISP中,變量沒有類型的,但有數據對象。LISP數據類型可分類為:
-
標量類型 - 例如,數字類型,字符,符號等。
-
數據結構 - 例如,列表,向量,比特向量和字符串。
任何變量都可以采取任何的Lisp對象作為它的值,除非明確地聲明它。雖然,這是沒有必要指定一個Lisp變量的數據類型,但是,它有助於在一定的循環擴展,在方法聲明和其他一些情況下,我們將在后面的章節中討論。 該數據類型被布置成層次結構。數據類型是一組LISP對象和多個對象可能屬於這樣的一套。
-
typep謂詞用於發現一個對象是否屬於一個特定的類型。
-
type-of函數,返回給定對象的數據類型的類型。
4.1 在LISP類型說明符
類型說明符是數據類型的系統定義的符號。
array |
fixnum |
package |
simple-string |
atom |
float |
pathname |
simple-vector |
bignum |
function |
random-state |
single-float |
bit |
hash-table |
ratio |
standard-char |
bit-vector |
integer |
rational |
stream |
character |
keyword |
readtable |
string |
[common] |
list |
sequence |
[string-char] |
compiled-function |
long-float |
short-float |
symbol |
complex |
nill |
signed-byte |
t |
cons |
null |
simple-array |
unsigned-byte |
double-float |
number |
simple-bit-vector |
vector |
除了這些系統定義的類型,可以創建自己的數據類型。當一個結構類型是使用defstruct函數定義,結構類型的名稱將成為一個有效的類型符號。
示例1
創建一個名為main.lisp新的源代碼文件,並在其中輸入如下代碼:
(setq x 10)(setq y 34.567)(setq ch nil)(setq n 123.78)(setq bg 11.0e+4)(setq r 124/2)(print x)(print y)(print n)(print ch)(print bg)(print r)
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
10
34.567
123.78
NIL
110000.0
62
實例2
接下來讓我們看看前面的例子中使用的變量的類型。創建一個名為main.lisp新的源代碼文件,並在其中輸入如下代碼:
(setq x 10)(setq y 34.567)(setq ch nil)(setq n 123.78)(setq bg 11.0e+4)(setq r 124/2)(print(type-of x))(print(type-of y))(print(type-of n))(print(type-of ch))(print(type-of bg))(print(type-of r))
當您單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
(INTEGER 0281474976710655)
SINGLE-FLOAT
SINGLE-FLOAT
NULL
SINGLE-FLOAT
(INTEGER 0281474976710655)
5 LISP - 宏
宏可以擴展標准LISP的語法。從技術上講,宏是一個函數,它接受一個s-expression作為參數,並返回一個LISP的形式,然后進行評估計算。
5.1 定義一個宏
在LISP中,一個名為宏使用另一個名為defmacro宏定義。定義一個宏的語法:
(defmacro macro-name (parameter-list)"Optional documentation string." body-form)
宏定義包含宏的名稱,參數列表,可選的文檔字符串,和Lisp表達式的體,它定義要由宏執行的任務。
實例
讓我們寫了一個名為setTo10簡單的宏,將采取一系列並將其值設置為10。創建一個名為main.lisp新的源代碼文件,並在其中輸入如下代碼:
defmacro setTo10(num)(setq num 10)(print num))(setq x 25)(print x)(setTo10 x)
當您單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
25
10
6 LISP - 變量
在LISP中,每個變量由一個'符號'表示。變量的名稱是符號的名字,並將其存儲在碼元的存儲單元。
6.1 全局變量
全局變量有永久值在整個LISP系統,並保持有效,直到指定的新值。全局變量是使用defvar結構一般聲明。
例如:
(defvar x 234)(write x)
當您單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
234
由於沒有類型聲明在LISP變量,可直接用setq一樣構建一個符號指定一個值
例如,
->(setq x 10)
上面的表達式的值10賦給變量x,也可以使用符號本身作為一個表達式來引用該變量。
符號值函數允許提取存儲在符號存儲位置的值。
示例
創建一個名為main.lisp新的源代碼文件,並在其中輸入如下代碼:
(setq x 10)(setq y 20)(format t "x = ~2d y = ~2d ~%" x y)(setq x 100)(setq y 200)(format t "x = ~2d y = ~2d" x y)
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
x =10 y =20
x =100 y =200
6.2 局部變量
局部變量在給定的過程中定義。被命名為一個函數定義中參數的參數也是局部變量。局部變量只能訪問內相應的功能。像的全局變量,也可以使用本setq一樣構建體被創建的局部變量。還有其他兩種結構- let和prog創建局部變量。
該let結構的語法如下:
(let((var1 val1)(var2 val2)..(varn valn))<s-expressions>)
其中var1, var2, ..varn 是變量名和val1, val2, .. valn是分配給相應的變量的初始值。
當執行let,每個變量被分配了各自的值,最后的s-expression。則返回最后一個表達式的值。
如果不包括的變量的初始值,它被分配到nil。
例子
創建一個名為main.lisp新的源代碼文件,並在其中輸入如下代碼:
(let((x 'a)
(y 'b)(z 'c))
(format t "x = ~a y = ~a z = ~a" x y z))
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
x = A y = B z = C
該編結構也有局部變量作為第一個參數,它后面是prog的主體,以及任意數量s-expressions的列表。
該編函數執行s-expressions序列的列表,並返回零,除非遇到函數調用名返回。然后函數參數計算並返回。
例子
創建一個名為main.lisp新的源代碼文件,並在其中輸入如下代碼:
(prog ((x '(a b c))
(y '(123))(z '(p q 10)))
(format t "x = ~a y = ~a z = ~a" x y z))
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
x =(A B C) y =(123) z =(P Q 10)
7 LISP - 常量
在LISP中,常量變量在程序執行期間,從來沒有改變它們的值。常量使用defconstant結構聲明。
例子
下面的例子顯示了聲明一個全局常量PI和以后使用的函數命名area-circle計算圓的面積的值。該函數defun結構用於定義一個函數,我們將看看它在“函數”一章。創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defconstant PI 3.141592)(defun area-circle(rad)(terpri)(format t "Radius: ~5f" rad)(format t "~%Area: ~10f"(* PI rad rad)))(area-circle 10)
當您單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
Radius:10.0Area:314.1592
8 LISP - 運算符
運算符是一個符號,它告訴編譯器執行特定的數學或邏輯操作。 LISP允許在眾多的數據業務,通過各種函數,宏和其他結構的支持。允許對數據的操作都可以歸類為:
-
算術運算
-
比較操作
-
邏輯運算
-
位運算
8.1 算術運算
下表列出了所有支持的LISP算術運算符。假設變量A=10和變量B=20則:
運算符 |
描述 |
Example |
+ |
增加了兩個操作數 |
(+ A B) = 30 |
- |
從第一數減去第二個操作數 |
(- A B)= -10 |
* |
乘兩個操作數 |
(* A B) = 200 |
/ |
通過取消分子除以分子 |
(/ B A) = 2 |
mod,rem |
模運算符和其余整數除法后 |
(mod B A ) = 0 |
incf |
遞增運算符,所指定的第二個參數增加整數值 |
(incf A 3) = 13 |
decf |
遞減操作符,通過指定的第二個參數減小整數值 |
(decf A 4) = 9 |
例子
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a 10)(setq b 20)(format t "~% A + B = ~d"(+ a b))(format t "~% A - B = ~d"(- a b))(format t "~% A x B = ~d"(* a b))(format t "~% B / A = ~d"(/ b a))(format t "~% Increment A by 3 = ~d"(incf a 3))(format t "~% Decrement A by 4 = ~d"(decf a 4))
當您單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
A + B =30 A - B =-10 A x B =200 B / A =2Increment A by3=13Decrement A by4=9
8.2 比較操作
下表列出了所有支持的LISP關系運算符的數字之間進行比較。然而不像其他語言的關系運算符,LISP的比較操作符可能需要超過兩個操作數,他們在只有數字工作。
假設變量A=10和變量B=20,則:
Operator |
描述 |
Example |
= |
檢查如果操作數的值都相等與否,如果是的話那么條件為真。 |
(= A B)= true. |
/= |
檢查如果操作數的值都不同,或沒有,如果值不相等,則條件為真。 |
(/= A B) =true. |
> |
檢查如果操作數的值單調遞減。 |
(> A B) !=true. |
< |
檢查如果操作數的值單調遞增。 |
(< A B) = true. |
>= |
如有左操作數的值大於或等於下一個右操作數的值,如果是則條件檢查為真。 |
(>= A B) !=true. |
<= |
如有左操作數的值小於或等於其右操作數的值,如果是,則條件檢查為真。 |
(<= A B) = true. |
max |
它比較兩個或多個參數,並返回最大值。 |
(max A B) 返回20 |
min |
它比較兩個或多個參數,並返回最小值。 |
(min A B) 返回20 |
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a 10)(setq b 20)(format t "~% A = B is ~a"(= a b))(format t "~% A /= B is ~a"(/= a b))(format t "~% A > B is ~a"(> a b))(format t "~% A < B is ~a"(< a b))(format t "~% A >= B is ~a"(>= a b))(format t "~% A <= B is ~a"(<= a b))(format t "~% Max of A and B is ~d"(max a b))(format t "~% Min of A and B is ~d"(min a b))
當您單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
A = B is NIL
A /= B is T
A > B is NIL
A < B is T
A >= B is NIL
A <= B is T
Max of A and B is20Min of A and B is10
8.3 布爾值邏輯操作
Common Lisp中提供了三種邏輯運算符:AND,OR,而不是運算符的布爾值。假定A=nil,B=5,那么
運算符 |
描述 |
示例 |
and |
這需要任意數量的參數。該參數是從左向右計算。如果所有參數的計算結果為非零,那么最后一個參數的值返回。否則就返回nil。 |
(and A B) = NIL. |
or |
這需要任意數量的參數。該參數是從左向右計算的,直到一個計算結果為非零,則此情況下返回參數值,否則返回nil。 |
(or A B) = 5. |
not |
它接受一個參數,並返回t,如果參數的計算結果為nil。 |
(not A) = T. |
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a 10)(setq b 20)(format t "~% A and B is ~a"(and a b))(format t "~% A or B is ~a"(or a b))(format t "~% not A is ~a"(not a))(terpri)(setq a nil)(setq b 5)(format t "~% A and B is ~a"(and a b))(format t "~% A or B is ~a"(or a b))(format t "~% not A is ~a"(not a))(terpri)(setq a nil)(setq b 0)(format t "~% A and B is ~a"(and a b))(format t "~% A or B is ~a"(or a b))(format t "~% not A is ~a"(not a))(terpri)(setq a 10)(setq b0)(setq c 30)(setq d 40)(format t "~% Result of and operation on 10, 0, 30, 40 is ~a"(and a b c d))(format t "~% Result of and operation on 10, 0, 30, 40 is ~a"(or a b c d))(terpri)(setq a 10)(setq b 20)(setq c nil)(setq d 40)(format t "~% Result of and operation on 10, 20, nil, 40 is ~a"(and a b c d))(format t "~% Result of and operation on 10, 20, nil, 40 is ~a"(or a b c d))
當您單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
A and B is20 A or B is10not A is NIL
A and B is NIL
A or B is5not A is T
A and B is NIL
A or B is0not A is T
Result of and operation on 10,0,30,40is40Result of and operation on 10,0,30,40is10
Result of and operation on 10,20,nil,40is NIL
Result of and operation on 10,20,nil,40is10
請注意,邏輯運算工作,布爾值,其次,數字為零,NIL不是一樣的。
8.4 對數位運算
位運算符位工作並進行逐位操作。對於按位與,或,和XOR運算的真值表如下:
p |
q |
p and q |
p or q |
p xor q |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
1 |
0 |
0 |
1 |
1 |
Assumeif A =60;and B =13; now in binary format they will be as follows: A =00111100 B =00001101----------------- A and B =00001100 A or B=00111101 A xor B =00110001not A =11000011
通過LISP支持位運算符列於下表中。假設變量A=60和變量B=13,則:
操作符 |
描述 |
Example |
logand |
這將返回位邏輯的參數和。如果沒有給出參數,則結果為-1,這是該操作的標識。 |
(logand a b)) = 12 |
logior |
這將返回位邏輯包括它的參數或。如果沒有給出參數,那么結果是零,這是該操作的標識。 |
(logior a b) = 61 |
logxor |
這將返回其參數的按位邏輯異或。如果沒有給出參數,那么結果是零,這是該操作的標識。 |
(logxor a b) = 49 |
lognor |
這不返回的逐位它的參數。如果沒有給出參數,則結果為-1,這是該操作的標識。 |
(lognor a b) = -62, |
logeqv |
這將返回其參數的逐位邏輯相等(也稱為異或非)。如果沒有給出參數,則結果為-1,這是該操作的標識。 |
(logeqv a b) = -50 |
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a 60)(setq b 13)(format t "~% BITWISE AND of a and b is ~a"(logand a b))(format t "~% BITWISE INCLUSIVE OR of a and b is ~a"(logior a b))(format t "~% BITWISE EXCLUSIVE OR of a and b is ~a"(logxor a b))(format t "~% A NOT B is ~a"(lognor a b))(format t "~% A EQUIVALANCE B is ~a"(logeqv a b))(terpri)(terpri)(setq a 10)(setq b 0)(setq c 30)(setq d 40)(format t "~% Result of bitwise and operation on 10, 0, 30, 40 is ~a"(logand a b c d))(format t "~% Result of bitwise or operation on 10, 0, 30, 40 is ~a"(logior a b c d))(format t "~% Result of bitwise xor operation on 10, 0, 30, 40 is ~a"(logxor a b c d))(format t "~% Result of bitwise eqivalance operation on 10, 0, 30, 40 is ~a"(logeqv a b c d))
當您單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
BITWISE AND of a and b is12 BITWISE INCLUSIVE OR of a and b is61 BITWISE EXCLUSIVE OR of a and b is49 A NOT B is-62 A EQUIVALANCE B is-50
Result of bitwise and operation on 10,0,30,40is0Result of bitwise or operation on 10,0,30,40is62Result of bitwise xor operation on10,0,30,40is60Result of bitwise eqivalance operation on 10,0,30,40is-61
9 LISP - 決策
決策結構需要程序員指定一個或多個條件由程序進行評估或測試,以及要執行的語句或語句如果條件被確定為true,如果條件被確定為false那么選擇要執行其他語句。
下面是在大多數編程語言中一個典型的決策結構的一般形式為:
LISP提供了以下類型的決策構造。
Construct |
描述 |
cond |
這個結構是用於用於檢查多個測試行動作條件。它可以嵌套if或其他編程語言語句。 |
if |
if結構有多種形式。在最簡單的形式,它后面跟着一個測試條,測試操作和一些其它相應措施(次)。如果測試子句的值為true,那么測試的動作被執行,否則,由此產生的子句求值。 |
when |
在最簡單的形式,它后面跟着一個測試條和測試操作。如果測試子句的值為true,那么測試的動作被執行,否則,由此產生的子句求值。 |
case |
這種結構實現了像cond 構造多個測試行動語句。但是,它會評估的關鍵形式,並允許根據該鍵的形式評價多個行動語句。 |
9.1 LISP的cond特殊構造
在LISP語言中cond結構是最常用的,以允許分支。
cond的語法是:
(cond (test1 action1)(test2 action2)...(testn actionn))
在cond 語句中每個子句包含一個條件測試,並要執行的動作。
如果第一次測試下面的芯線,為test1,被評估為true,那么相關的行動的一部分, action1執行,返回它的值,及本子句的其余部分被跳過。如果test1的計算結果是nil,然后控制移動到第二個子句,而不執行action1,和相同的流程進行后續處理。如果沒有試驗條件計算結果為真,那么cond語句返回nil。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a 10)(cond ((> a 20)(format t "~% a is less than 20"))(t (format t "~% value of a is ~d " a)))
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
value of a is10
請注意,第二個子句中t保證的是,如果沒有其他的將最后完成的動作。
9.2 if結構
如果該宏后跟一個測試子句計算為 t 或nil。如果測試子句計算到t,然后按照測試子句的動作被執行。如果它是零,那么下一個子句進行評估計算。
if的語法:
(if (test-clause) (<action1) (action2))
示例1
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a 10)(if(> a 20)(format t "~% a is less than 20"))(format t "~% value of a is ~d " a)
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
value of a is10
示例2
if子句后面可以跟一個可選的then子句:
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a 10)(if(> a 20)then(format t "~% a is less than 20"))(format t "~% value of a is ~d " a)
當您單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
a is less than 20 value of a is10
示例3
還可以創建使用if子句的if-then-else類型聲明。
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a 100)(if(> a 20)(format t "~% a is greater than 20")
(format t "~% a is less than 20"))(format t "~% value of a is ~d " a)
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
a is greater than 20 value of a is100
9.3 when構造
該when宏,后面跟着一個測試子句計算為t或為零。如果測試條被評估計算為nil,則任何形式的評估及nil返回,但是它的測試結果為t,則下面的測試條的動作被執行。
when宏的語法:
(when (test-clause) (<action1) )
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a 100)(when(> a 20)(format t "~% a is greater than 20"))(format t "~% value of a is ~d " a)
當您單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
a is greater than 20 value of a is100
9.4 case構造
case結構實現像cond結構多個測試動作語句。但是,它會評估的鍵形式,並允許根據該鍵的形式評價多個動作語句。
該case宏的語法是:
The template for CASE is:
(case(keyform)((key1)(action1 action2 ...))((key2)(action1 action2 ...))...((keyn)(action1 action2 ...)))
(setq day 4)(case day
(1(format t "~% Monday"))(2(format t "~% Tuesday"))(3(format t "~% Wednesday"))(4(format t "~% Thursday"))(5(format t "~% Friday"))(6(format t "~% Saturday"))(7(format t "~% Sunday")))
當您單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
Thursday
10 LISP - 循環
可能有一種情況,當需要執行代碼塊多次。循環語句可以讓我們執行一個語句或語句組多次,下面是在大多數編程語言中的循環語句的一般形式為:
LISP提供的結構來處理循環要求以下類型。
Construct |
描述 |
loop |
循環loop結構是迭代通過LISP提供的最簡單的形式。在其最簡單的形式,它可以重復執行某些語句(次),直到找到一個return語句。 |
loop for |
loop結構可以實現一個for循環迭代一樣作為最常見於其他語言。 |
do |
do 結構也可用於使用LISP進行迭代。它提供了迭代的一種結構形式。 |
dotimes |
dotimes構造允許循環一段固定的迭代次數。 |
dolist |
dolist來構造允許迭代通過列表的每個元素。 |
10.1 循環loop結構
循環loop結構是迭代通過LISP提供的最簡單的形式。在其最簡單的形式,它可以重復執行某些語句(次),直到找到一個return語句。它的語法如下:
(loop (s-expressions))
例子
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a 10)(loop
(setq a (+ a 1))(write a)(terpri)(when(> a 17)(return a)))
當執行的代碼,它返回以下結果:
1112131415161718
請注意,沒有return語句,循環宏會產生一個無限循環。
10.2 循環的構造
loop結構可以實現一個for循環迭代一樣作為最常見於其他語言。它可以
-
設置為迭代變量
-
指定表達式(s)表示,將有條件終止迭代
-
對於執行某些任務在每次迭代中指定表達式的結果
-
做一些任務而退出循環之前指定表達式(s)和表達式
在for循環的結構如下幾種語法:
(loop for loop-variable in<a list>do(action))
(loop for loop-variable from value1 to value2
do(action))
示例1
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(loop for x in'(tom dick harry)
do (format t " ~s" x)
)
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
TOM DICK HARRY
示例2
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(loop for a from10 to 20do(print a))
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
10
11
12
13
14
15
16
17
18
19
20
示例3
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(loop for x from1 to 20if(evenp x)do(print x))
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
2
4
6
8
10
12
14
16
18
20
10.3 do構造
do結構也可用於使用LISP進行迭代。它提供了迭代的一種結構形式。
do語句的語法:
(do(variable1 value1 updated-value1)(variable2 value2 updated-value2)(variable3 value3 updated-value3)...(test return-value)(s-expressions))
每個變量的初始值的計算和結合到各自的變量。每個子句中更新的值對應於一個可選的更新語句,指定變量的值將在每次迭代更新。每次迭代后,將測試結果進行評估計算,並且如果它返回一個nil 或 true,則返回值被求值並返回。最后一個S-表達式(s)是可選的。如果有,它們每一次迭代后執行,直到測試返回true值。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(do((x 0(+2 x))(y 20(- y 2)))((= x y)(- x y))(format t "~% x = ~d y = ~d" x y))
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
x =0 y =20 x =2 y =18 x =4 y =16 x =6 y =14 x =8 y =12
10.4 dotimes 構造
dotimes構造允許循環一段固定的迭代次數。
實例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(dotimes (n 11)(print n)(prin1 (* n n)))
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
0011243941652563674986498110100
10.5 dolist 構造
dolist來構造允許迭代通過列表的每個元素。
實例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(dolist (n '(1 2 3 4 5 6 7 8 9))
(format t "~% Number: ~d Square: ~d" n (* n n)))
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
Number:1Square:1Number:2Square:4Number:3Square:9Number:4Square:16Number:5Square:25Number:6Square:36Number:7Square:49Number:8Square:64Number:9Square:81
10.6 退出塊
塊返回,從允許從正常情況下的任何錯誤的任何嵌套塊退出。塊功能允許創建一個包含零個或多個語句組成的機構命名塊。語法是:
(block block-name(......))
返回 - 從函數接受一個塊名稱和可選(默認為零)的返回值。
下面的例子演示了這一點:
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun demo-function(flag)(print'entering-outer-block)
(block outer-block
(print 'entering-inner-block)(print(block inner-block
(if flag
(return-from outer-block 3)(return-from inner-block 5))(print'This-wil--not-be-printed)))
(print 'left-inner-block)(print'leaving-outer-block)
t))
(demo-function t)
(terpri)
(demo-function nil)
當單擊Execute按鈕,或按下Ctrl+ E,LISP立即執行它,返回的結果是:
ENTERING-OUTER-BLOCK
ENTERING-INNER-BLOCK
ENTERING-OUTER-BLOCK
ENTERING-INNER-BLOCK
5
LEFT-INNER-BLOCK
LEAVING-OUTER-BLOCK
11 LISP - 函數
函數是一組一起執行任務的語句。可以把代碼放到單獨的函數。如何划分代碼之前不同的功能,但在邏輯上划分通常是這樣每個函數執行特定的任務。
11.1 LISP-函數定義
命名函數defun宏用於定義函數。該函數的defun宏需要三個參數:
-
函數名稱
-
函數的參數
-
函數的體
defun語法是:
(defun name (parameter-list)"Optional documentation string." body)
讓我們舉例說明概念,簡單的例子。
例子 1
讓我們編寫了一個名為averagenum,將打印四個數字的平均值的函數。我們將會把這些數字作為參數。創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun averagenum (n1 n2 n3 n4)(/(+ n1 n2 n3 n4)4))(write(averagenum 10203040))
當執行的代碼,它返回以下結果:
25
示例 2
讓我們定義和調用函數,將計算出的圓的面積,圓的半徑被指定作為參數的函數。創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun area-circle(rad)"Calculates area of a circle with given radius"(terpri)(format t "Radius: ~5f" rad)(format t "~%Area: ~10f"(*3.141592 rad rad)))(area-circle 10)
當執行的代碼,它返回以下結果:
請注意:
-
可以提供一個空的列表作為參數,這意味着函數沒有參數,該列表是空的,表示為()。
-
LISP還允許可選,多個和關鍵字參數。
-
文檔字符串描述了函數的目的。它與函數名相關聯,並且可以使用文檔函數來獲得。
-
函數的主體可以包含任意數量的Lisp表達式。
-
在主體內的最后一個表達式的值返回函數的值。
-
還可以使用返回 - 從特殊的運算符函數返回一個值。
我們在簡要討論上述概念。更多高級主題請自行搜索或等待下一版加入(編者注)
-
可選參數
-
其余部分參數
-
關鍵字參數
-
從函數返回的值
-
lambda函數
-
映射函數
11.2 可選參數
可以使用可選參數定義一個函數。要做到這一點,需要把符號與可選的可選參數的名稱之前。我們將只是顯示它接收的參數的函數。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun show-members (a b &optional c d)(write (list a b c d)))(show-members 123)(terpri)(show-members 'a 'b 'c 'd)(terpri)(show-members 'a 'b)(terpri)(show-members 1234)
當執行代碼,它返回以下結果:
(123 NIL)(A B C D)(A B NIL NIL)(1234)
請注意,參數c和d是在上面的例子中,是可選參數。
11.3 其余部分參數
有些函數需要采用可變數目的參數。例如,我們使用格式化函數需要兩個必需的參數,數據流和控制字符串。然而,該字符串后,它需要一個可變數目的取決於要顯示的字符串中的值的數目的參數。同樣,+ 函數,或 * 函數也可以采取一個可變數目的參數。可以提供這種可變數目的使用符號與其余參數。下面的例子說明了這個概念:
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun show-members (a b &rest values)(write (list a b values)))(show-members 123)(terpri)(show-members 'a 'b 'c 'd)(terpri)(show-members 'a 'b)(terpri)(show-members 1234)(terpri)(show-members 123456789)
當執行代碼,它返回以下結果:
(12(3))(A B (C D))(A B NIL)(12(34))(12(3456789))
11.4 關鍵字參數
關鍵字參數允許指定哪個值與特定的參數。它使用的是 &key 符號表示。當發送的值到該函數必須先於值 :parameter-name.下面的例子說明了這個概念。
例子
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun show-members (&key a b c d )(write (list a b c d)))(show-members :a 1:c 2:d 3)(terpri)(show-members :a 'p :b 'q :c 'r :d 's)(terpri)(show-members:a 'p :d 'q)(terpri)(show-members :a 1:b 2)
當執行代碼,它返回以下結果:
(1 NIL 23)(P Q R S)(P NIL NIL Q)(12 NIL NIL)
11.5 從函數返回的值
默認情況下,在LISP函數返回最后一個表達式作為返回值的值。下面的例子將證明這一點。
示例 1
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun add-all(a b c d)(+ a b c d))(setq sum (add-all 10203040))(write sum)(terpri)(write (add-all 23.456.734.910.0))
當執行代碼,它返回以下結果:
100125.0
但是,可以使用返回- 從特殊的操作符立即從函數返回任何值。
示例 2
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun myfunc (num)(return-from myfunc 10) num)(write (myfunc 20))
當執行代碼,它返回以下結果:
10
更改一點點代碼:
(defun myfunc (num)(return-from myfunc 10) write num)(write (myfunc 20))
它仍然返回:
10
11.6 lambda函數
有時,可能需要一個函數只在一個程序中的位置和功能是如此的微不足道,可能不給它一個名稱,也可以不喜歡它存儲在符號表中,寧可寫一個未命名或匿名函數。LISP允許編寫評估計算在程序中遇到的匿名函數。這些函數被稱為Lambda函數。可以使用lambda表達式創建這樣的功能。lambda表達式語法如下:
(lambda(parameters) body)
lambda形式可以不進行評估計算,它必須出現只有在LISP希望找到一個函數。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(write ((lambda(a b c x)(+(* a (* x x))(* b x) c))4293))
當執行代碼,它返回以下結果:
51
11.7 映射函數
映射函數是一組函數,可以連續地施加於元件中的一個或多個列表。應用這些功能列表的結果被放置在一個新的列表,而新的列表返回。
例如,mapcar函數處理的一個或多個列表連續元素。
在mapcar函數的第一個參數應該是一個函數,其余的參數是該函數的應用列表(次)。
函數的參數被施加到連續的元素,結果為一個新構造的列表。如果參數列表是不相等的長度,然后映射的過程停止在達到最短的列表的末尾。結果列表將元素作為最短輸入列表的數目相同。
示例 1
讓我們從一個簡單的例子和數字1 添加到每個列表的元素( 23 34 45 56 67 78 89)。
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(write (mapcar '1+ '(23344556677889)))
當執行代碼,它返回以下結果:
(24354657687990)
示例 2
讓我們寫這將多維數據集列表中的元素的函數。讓我們用一個lambda函數用於計算數字的立方。
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun cubeMylist(lst)(mapcar #'(lambda(x) (* x x x)) lst))(write (cubeMylist '(2 3 4 5 6 7 8 9)))
當執行代碼,它返回以下結果:
(82764125216343512729)
示例3
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(write (mapcar '+ '(135791113)'( 2 4 6 8)))
當執行代碼,它返回以下結果:
(371115)
12 LISP - 謂詞
謂詞是函數,測試其參數對一些特定的條件和返回nil,如果條件為假,或某些非nil值條件為true。
下表顯示了一些最常用的謂詞:
謂詞 |
描述 |
atom |
它接受一個參數,並返回t如果參數是一個原子或,否則nil。 |
equal |
它有兩個參數,並返回t,如果他們在結構上相同或否則nil |
eq |
它有兩個參數,並返回t,如果它們是相同的相同的對象,共享相同的內存位置或否則nil |
eql |
它有兩個參數,並返回t如果參數相等,或者如果他們是同一類型具有相同值的數字,或者如果他們是代表相同的字符的字符對象,否則返回nil |
evenp |
它接受一個數字參數,並返回t如果參數為偶數或否則為nil。 |
oddp |
它接受一個數字參數,並返回t如果參數為奇數或否則為nil。 |
zerop |
它接受一個數字參數,並返回t如果參數是零或否則為nil。 |
null |
它接受一個參數,並返回t,如果參數的計算結果為nil,否則返回nil。 |
listp |
它接受一個參數,並返回t如果參數的計算結果為一個列表,否則返回nil。 |
greaterp |
這需要一個或多個參數,並返回t,如果不是有一個單一的參數或參數是從左到右,或如果無先后,否則為nil。 |
lessp |
這需要一個或多個參數,並返回t,如果不是有一個單一的參數或參數是從左到右依次更小的向右,或否則為nil. |
numberp |
它接受一個參數,並返回t如果參數是一個數字,否則為nil。 |
symbolp |
它接受一個參數,並返回t如果參數是一個符號,否則返回nil。 |
integerp |
它接受一個參數,並返回t如果參數是一個整數,否則返回nil。 |
rationalp |
它接受一個參數,並返回t如果參數是有理數,無論是比例或數量,否則返回nil>。 |
floatp |
它接受一個參數,並返回t當參數則返回一個浮點數否則為nil。 |
realp |
它接受一個參數,並返回t如果參數是一個實數,否則返回nil。 |
complexp |
它接受一個參數,並返回t如果參數是一個復數,否則返回nil。 |
characterp |
它接受一個參數,並返回t如果參數是一個字符,否則返回nil。 |
stringp |
它接受一個參數,並返回t,如果參數是一個字符串對象,否則返回nil。 |
arrayp |
它接受一個參數,並返回t如果參數是一個數組對象,否則返回nil。 |
packagep |
它接受一個參數,並返回t,如果參數是一個包,否則返回nil。 |
示例 1
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(write (atom 'abcd))
(terpri)
(write (equal 'a 'b))
(terpri)
(write (evenp 10))
(terpri)
(write (evenp 7 ))
(terpri)
(write (oddp 7 ))
(terpri)
(write (zerop 0.0000000001))
(terpri)
(write (eq 3 3.0 ))
(terpri)
(write (equal 3 3.0 ))
(terpri)
(write (null nil ))
當執行以上代碼,它返回以下結果:
T
NIL
T
NIL
T
NIL
NIL
NIL
T
示例2
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun factorial (num)(cond ((zerop num)1)(t (* num (factorial (- num 1))))))(setq n 6)(format t "~% Factorial ~d is: ~d" n (factorial n))
當執行以上代碼,它返回以下結果:
Factorial6is:720
13 LISP - 數字
數字——通過LISP支持數類型是:
-
Integers
-
Ratios
-
Floating-yiibai numbers
-
Complex numbers
下圖顯示的數量和層次在LISP提供的各種數字數據類型:
13.1在LISP各種數值類型
下表描述了LISP語言提供的各種數字類型的數據:
Data type |
描述 |
fixnum |
這個數據類型表示的整數哪些不是太大,大多在范圍-215到215-1(它是依賴於機器) |
bignum |
這些都是非常大的數字有大小受限於內存中分配LISP量,它們不是長整數數字。 |
ratio |
表示兩個數中的分子/分母形式的比率。在/函數總是產生結果的比率,當其參數都是整數。 |
float |
它表示非整數。還有隨着精密四個浮點數據類型。 |
complex |
它表示復數,這是由#C表示。實部和虛部可以是兩者或者理性或浮點數。 |
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(write (/12))(terpri)(write (+(/ 1 2) (/34)))(terpri)(write (+#c( 1 2) #c( 3 -4)))
當執行以上代碼,它返回以下結果:
1/25/4#C(4 -2)
13.2 數字函數
下表描述了一些常用的數值函數:
Function |
描述 |
+, -, *, / |
各算術運算 |
sin, cos, tan, acos, asin, atan |
相應的三角函數 |
sinh, cosh, tanh, acosh, asinh, atanh |
相應的雙曲函數 |
exp |
冪函數,計算ex |
expt |
冪函數,需要基礎和冪兩者 |
sqrt |
它可以計算一個數的平方根 |
log |
對數函數。它的一個參數給出,則它計算其自然對數,否則將第二個參數被用作基數 |
conjugate |
它計算一個數的復共軛,如有任何實數,它返回數字本身 |
abs |
它返回一個數的絕對值(或幅度) |
gcd |
它可以計算給定數字的最大公約數 |
lcm |
它可以計算給定數的最小公倍數 |
isqrt |
它提供了最大的整數小於或等於一個給定的自然數的精確平方根。 |
floor, ceiling, truncate, round |
所有這些函數把一個數字的兩個參數,並返回商;地面返回的最大整數不大於比,天花板選擇較小的整數,它比比率越大,截斷選擇相同符號的整數的比值與最大的絕對值是小於的比值的絕對值,與圓公司選用一個整數,它是最接近比值 |
ffloor, fceiling, ftruncate, fround |
確實與上述相同,但返回的商作為一個浮點數 |
mod, rem |
返回除法運算的余數 |
float |
將實數轉換為浮點數 |
rational, rationalize |
將實數轉換為有理數 |
numerator, denominator |
返回有理數的各個部分 |
realpart, imagpart |
返回一個復數的實部和虛部 |
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(write (/4578))(terpri)(write (floor 4578))(terpri)(write (/345675))(terpri)(write (floor 345675))(terpri)(write (ceiling 345675))(terpri)(write(truncate 345675))(terpri)(write (round 345675))(terpri)(write (ffloor 345675))(terpri)(write (fceiling 345675))(terpri)(write (ftruncate345675))(terpri)(write (fround 345675))(terpri)(write (mod 345675))(terpri)(setq c (complex 67))(write c)(terpri)(write (complex 5-9))(terpri)(write (realpart c))(terpri)(write (imagpart c))
當執行以上代碼,它返回以下結果:
15/2601152/254647464646.047.046.046.06#C(6 7)#C(5 -9)67
14 LISP - 字符
在LISP中,字符被表示為字符類型的數據對象。可以記#前字符本身之前的字符的對象。例如,#一個表示字符a。空格和其它特殊字符可以通過#前面的字符的名稱前表示。例如,#空格代表空格字符。下面的例子演示了這一點:
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(write 'a)
(terpri)
(write #a)
(terpri)
(write-char #a)
(terpri)
(write-char 'a)
當執行以上代碼,它返回以下結果:
A
#a a
***- WRITE-CHAR: argument A isnot a character
14.1 特殊字符
Common Lisp允許使用以下特殊字符在代碼。他們被稱為半標准字符。
-
#Backspace
-
#Tab
-
#Linefeed
-
#Page
-
#Return
-
#Rubout
14.2 字符比較函數
數字比較函數和運算符,如,< 和 >上字符不工作。 Common Lisp提供了另外兩組的功能,在代碼中比較字符。一組是區分大小寫的,而另一個不區分大小寫。
下表提供的功能:
Case Sensitive Functions |
Case-insensitive Functions |
描述 |
char= |
char-equal |
檢查如果操作數的值都相等與否,如果是的話那么條件為真。 |
char/= |
char-not-equal |
檢查如果操作數的值都不同,或沒有,如果值不相等,則條件為真。 |
char< |
char-lessp |
檢查如果操作數的值單調遞減。 |
char> |
char-greaterp |
檢查如果操作數的值單調遞增。 |
char<= |
char-not-greaterp |
如有左操作數的值大於或等於下一個右操作數的值,如果是則條件為真檢查。 |
char>= |
char-not-lessp |
如有左操作數的值小於或等於其右操作數的值,如果是,則條件為真檢查。 |
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
;case-sensitive comparison
(write (char=#a #))(terpri)(write (char=#a #a))(terpri)(write (char=#a #A))(terpri);case-insensitive comparision
(write (char-equal #a #A))(terpri)(write (char-equal #a #))(terpri)(write (char-lessp #a # #c))(terpri)(write (char-greaterp #a # #c))
當執行以上代碼,它返回以下結果:
NIL
T
NIL
T
NIL
T
NIL
15 LISP - 數組
LISP允許使用make-array函數來定義一個或多個維數組。一個數組可以任意LISP對象存儲為它的元素。所有數組組成的連續的存儲單元。最低的地址對應於第一個元素和最高地址的最后一個元素。
數組的維數被稱為它的秩。
在LISP語言中,數組元素是由一個非負整數索引的順序指定。該序列的長度必須等於數組的秩。索引從0開始。
例如,要創建一個數組,10 - 單元格,命名為my-array,我們可以這樣寫:
(setf my-array (make-array '(10)))
aref 函數允許訪問該單元格的內容。它有兩個參數,數組名和索引值。
例如,要訪問的第十單元格的內容,可以這樣編寫:
(aref my-array 9)
示例1
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(write (setf my-array (make-array '(10))))
(terpri)
(setf (aref my-array 0) 25)
(setf (aref my-array 1) 23)
(setf (aref my-array 2) 45)
(setf (aref my-array 3) 10)
(setf (aref my-array 4) 20)
(setf (aref my-array 5) 17)
(setf (aref my-array 6) 25)
(setf (aref my-array 7) 19)
(setf (aref my-array 8) 67)
(setf (aref my-array 9) 30)
(write my-array)
當執行以上代碼,它返回以下結果:
#(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)#(25 23 45 10 20 17 25 19 67 30)
示例 2
讓我們創建一個3×3數組。
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setf x (make-array '(3 3)
:initial-contents '((012)(345)(678))))(write x)
當執行以上代碼,它返回以下結果:
#2A((0 1 2) (3 4 5) (6 7 8))
示例3
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a (make-array '(4 3)))
(dotimes (i 4)
(dotimes (j 3)
(setf (aref a i j) (list i 'x j '= (* i j)))))
(dotimes (i 4)
(dotimes (j 3)
(print (aref a i j))))
當執行以上代碼,它返回以下結果:
(0 X 0=0)
(0 X 1=0)
(0 X 2=0)
(1 X 0=0)
(1 X 1=1)
(1 X 2=2)
(2 X 0=0)
(2 X 1=2)
(2 X 2=4)
(3 X 0=0)
(3 X 1=3)
(3 X 2=6)
15.1 make-array函數完整的語法
make-array函數需要許多其他的參數。讓我們來看看這個函數的完整語法:
make-array dimensions :element-type :initial-element :initial-contents :adjustable :fill-yiibaier :displaced-to :displaced-index-offset
除了維度參數,所有其他參數都是關鍵字。下表提供的參數簡要說明。
參數 |
描述 |
dimensions |
它給該數組的大小。它是一個數字為一維數組,而對於多維數組列表。 |
:element-type |
它是類型說明符,默認值是T,即任何類型 |
:initial-element |
初始元素值。它將使一個數組的所有初始化為一個特定值的元素。 |
:initial-content |
初始內容作為對象。 |
:adjustable |
它有助於創造一個可調整大小(或可調)向量,其底層的內存可以調整大小。該參數是一個布爾值,表示數組是否可調與否,默認值是nil。 |
:fill-yiibaier |
它跟蹤實際存儲在一個可調整大小的矢量元素的數目 |
:displaced-to |
它有助於創造一個移位的數組或共享數組共享其內容與指定的數組。這兩個數組應該有相同的元素類型。位移到選項可能無法使用:displaced-to或:initial-contents選項。此參數默認為nil。 |
:displaced-index-offset |
它給出了索引偏移創建的共享數組。 |
示例4
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq myarray (make-array '(3 2 3)
:initial-contents
'(((a b c)(123))
((d e f)(456))
((g h i)(789))
)))
(setq array2 (make-array 4:displaced-to myarray
:displaced-index-offset 2))
(write myarray)(terpri)(write array2)
當執行以上代碼,它返回以下結果:
#3A(((A B C) (1 2 3)) ((D E F) (4 5 6)) ((G H I) (7 8 9)))#(C 1 2 3)
若對數組是二維的:
(setq myarray (make-array '(3 2 3)
:initial-contents
'(((a b c)(123))
((d e f)(456))
((g h i)(789))
)))
(setq array2 (make-array '(3 2) :displaced-to myarray
:displaced-index-offset 2))
(write myarray)
(terpri)
(write array2)
當執行以上代碼,它返回以下結果:
#3A(((A B C) (1 2 3)) ((D E F) (4 5 6)) ((G H I) (7 8 9)))#2A((C 1) (2 3) (D E))
讓我們改變流離指數偏移量5:
(setq myarray (make-array '(3 2 3)
:initial-contents
'(((a b c)(123))
((d e f)(456))
((g h i)(789))
)))
(setq array2 (make-array '(3 2) :displaced-to myarray
:displaced-index-offset 5))
(write myarray)
(terpri)
(write array2)
當執行以上代碼,它返回以下結果:
#3A(((A B C) (1 2 3)) ((D E F) (4 5 6)) ((G H I) (7 8 9)))#2A((3 D) (E F) (4 5))
示例5
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
;a one dimensional array with5 elements,
;initail value 5(write (make-array 5:initial-element 5))(terpri);two dimensional array,with initial element a
(write (make-array '(2 3) :initial-element 'a))(terpri);an array of capacity 14, but fill yiibaier 5,is5(write(length (make-array 14:fill-yiibaier 5)))(terpri);however its length is14(write (array-dimensions (make-array 14:fill-yiibaier 5)))(terpri); a bit array with all initial elements set to1(write(make-array 10:element-type 'bit :initial-element 1))
(terpri)
; a character array with all initial elements set to a
; is a string actually
(write(make-array 10 :element-type 'character :initial-element #a)) (terpri); a two dimensional array with initial values a
(setq myarray (make-array '(2 2) :initial-element 'a :adjustable t))(write myarray)(terpri);readjusting the array
(adjust-array myarray '(1 3) :initial-element 'b)
(write myarray)
當執行以上代碼,它返回以下結果:
#(5 5 5 5 5)#2A((A A A) (A A A))5(14)#*1111111111"aaaaaaaaaa"#2A((A A) (A A))#2A((A A B))
16 LISP - 符號
在LISP語言中,符號是表示數據對象和有趣的是它也是一個數據對象的名稱。是什么使得符號特殊之處在於他們有分別叫property list,或 plist.
16.1 屬性列表
LISP可以讓屬性,以符號分配。例如,我們有一個'人'的對象。希望這個'人'的對象有像姓名,性別,身高,體重,住址,職業等屬性是一些屬性名稱。一個屬性列表被實現為具有元素為偶數(可能為零)的列表。每對列表中的元素構成一個條目;第一個項目是指標,而第二個是該值。當創建一個符號,它的屬性列表最初是空的。屬性是使用於asetf形式得到建立。
例如,下面的語句使我們能夠分配屬性標題,作者和出版商,以及相應的值,命名(符號)'書'的對象。
示例 1
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
((write (setf (get 'books'title) '(Gone with the Wind)))
(terpri)
(write (setf (get 'books 'author) '(Margaret Michel)))
(terpri)
(write (setf (get 'books 'publisher) '(Warner Books)))
當執行代碼,它返回以下結果:
(GONE WITH THE WIND)
(MARGARET MICHEL)
(WARNER BOOKS)
各種屬性列表功能允許你指定的屬性以及檢索,替換或刪除一個符號的屬性。
get 函數返回符號的屬性列表對於一個給定的指標。它的語法如下:
get symbol indicator &optional default
get 函數查找指定的指標給定的符號的屬性列表,如果找到則返回相應的值;否則默認返回(或nil,如果沒有指定默認值)。
示例 2
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setf (get 'books 'title) '(Gone with the Wind))
(setf (get 'books 'author) '(Margaret Micheal))
(setf (get 'books 'publisher) '(Warner Books))
(write (get 'books 'title))
(terpri)
(write (get 'books 'author))
(terpri)
(write (get 'books 'publisher))
當執行代碼,它返回以下結果:
(GONE WITH THE WIND)
(MARGARET MICHEAL)
(WARNER BOOKS)
symbol-plist函數可以看到一個符號的所有屬性。
示例 3
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setf (get 'annie 'age) 43)
(setf (get 'annie 'job) 'accountant)
(setf (get 'annie 'sex) 'female)
(setf (get 'annie 'children) 3)
(terpri)
(write (symbol-plist 'annie))
當執行代碼,它返回以下結果:
(CHILDREN 3 SEX FEMALE JOB ACCOUNTANT AGE 43)
remprop函數從符號中刪除指定的屬性。
示例 4
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setf (get 'annie 'age) 43)
(setf (get 'annie 'job) 'accountant)
(setf (get 'annie 'sex) 'female)
(setf (get 'annie 'children) 3)
(terpri)
(write (symbol-plist 'annie))
(remprop 'annie 'age)
(terpri)
(write (symbol-plist 'annie))
當執行代碼,它返回以下結果:
(CHILDREN 3 SEX FEMALE JOB ACCOUNTANT AGE 43)
(CHILDREN 3 SEX FEMALE JOB ACCOUNTANT)
17 LISP - 向量
向量是一維數組,數組因此子類型。向量和列表統稱序列。因此,我們迄今為止所討論的所有序列的通用函數和數組函數,工作在向量上。
17.1 創建向量
向量函數使可以使用特定的值固定大小的向量。這需要任意數量的參數,並返回包含這些參數的向量。
示例1
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setf v1 (vector 12345))(setf v2 #(a b c d e))(setf v3 (vector 'p 'q 'r 's 't))
(write v1)
(terpri)
(write v2)
(terpri)
(write v3)
當執行代碼,它返回以下結果:
#(1 2 3 4 5)#(A B C D E)#(P Q R S T)
請注意,LISP使用#(...)語法為向量的文字符號。可以使用此#(...)語法來創建並包含在代碼中的文字向量。然而,這些是文字向量,所以修改它們沒有在LISP語言中定義。因此,對於編程,應始終使用向量函數,或者make-array函數來創建打算修改的向量。
make-array函數是比較通用的方式來創建一個矢量。可以訪問使用aref函數的矢量元素。
示例 2
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a (make-array 5:initial-element 0))(setq b (make-array 5:initial-element 2))(dotimes (i 5)(setf (aref a i) i))(write a)(terpri)(write b)(terpri)
當執行代碼,它返回以下結果:
#(0 1 2 3 4)#(2 2 2 2 2)
17.2 Fill 指針
make-array函數允許創建一個可調整大小的矢量。
函數fill-yiibaier參數跟蹤實際存儲在向量中的元素的數量。它的下一個位置,當添加元素的向量來填充的索引。
vector-push函數允許將元素添加到一個可調整大小的矢量的結束。它增加了填充指針加1。
vector-pop函數返回最近推條目,由1遞減填充指針。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq a (make-array 5:fill-yiibaier 0))(write a)(vector-push 'a a)
(vector-push 'b a)(vector-push 'c a)
(terpri)
(write a)
(terpri)
(vector-push 'd a)(vector-push 'e a)
;this will not be entered as the vector limit is 5
(vector-push 'f a)(write a)(terpri)(vector-pop a)(vector-pop a)(vector-pop a)(write a)
當執行代碼,它返回以下結果:
#()#(A B C)#(A B C D E)#(A B)
向量是序列,所有序列函數是適用於向量。請參考序列章節,對向量函數。
18 LISP - 集合
Common Lisp不提供的一組數據類型。然而,它提供的函數數量,它允許一組操作,以可以在列表上執行。可以添加,刪除和搜索列表中的項目,根據不同的標准。還可以執行像不同的集合運算:並,交和集合差。
18.2 實現LISP集合
集合像列表一樣,一般實現的利弊單元。由於這個原因,集合操作越來越少,高效的獲取大的集合。要明白這一點,一旦我們深入研究這個問題更深一點。
adjoin函數可建立一個集合。這需要一個條目和一個列表表示一組,並返回表示包含該項目,並在原設定的所有項目的集合列表。adjoin函數首先查找的條目給定列表中,一旦找到,將返回原來的名單;否則,創建一個新的cons單元,其car作為該目條,cdr指向原來的列表並返回這個新列表。該毗函數也需要:key 和 :test關鍵字參數。這些參數用於檢查該條目是否存在於原始列表。因為,adjoin函數不會修改原來的列表,讓列表本身的變化,必須指定由adjoin到原始列表返回的值或者可以使用宏pushnew將條目添加到集合。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
; creating myset as an empty list
(defparameter *myset*())(adjoin 1*myset*)(adjoin 2*myset*); adjoin didn't change the original set
;so it remains same
(write *myset*)
(terpri)
(setf *myset* (adjoin 1 *myset*))
(setf *myset* (adjoin 2 *myset*))
;now the original set is changed
(write *myset*)
(terpri)
;adding an existing value
(pushnew 2 *myset*)
;no duplicate allowed
(write *myset*)
(terpri)
;pushing a new value
(pushnew 3 *myset*)
(write *myset*)
(terpri)
當執行代碼,它返回以下結果:
NIL
(21)(21)(321)
18.3 檢查成員
函數的成員組允許檢查一個元素是否是一個集合成員。
以下是這些函數的語法:
member item list &key :test :test-not:key
member-if predicate list &key :key
member-if-not predicate list &key :key
這些函數搜索給定列表中一個給定的項,滿足了測試。它沒有這樣的項被找到,則函數返回nil。否則,將返回列表中的元素作為第一個元素的尾部。搜索是只在頂層進行。這些函數可作為謂詞。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(write (member 'zara '(ayan abdul zara riyan nuha)))(terpri)(write (member-if#'evenp '(3 7 2 5/3 'a)))(terpri)(write (member-if-not#'numberp '(3 7 2 5/3 'a 'b 'c)))
當執行代碼,它返回以下結果:
(ZARA RIYAN NUHA)(25/3'A)
('A 'B 'C)
18.4 集合聯合
聯合組功能能夠在作為參數提供給這些功能測試的基礎上,兩個列表進行集聯合。
以下是這些函數的語法:
union list1 list2 &key :test :test-not:key
nunion list1 list2 &key :test :test-not:key
union函數有兩個列表,並返回一個包含所有目前無論是在列表中的元素的新列表。如果有重復,則該成員只有一個副本被保存在返回的列表。union函數執行相同的操作,但可能會破壞參數列表。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq set1 (union'(a b c) '(c d e)))(setq set2 (union'(#(a b) #(5 6 7) #(f h))
'(#(567)#(a b) #(g h)) :test-not #'mismatch))
(setq set3 (union'(#(a b) #(5 6 7) #(f h))
'(#(567)#(a b) #(g h))))(write set1)(terpri)(write set2)(terpri)(write set3)
當執行代碼,它返回以下結果:
(A B C D E)(#(F H)#(5 6 7) #(A B) #(G H))(#(A B)#(5 6 7) #(F H) #(5 6 7) #(A B) #(G H))
請注意:
對於三個向量列表 :test-not #'不匹配的參數:如預期的union函數不會工作。這是因為,該名單是由cons單元元素,雖然值相同的外觀明顯,單元元素cdr部分不匹配,所以他們 並不完全一樣,以LISP解釋器/編譯器。這是原因;實現大集全不建議使用的列表。它工作正常的小集合上。
18.5 交集
函數的交點組允許作為參數提供給這些函數測試的基礎上,兩個列表進行交點。以下是這些函數的語法:
intersection list1 list2 &key :test :test-not:key
nintersection list1 list2 &key :test :test-not:key
這些函數需要兩個列表,並返回一個包含所有目前在這兩個參數列表中的元素的新列表。如果任一列表中的重復項,冗余項可能會或可能不會出現在結果中。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq set1 (intersection '(a b c) '(c d e)))(setq set2 (intersection '(#(a b) #(5 6 7) #(f h))
'(#(567)#(a b) #(g h)) :test-not #'mismatch))
(setq set3 (intersection '(#(a b) #(5 6 7) #(f h))
'(#(567)#(a b) #(g h))))(write set1)(terpri)(write set2)(terpri)(write set3)
當執行代碼,它返回以下結果:
(C)(#(A B)#(5 6 7)) NIL
intersection 函數是相交的破壞性版本,也就是說,它可能會破壞原始列表。
18.6 差集
set-difference組差集,可以在作為參數提供給這些功能測試的基礎上,兩個列表進行差集。以下是這些函數的語法:
set-difference list1 list2 &key :test :test-not:key
nset-difference list1 list2 &key :test :test-not:key
set-difference函數返回,不會出現在第二個列表的第一個列表的元素的列表。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq set1 (set-difference '(a b c) '(c d e)))(setq set2 (set-difference '(#(a b) #(5 6 7) #(f h))
'(#(567)#(a b) #(g h)) :test-not #'mismatch))(setq set3 (set-difference '(#(a b) #(5 6 7) #(f h))
'(#(567)#(a b) #(g h))))(write set1)(terpri)(write set2)(terpri)(write set3)
當執行代碼,它返回以下結果:
(A B)(#(F H))(#(A B)#(5 6 7) #(F H))
19 LISP - 樹
可以從cons單元構建樹的數據結構,如清單列表。為了實現樹形結構,則必須設計功能,將遍歷cons 單元,在特定的順序,例如,前序,順序和后序的二進制樹。
19.1 樹列表的列表
讓我們考慮由cons單元的樹狀結構,形成列出的清單如下:
((1 2) (3 4) (5 6)).
圖解,它可以表示為:
19.2 LISP樹的功能
雖然多數時候仍需要根據其它特殊需求編寫自己的樹的功能,LISP提供了一些樹的功能,您可以使用。
除了所有列表函數,以下是工作在樹結構函數:
函數 |
描述 |
copy-tree x &optional vecp |
它返回cons單元×樹的副本。它遞歸地拷貝兩款車和cdr方向。如果x不是一個cons單元,該函數只返回x不變。如果可選vecp參數為true,這個函數將向量(遞歸),以及cons單元。 |
tree-equal x y &key :test :test-not :key |
它比較兩棵樹的cons單元。如果x和y是兩個cons單元,他們的汽車和cdr是遞歸比較。如果x和y都不是一個cons單元,它們是由eql比較,或根據指定的測試。:key函數,如果指定,應用到這兩個目錄樹中的元素。 |
subst new old tree &key :test :test-not :key |
它可以代替出現給老項與新項,在樹,這是cons單元的一棵樹。 |
nsubst new old tree &key :test :test-not :key |
它的工作原理與subst相同,但它破壞了原來的樹。 |
sublis alist tree &key :test :test-not :key |
它的工作原理就像subst,只不過它采用的新舊對關聯表alist。樹(應用后:key函數,如果有的話)中的每個元素,與alist的車相比;如果它匹配,它被替換為相應的cdr。 |
nsublis alist tree &key :test :test-not :key |
它的工作原理與sublis相同,而是一個破壞性的版本。 |
示例1
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq lst (list '(1 2) '(34)'(5 6)))
(setq mylst (copy-list lst))
(setq tr (copy-tree lst))
(write lst)
(terpri)
(write mylst)
(terpri)
(write tr)
當執行代碼,它返回以下結果:
((12)(34)(56))((12)(34)(56))((12)(34)(56))
示例2
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq tr '((1 2 (3 4 5) ((7 8) (7 8 9)))))
(write tr)
(setq trs (subst 7 1 tr))
(terpri)
(write trs)
當執行代碼,它返回以下結果:
((12(345)((78)(789))))((72(345)((78)(789))))
19.3 建立自己的樹
讓我們嘗試建立自己的樹,使用LISP列表功能。
(1)首先,讓我們創建一個包含一些數據的新節點:
(defun make-tree (item)"it creates a new node with item."(cons (cons item nil)nil))
(2)接下來讓我們添加一個子節點插入到樹:它會采取兩種樹節點,並添加第二棵樹作為第一個的子樹。
(defun add-child (tree child)(setf (car tree)(append (car tree) child)) tree)
(3)接下來讓我們添加一個子節點插入到樹:這將需要一個樹節點,並返回該節點第一個子節點,或nil,如果這個節點沒有任何子節點。
(defun first-child (tree)(if(null tree)nil(cdr (car tree))))
(4)這個函數會返回一個給定節點的下一個同級節點:它需要一個樹節點作為參數,並返回一個指向下一個同級節點,或者為nil,如果該節點沒有任何。
(defun next-sibling (tree)(cdr tree))
(5)最后,我們需要一個函數來返回一個節點的信息:
(defun data (tree)(car (car tree)))
示例
本示例使用上述功能:
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun make-tree (item)"it creates a new node with item."(cons (cons item nil)nil))(defun first-child (tree)(if(null tree)nil(cdr (car tree))))(defunnext-sibling (tree)(cdr tree))(defun data (tree)(car (car tree)))(defun add-child (tree child)(setf (car tree)(append (car tree) child)) tree)
(setq tr '((1 2 (3 4 5) ((7 8) (7 8 9)))))
(setq mytree (make-tree 10))
(write (data mytree))
(terpri)
(write (first-child tr))
(terpri)
(setq newtree (add-child tr mytree))
(terpri)
(write newtree)
當執行代碼,它返回以下結果:
10(2(345)((78)(789)))
((12(345)((78)(789))(10)))
20 LISP - 哈希表
哈希表的數據結構表示是基於鍵哈希代碼進行組織鍵 - 值對的集合。它使用鍵來訪問集合中的元素。哈希表是用於需要使用一鍵訪問元素,可以找出一個有用的鍵值。在哈希表中每個項目都有一個鍵/值對。鍵是用於訪問該集合中的項。
20.1 LISP中創建哈希表
在Common Lisp中表是一種通用的集合。可以隨心所欲的使用對象作為一個鍵或索引。當在一個哈希表中存儲的值,設置鍵 - 值對,並將其存儲在該鍵。以后可以從哈希表中使用相同的key檢索值。每個鍵映射到一個單一的值,雖然可以在一鍵保存新值。哈希表,在LISP,可分為三種類型,基於這樣的鍵所不能compared - eq, eql或 equal。如果哈希表進行哈希處理的LISP對象然后將鑰匙與eq或eql比較。如果在樹結構中的哈希表散列,那么它會使用相等比較。
make-hash-table函數用於創建一個哈希表。此函數語法的是:
make-hash-table &key :test :size :rehash-size :rehash-threshold
那么:
-
key參數提供了鍵。
-
:test參數確定鍵如何比較- 它應該有一個三個值 #'eq, #'eql 或 #'equal或三個符號式之一,eq, eql, 或 equal。如果未指定,則使用eql。
-
:size參數設置哈希表的初始大小。這應該是一個大於零的整數。
-
:rehash-size 參數指定用多少提高哈希表的大小時已滿。這可以是一個大於零的整數,這是添加的項的數量,或者它可以是一個浮點數大於1,這是新的尺寸,以舊的大小的比率。該參數的默認值是實現相關。
-
:rehash-threshold參數指定的哈希表如何能充分得到之前,它必須成長。這可以是一個大於零的整數,並且小於 :rehash-size(在這種情況下,每當該表是生長其將被縮小),或者它可以是零和1之間的浮點數此默認值。參數是實現相關的。
-
也可以調用 make-hash-table函數的無參數形式。
20.2 正在從項和新增項到哈希表
gethash函數通過搜索其鍵檢索從哈希表中的項。如果沒有找到鍵,那么它返回nil。
它的語法如下:
gethash key hash-table &optional default
那么:
-
key:是相關聯的鍵
-
hash-table:是要被搜索的哈希表
-
default:要返回的值,如果沒有找到該入口,它是nil,如果不是指定的值。
-
gethash函數實際上返回兩個值,第二個是一個謂詞值,如果發現一個項則是true;如果被發現沒有項目返回false。
-
對於將項添加到哈希表中,可以使用setf函數及gethash函數。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq empList (make-hash-table))
(setf (gethash '001 empList) '(CharlieBrown))(setf (gethash '002 empList) '(FreddieSeal))
(write (gethash '001 empList))
(terpri)
(write (gethash '002 empList))
當執行代碼,它返回以下結果:
(CHARLIE BROWN)(FREDDIE SEAL)
20.3 刪除條目
remhash函數刪除在哈希表中的特定鍵的任何項。如果是一個謂詞,那么它為true,如果沒有有一個項則為false。
其函數語法:
remhash key hash-table
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq empList (make-hash-table))
(setf (gethash '001 empList) '(CharlieBrown))(setf (gethash '002 empList) '(FreddieSeal))
(setf (gethash '003 empList) '(MarkMongoose))
(write (gethash '001 empList))
(terpri)
(write (gethash '002 empList))
(terpri)(write (gethash '003 empList))
(remhash '003 empList)(terpri)(write (gethash '003 empList))
當執行代碼,它返回以下結果:
(CHARLIE BROWN)(FREDDIE SEAL)(MARK MONGOOSE) NIL
20.4 maphash函數
maphash函數允許在每個鍵 - 值對應用一個指定的函數在一個哈希表。
它有兩個參數 - 函數和哈希表,並調用該函數一次為每個鍵/值對的哈希表中。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(setq empList (make-hash-table))
(setf (gethash '001 empList) '(CharlieBrown))(setf (gethash '002 empList) '(FreddieSeal))
(setf (gethash '003 empList) '(MarkMongoose))
(maphash #'(lambda (k v) (format t "~a => ~a~%" k v)) empList)
當執行代碼,它返回以下結果:
3=>(MARK MONGOOSE)2=>(FREDDIE SEAL)1=>(CHARLIE BROWN)
21 LISP - 輸入和輸出
Common Lisp提供了大量的輸入輸出功能。我們已經使用的格式功能,打印輸出功能。在本節中,我們將探討一些在LISP提供了最常用的輸入輸出功能。
21.1 輸入函數
下表提供了LISP的最常用的輸入功能:
SL No. |
函數和說明 |
1 |
read &optional input-stream eof-error-p eof-value recursive-p 它讀取一個Lisp對象從輸入流的打印形式,建立相應的Lisp對象,並返回該對象。 |
2 |
read-preserving-whitespace &optional in-stream eof-error-p eof-value recursive-p 這是用在一些特殊情況下,最好是確定擴展令牌正好是字符結束。 |
3 |
read-line &optional input-stream eof-error-p eof-value recursive-p 它讀取一個文本行由換行符終止。 |
4 |
read-char &optional input-stream eof-error-p eof-value recursive-p 這需要一個字符從輸入流並將其作為一個字符的對象。 |
5 |
unread-char character &optional input-stream 它把最近從輸入流中讀取的字符,到輸入數據流的前部。 |
6 |
peek-char &optional peek-type input-stream eof-error-p eof-value recursive-p 它返回的下一個字符被從輸入流中讀取,而無需實際從輸入流中除去它。 |
7 |
listen &optional input-stream 謂詞監聽為true如果有立即從輸入流中的字符,如果不是則為false。 |
8 |
read-char-no-hang &optional input-stream eof-error-p eof-value recursive-p 它類似於read-char字符,但是如果它沒有得到一個字符,它不會等待一個字符,但立即返回為nil。 |
9 |
clear-input &optional input-stream 它清除與輸入流關聯的所有緩沖的輸入。 |
10 |
read-from-string string &optional eof-error-p eof-value &key :start :end :preserve-whitespace 它采用字符串的字符,並相繼建立一個LISP的對象,並返回該對象。它也返回第一個字符的索引無法讀取字符串或字符串(或長度+1)的長度,視具體情況而定。 |
11 |
parse-integer string &key :start :end :radix :junk-allowed 它會檢查字符串的子串被分隔:start 和:end(默認為字符串的開頭和結尾)。它會跳過空白字符,然后嘗試解析一個整數。 |
12 |
read-byte binary-input-stream &optional eof-error-p eof-value 它讀取1字節的二進制輸入流並將其返回一個整數的形式。 |
21.2 讀取鍵盤的輸入
read 函數用於從鍵盤輸入。也可以不帶任何參數。
例如,考慮代碼片段:
(write (+15.0(read)))
假設用戶輸入10.2 來自stdin 輸入,它返回,
25.2
read 函數從輸入流中讀取字符,並通過解析為Lisp對象的表示解釋它們。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
; the functionAreaOfCircle; calculates area of a circle
;when the radius is input from keyboard
(defun AreaOfCircle()(terpri)(princ "Enter Radius: ")(setq radius (read))(setq area (*3.1416 radius radius))(princ "Area: ")(write area))(AreaOfCircle)
當執行代碼,它返回以下結果:
EnterRadius:5(STDIN Input)Area:78.53999
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(with-input-from-string(stream "Welcome to Tutorials Yiibai!")(print(read-char stream))(print(read-char stream))(print(read-char stream))(print(read-char stream))(print(read-char stream))(print(read-char stream))(print(read-char stream))(print(read-char stream))(print(read-char stream))(print(read-char stream))(print(peek-charnil stream nil'the-end))
(values))
當執行代碼,它返回以下結果:
#W #e #l #c #o #m #e #Space # #o #Space
21.3 輸出功能
在LISP所有的輸出函數都有一個稱為輸出流可選參數,其輸出傳送。如果沒有提及或nil,輸出流默認為變量*標准輸出*的值。
下表提供了LISP的最常用的輸出函數:
SL No. |
函數和說明 |
1 |
write object &key :stream :escape :radix :base :circle :pretty :level :length :case :gensym :array write object &key :stream :escape :radix :base :circle :pretty :level :length :case :gensym :array :readably :right-margin :miser-width :lines :pprint-dispatch 既寫對象通過指定的輸出流:stream,默認為標准輸出*值*。其他值默認為打印設置相應的全局變量。 |
2 |
prin1object &optional output-stream print object &optional output-stream pprint object &optional output-stream princ object &optional output-stream 所有這些函數對象的打印形式輸出到輸出流。但是,下面的不同之處有: prin1 返回對象作為其值。 print 打印與前一個換行符的目標和后跟一個空格。它返回的對象。 pprint 就像印刷不同之處在於省略了結尾間隔。 princ 就像prin1除了輸出沒有轉義字符。 |
3 |
write-to-string object &key :escape :radix :base :circle :pretty :level :length :case :gensym :array write-to-stringobject &key :escape :radix :base :circle :pretty :level :length :case :gensym :array :readably :right-margin :miser-width :lines :pprint-dispatch prin1-to-string object princ-to-string object 該對象被有效地打印和輸出的字符被轉成一個字符串,並將該字符串返回。 |
4 |
write-char character &optional output-stream 它輸出的字符輸出流,並返回字符。 |
5 |
write-string string &optional output-stream &key :start :end 它寫入字符串的指定子字符串的字符輸出流。 |
6 |
write-line string &optional output-stream &key :start :end 它的工作原理與write-string的方式相同,但是之后輸出一個換行符。 |
7 |
terpri &optional output-stream 它輸出一個換行符到output-stream。 |
8 |
fresh-line &optional output-stream 它只輸出一個換行,如果流不是已經在一行的開始。 |
9 |
finish-output &optional output-stream force-output &optional output-stream clear-output &optional output-stream 函數finish-output嘗試確保發送到輸出流的所有輸出已達到其目標,然后才返回nil。 函數force-output發起的任何內部緩沖區清空,但返回nil,而無需等待完成或確認。 函數clear-output 嘗試中止,以便使盡可能少的輸出繼續到目標中的任何出色的輸出操作。 |
10 |
write-byte integer binary-output-stream 它寫入一個字節,整數的值。 |
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
;this program inputs a numbers and doubles it
(defun DoubleNumber()(terpri)(princ "Enter Number : ")(setq n1 (read))(setq doubled (*2.0 n1))(princ "The Number: ")(write n1)(terpri)(princ"The Number Doubled: ")(write doubled))(DoubleNumber)
當執行代碼,它返回以下結果:
EnterNumber:3456.78(STDIN Input)TheNumber:3456.78TheNumberDoubled:6913.56
21.4 格式化輸出
format函數是用於生產很好的格式化文本。它的語法如下:
format destination control-string&rest arguments
那么,
-
destination 是一個標准輸出
-
control-string 持有的字符要被輸出和打印指令。
-
format directive 由符號(〜)的,用逗號,可選的冒號(:)和符號(@)修飾符和一個字符指明了哪些指令是分開的可選前綴參數。
-
前綴參數一般都是整數,記載為可選符號十進制數。
下表提供了常用的指令的簡要說明:
指令 |
描述 |
~A |
后跟ASCII碼參數 |
~S |
后跟S-表達式 |
~D |
為十進制參數 |
~B |
用於二進制參數 |
~O |
用於八進制參數 |
~X |
用於十六進制參數 |
~C |
用於字符參數 |
~F |
用於固定格式的浮點參數。 |
~E |
指數浮點參數 |
~$ |
美元和浮點參數。 |
~% |
被打印新的一行 |
~* |
被忽略的下一個參數 |
~? |
間接。下一個參數必須是一個字符串,一個接一個列表。 |
示例
讓我們重寫程序計算圓的面積:
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun AreaOfCircle()(terpri)(princ "Enter Radius: ")(setq radius (read))(setq area (*3.1416 radius radius))(format t "Radius: = ~F~% Area = ~F"radius area))(AreaOfCircle)
當執行代碼,它返回以下結果:
EnterRadius:10.234(STDIN Input)Radius:=10.234Area=329.03473
22 LISP - 文件I/O
我們已經了解如何使用標准的輸入和輸出是由Common Lisp處理的參數。所有這些函數讀取和寫入文本文件和二進制文件。唯一不同的是在這種情況下,我們使用流不是標准輸入或輸出,但對於寫入或讀取文件的特定目的的流創建的。在本章中,我們將看到的LISP如何創建,打開,關閉文本或二進制文件的數據存儲。文件代表一個字節序列,如果它是一個文本文件或二進制文件。本章將引導完成重要的功能/宏的文件管理。
22.1 打開文件
可以使用open 函數來創建一個新文件或打開一個現有的文件。這是最基本的功能為打開一個文件。然而,with-open-file通常更方便,更常用,因為我們將在本節后面看。當一個文件被打開,一個流對象被創建來代表它在LISP環境。流上的所有操作基本上等同於操作上的文件。
open 函數語法是:
open filename &key :direction :element-type :if-exists :if-does-not-exist :external-format
那么,
-
filename 參數是要打開或創建的文件的名稱。
-
keyword 參數指定的數據流和錯誤處理方式的類型。
-
:direction keyword 指定的流是否應處理的輸入,輸出,或兩者兼而有之,它采用下列值:
-
:input - 用於輸入流(默認值)
-
:output - 輸出流
-
:io - 雙向流
-
:probe - 只是檢查一個文件是否存在;該流被打開,然后關閉。
-
:element-type 指定事務單元的流類型。
-
:if-exists參數指定要采取的操作,如果 :direction 是 :output or :io和指定的名稱已存在的文件。如果方向是direction 為 :input 或 :probe,則忽略此參數。它采用下列值:
-
:error - 它發出錯誤信號。
-
:new-version - 它將創建一個具有相同名稱但大版本號的新文件。
-
:rename - 它重命名現有的文件。
-
:rename-and-delete - 它重命名現有的文件,然后將其刪除。
-
:append - 它追加到現有文件。
-
:supersede - 它將取代現有的文件。
-
nil - 它不創建一個文件甚至流只是返回零表示失敗。
-
:if-does-not-exist 參數指定,如果指定名稱的文件已經不存在應采取的操作。它采用下列值:
-
:error - 它發出錯誤信號。
-
:create - 它創建具有指定名稱的空文件,然后使用它。
-
nil - 它不創建一個文件或流,而是簡單地返回nil表示失敗。
-
:external-format 參數指定用於表示文件的字符的實施認可制度
例如,可以打開一個名為myfile.txt的存儲在/ tmp文件夾的文件:
(open "/tmp/myfile.txt")
22.2 寫入和讀取文件
with-open-file允許讀取或寫入到一個文件中,用與讀/寫事務相關聯的流變量。一旦這項工作完成后,它會自動關閉文件。它使用極為方便。
它的語法如下:
with-open-file (stream filename {options}*)
{declaration}* {form}*
-
filename 是要打開的文件的名稱;它可以是一個字符串,一個路徑,或一個流。
-
options 就像keyword 參數給函數打開的一樣。
示例 1
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(with-open-file (stream "/tmp/myfile.txt" :direction :output)
(format stream "Welcome to Tutorials Yiibai!")
(terpri stream)
(format stream "This is a tutorials database")
(terpri stream)
(format stream "Submit your Tutorials, White Papers and Articles into our Tutorials Directory."))
請注意,在前面的章節,如,terpri和format討論的所有輸入輸出函數正在編寫到創建的文件。當執行代碼,它不返回任何東西;然而,數據被寫入到該文件中。:direction :output關鍵字可以做到這一點。不過,我們可以使用read-line函數從這個文件中讀取。
實例 2
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(let ((in (open "/tmp/myfile.txt" :if-does-not-exist nil)))
(when in
(loop for line = (read-line in nil)
while line do (format t "~a~%" line))
(close in)))
當執行代碼,它返回以下結果:
Welcome to Tutorials Yiibai!
This is a tutorials database
Submit your Tutorials, White Papers and Articles into our Tutorials Directory.
22.3 關閉文件
close函數關閉一個流。
23 LISP – 結構
結構是用戶定義的數據類型,它讓用戶可以合並不同種類的數據項。結構被用於表示記錄。假設要跟蹤圖書館中的書籍。可能希望跟蹤了解每本書的以下屬性:
-
標題 - Title
-
作者 - Author
-
科目 - Subject
-
書籍編號 - Book ID
23.1 定義一個結構
LISP的defstruct宏允許定義一個抽象的記錄結構。defstruct語句定義了一個新的數據類型,項目結構中不止一個成員。討論defstruct宏的格式,編寫本書的結構的定義。可以定義本書的結構為:
(defstruct book
title
author
subject
book-id
)
請注意:
上述聲明創建一個本書結構有四個命名組件。因此,創建的每一個本書將是這個結構的對象。它定義了一個名為book-title,book-subject,book-book-id的書籍,這將需要一個參數,書的結構,並且將返回的字段標題,作者,主題和本書的book-book-id對象。這些函數被稱為接入功能。符號書成為一個數據類型,它可以使用typep謂詞檢查。也將命名為book-p隱函數,這是一個謂詞,將為true,如果它的參數是本、書,則返回false。另一個名為make-book 隱函數將被創建,這是一種構造方法,其中,當被調用時,將創建一個數據結構具有四個組件,適於與所述接入功能的使用。
-
#S語法指的是一個結構,可以用它來讀取或打印一本書的實例
-
copy-book書本參數還定義了隱函數。這需要書的對象,並創建另一個書的對象,這是第一個副本。調用此函數復印機功能。
-
可以使用setf改變書籍的組成結構
例如
(setf (book-book-id book3)100)
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defstruct book
title
author
subject
book-id
)( setq book1 (make-book :title "C Programming":author "Nuha Ali"
:subject "C-Programming Tutorial":book-id "478"))( setq book2 (make-book :title "Telecom Billing":author "Zara Ali"
:subject "C-Programming Tutorial":book-id "501"))
(write book1)(terpri)(write book2)(setq book3( copy-book book1))(setf (book-book-id book3)100)
(terpri)(write book3)
當執行代碼,它返回以下結果:
#S(BOOK :TITLE "C Programming" :AUTHOR "Nuha Ali" :SUBJECT "C-Programming Tutorial" :BOOK-ID "478")#S(BOOK :TITLE "Telecom Billing" :AUTHOR "Zara Ali" :SUBJECT "C-Programming Tutorial" :BOOK-ID "501")#S(BOOK :TITLE "C Programming" :AUTHOR "Nuha Ali" :SUBJECT "C-Programming Tutorial" :BOOK-ID
24 LISP - 包
在編程語言的通用術語中,包是專為提供一種方法來保持一組名從另一個分開的。在一個包中聲明的符號將不會與另一個聲明的相同的符號相沖突。這樣的包減少獨立的代碼模塊之間的命名沖突。LISP讀取器會維護所有已發現的符號表。當它找到一個新的字符序列,它在符號表中創建一個新的符號和存儲。這個表被稱為一個包。
當前包是由特殊變量*package*引用。
有兩個預定義的包在LISP:
common-lisp - it包含了所有已定義的函數和變量符號。
common-lisp-user - it 采用了common-lisp包和其他所有的包與編輯和調試工具;它簡稱為cl-user
24.1 LISP包函數
下表提供了用於創建,使用和操作封裝最常用的功能:
SL No |
函數和說明 |
1 |
make-packagepackage-name &key :nicknames :use 它創建並使用指定的包名返回一個新的包。 |
2 |
in-package package-name &key :nicknames :use 使得當前的程序包。 |
3 |
in-package name 這個宏的原因*package*設置為名為name的包,它必須是一個符號或字符串。 |
4 |
find-package name 它搜索一個包。返回包的名稱或昵稱;如果沒有這樣的程序包是否存在,find-package返回nil |
5 |
rename-packagepackage new-name &optional new-nicknames 它重命名一個包。 |
6 |
list-all-packages 該函數返回一個當前存在於Lisp語言系統中的所有包的列表。 |
7 |
delete-package package 它會刪除一個包 |
24.2 創建一個LISP包
defpackage函數用於創建一個用戶定義的程序包。它的語法如下:
defpackage :package-name
(:use:common-lisp ...)(:export:symbol1 :symbol2 ...))
那么,
-
package-name是包的名稱。
-
:use 關鍵字指定此包需要的包,即定義在此包中使用包的代碼函數。
-
:export 關鍵字指定為外部在這個包中的符號。
-
make-package函數也可用於創建一個包。其語法函數:
-
make-packagepackage-name &key :nicknames :use
-
參數和關鍵字具有相同的含義。
24.3 使用包
一旦創建了一個包,則可以使用代碼在這個包中,使其成為當前包。in-package宏使得環境中的當前程序包。
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(make-package:tom)(make-package:dick)(make-package:harry)(in-package tom)(defun hello ()
(write-line "Hello! This is Tom's Tutorials Yiibai"))(hello)(in-package dick)(defun hello ()
(write-line "Hello! This is Dick's Tutorials Yiibai"))(hello)(in-package harry)(defun hello ()
(write-line "Hello! This is Harry's Tutorials Yiibai"))(hello)(in-package tom)(hello)(in-package dick)(hello)(in-package harry)(hello)
當執行代碼,它返回以下結果:
Hello!ThisisTom's Tutorials Yiibai
Hello! This is Dick's TutorialsYiibaiHello!ThisisHarry's Tutorials Yiibai
24.4 刪除包
delete-package宏允許刪除一個包。下面的例子演示了這一點:
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(make-package:tom)(make-package:dick)(make-package:harry)(in-package tom)(defun hello ()
(write-line "Hello! This is Tom's Tutorials Yiibai"))(in-package dick)(defun hello ()
(write-line "Hello! This is Dick's Tutorials Yiibai"))(in-package harry)(defun hello ()
(write-line "Hello! This is Harry's Tutorials Yiibai"))(in-package tom)(hello)(in-package dick)(hello)(in-package harry)(hello)(delete-package tom)(in-package tom)(hello)
當執行代碼,它返回以下結果:
Hello!ThisisTom's Tutorials Yiibai
Hello! This is Dick's TutorialsYiibaiHello!ThisisHarry's Tutorials Yiibai
*** - EVAL: variable TOM has no value
25 LISP - 錯誤處理
25.1 面向對象的錯誤處理- LISP條件系統
在Common Lisp的術語中,異常被稱為條件。事實上,條件比在傳統編程語言的異常更為普遍,因為一個條件表示任何事件,錯誤與否,這可能會影響各級函數調用堆棧。在LISP狀態處理機制,處理的條件是用來警告信號(例如通過打印一個警告),而在調用堆棧的上層代碼可以繼續工作,這樣的情況下以這樣一種方式。
條件處理系統中LISP有三個部分:
-
信號的條件
-
處理條件
-
重啟進程
25.2 處理一個條件
讓我們處理由除零所產生的條件的例子,在這里解釋這些概念。需要處理的條件如下步驟:
定義條件 - “條件是一個對象,它的類表示條件的一般性質,其實例數據進行有關的特殊情況,導致被示意條件的細節信息”。
定義條件的宏用於定義一個條件,它具有以下語法:
(define-condition condition-name (error)((text :initarg :text :reader text)))
:initargs參數,新的條件對象與MAKE-CONDITION 宏,它初始化的基礎上,新的條件下的插槽中創建的。
在我們的例子中,下面的代碼定義的條件:
(define-condition on-division-by-zero (error)((message :initarg :message :reader message)))
25.3 編寫處理程序
條件處理程序是用於處理信號的條件在其上的代碼。它一般寫在調用該函數出問題的上級功能之一。當條件信號發生時,該信號轉導機制中搜索基於所述條件的類合適的處理器。
每個處理程序包括:
-
類型說明符,它指示條件,它可以處理的類型
-
一個函數,它接受一個參數條件
-
當條件獲得信號,該信號機制發現最近建立的處理程序與條件類型兼容,並調用它的函數。
宏處理程序的情況建立了一個條件處理程序。一個處理程序的 handler-case 形式:
(handler-case expression
error-clause*)
那么,每個error從句的形式為:
condition-type ([var]) code)
25.4 重新啟動階段
這是真正從錯誤的代碼中恢復程序,條件處理程序可以通過調用一個適當的重啟處理的條件。重啟代碼一般是放置在中層或底層函數和條件處理程序被放置到應用程序的上層。
handler-bind宏允許提供一個重啟功能,並允許繼續在較低級的功能,無需解除函數的調用堆棧。換句話說,控制流將仍然處於較低水平的功能。
handler-bind的基本形式如下:
(handler-bind (binding*) form*)
其中每個綁定如以下列表:
-
條件類型
-
一個參數的處理函數
-
invoke-restart宏查找並調用具有指定名稱作為參數最近綁定重啟功能。
-
可以有多個重新啟動。
示例
在這個例子中,我們演示了上述概念通過寫一個名為划分功能函數,則會創建錯誤條件,如果除數參數為零。我們有三個匿名的功能,提供三種方式來出它 – 通過返回一個值1,通過發送一個除數2和重新計算,或通過返回1。
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(define-condition on-division-by-zero (error)((message :initarg :message :reader message)))
(defun handle-infinity ()(restart-case(let((result 0))(setf result (division-function100))(format t "Value: ~a~%" result))(just-continue()nil)))
(defun division-function(value1 value2)(restart-case(if(/= value2 0)(/ value1 value2)(error 'on-division-by-zero :message "denominator is zero"))
(return-zero () 0)
(return-value (r) r)
(recalc-using (d) (division-function value1 d))))
(defun high-level-code ()
(handler-bind
((on-division-by-zero
#'(lambda(c)(format t "error signaled: ~a~%"(message c))(invoke-restart 'return-zero)))
(handle-infinity))))
(handler-bind
((on-division-by-zero
#'(lambda(c)(format t "error signaled: ~a~%"(message c))(invoke-restart 'return-value 1))))
(handle-infinity))
(handler-bind
((on-division-by-zero
#'(lambda(c)(format t "error signaled: ~a~%"(message c))(invoke-restart 'recalc-using 2))))
(handle-infinity))
(handler-bind
((on-division-by-zero
#'(lambda(c)(format t "error signaled: ~a~%"(message c))(invoke-restart 'just-continue))))
(handle-infinity))
(format t "Done."))
當執行代碼,它返回以下結果:
error signaled: denominator is zero
Value:1 error signaled: denominator is zero
Value:5 error signaled: denominator is zero
Done.
除了“系統狀態”,如上文所討論,普通的LISP還提供了各種功能,其可被稱為信令錯誤。當信號實現相關處理錯誤。
25.5 LISP的錯誤信號功能
下表提供了常用功能的信令警告,休息,非致命和致命的錯誤。
用戶程序指定一個錯誤信息(字符串)。該函數處理這個消息,並且可能/可能不會顯示給用戶。錯誤信息應該通過應用的格式化功能進行構造,不應該在開頭或結尾包含一個換行符,也無需指明錯誤,如LISP系統將根據其喜好的樣式利用這些服務。
SL No. |
函數和說明 |
1 |
errorformat-string &rest args 它標志着一個致命的錯誤。這是不可能從這種錯誤的繼續;這樣的錯誤將永遠不會返回到其調用者。 |
2 |
cerrorcontinue-format-string error-format-string &rest args 它發出錯誤信號,並進入調試器。但是,它允許程序從調試器解決錯誤之后繼續。 |
3 |
warnformat-string &rest args 它打印一條錯誤消息,但一般不會進入調試 |
4 |
break &optional format-string &rest args 它打印的消息,並直接進入調試器,而不允許攔截由編程錯誤處理設施的任何可能性 |
示例
在這個例子中,階乘函數計算一個數階乘;但是,如果參數為負,它拋出一個錯誤條件。
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defun factorial (x)(cond ((or(not(typep x 'integer)) (minusp x))
(error "~S is a negative number." x))
((zerop x) 1)
(t (* x (factorial (- x 1))))))
(write(factorial 5))
(terpri)
(write(factorial -1))
當執行代碼,它返回以下結果:
120***--1is a negative number.
26 LISP - 對象系統(CLOS)
Common Lisp通過幾十年的面向對象編程的推進。但是,面向對象被並入是在它最后一階段。
26.1 類的定義
defclass宏允許創建用戶定義的類。它建立了一個類作為數據類型。它的語法如下:
(DEFCLASS class-name (superclass-name*)(slot-description*)class-option*)
-
插槽是存儲數據變量或字段。
-
slot-description形式(插槽名稱插槽選項*),其中每個選項是一個關鍵字后跟一個名字,表達式和其他選項。最常用的槽選項是:
-
:accessor 函數名稱
-
:initform 表達式
-
:initarg 符號
例如,讓我們定義一個Box類,有三個槽的長度,廣度和高度。
(defclass Box()
(length
breadth
height))
26.2 提供訪問和讀/寫控制到一個插槽
除非有插槽可以訪問,讀取或寫入的值,類是好看不中用。
當定義一個類可以為每個插槽指定訪問。例如,把我們的Box類:
(defclass Box()((length :accessor length)(breadth :accessor breadth)(height :accessor height)))
也可以讀取和寫入一個插槽指定單獨的訪問器的名稱。
(defclass Box()((length :reader get-length :writer set-length)(breadth :reader get-breadth :writer set-breadth)(height :reader get-height :writerset-height)))
26.3 類創建實例
通用函數make-instance創建並返回一個類的新實例。
它的語法如下:
(make-instance class{initarg value}*)
示例
讓我們創建一個Box類,有三個插槽,長度,寬度和高度。我們將使用三個插槽存取到這些字段設置的值。
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defclass box ()((length :accessor box-length)(breadth :accessor box-breadth)(height :accessor box-height)))(setf item (make-instance 'box))
(setf (box-length item) 10)
(setf (box-breadth item) 10)
(setf (box-height item) 5)
(format t "Length of the Box is ~d~%" (box-length item))
(format t "Breadth of the Box is ~d~%" (box-breadth item))
(format t "Height of the Box is ~d~%" (box-height item))
當執行代碼,它返回以下結果:
Length of the Boxis10Breadth of the Boxis10Height of the Boxis5
26.4 定義一個類的方法
defmethod宏允許在類中定義一個方法。下面的示例擴展Box類包含一個方法名為volume。
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defclass box ()((length :accessor box-length)(breadth :accessor box-breadth)(height :accessor box-height)(volume :reader volume)))
; method calculating volume
(defmethod volume ((object box))(*(box-length object)(box-breadth object)(box-height object)))
;setting the values
(setf item (make-instance 'box))
(setf (box-length item) 10)
(setf (box-breadth item) 10)
(setf (box-height item) 5)
; displaying values
(format t "Length of the Box is ~d~%" (box-length item))
(format t "Breadth of the Box is ~d~%" (box-breadth item))
(format t "Height of the Box is ~d~%" (box-height item))
(format t "Volume of the Box is ~d~%" (volume item))
當執行代碼,它返回以下結果:
Length of the Boxis10Breadth of the Boxis10Height of the Boxis5Volume of the Boxis500
26.5 繼承
LISP允許在另一個對象來定義一個對象。這就是所謂的繼承。可以通過添加功能,新的或不同的創建派生類。派生類繼承了父類的功能。
下面的例子說明了這一點:
示例
創建一個名為main.lisp一個新的源代碼文件,並在其中輸入如下代碼:
(defclass box ()((length :accessor box-length)(breadth :accessor box-breadth)(height :accessor box-height)(volume :reader volume))); method calculating volume
(defmethod volume ((object box))(*(box-length object)(box-breadth object)(box-height object)))
;wooden-box class inherits the box class
(defclass wooden-box (box)((price :accessor box-price)))
;setting the values
(setf item (make-instance 'wooden-box))
(setf (box-length item) 10)
(setf (box-breadth item) 10)
(setf (box-height item) 5)
(setf (box-price item) 1000)
; displaying values
(format t "Length of the Wooden Box is ~d~%" (box-length item))
(format t "Breadth of the Wooden Box is ~d~%" (box-breadth item))
(format t "Height of the Wooden Box is ~d~%" (box-height item))
(format t "Volume of the Wooden Box is ~d~%" (volume item))
(format t "Price of the Wooden Box is ~d~%" (box-price item))
當執行代碼,它返回以下結果:
Length of the WoodenBoxis10Breadth of the WoodenBoxis10Height of the WoodenBoxis5Volume of the WoodenBoxis500Price of theWoodenBoxis1000
附錄:為什么我喜歡Lisp語言
Lisp是一種很老的語言。非常的老。Lisp有很多變種,但如今已沒有一種語言叫Lisp的了。事實上,有多少Lisp程序員,就有多少種Lisp。這是因為,只有當你獨自一人深入荒漠,用樹枝在黃沙上為自己喜歡的Lisp方言寫解釋器時,你才成為一名真正的Lisp程序員。
目前主要有兩種
Lisp
語言分支:
Common Lis
p
和
Scheme
,每一種都有無數種的語言實現。各種
Common Lisp
實現都大同小異,而各種
Scheme
實現表現各異,有些看起來非常的不同,但它們的基本規則都相同。這兩種語言都非常有趣,但我卻沒有在實際工作中用過其中的任何一種。這兩種語言中分別在不同的方面讓我苦惱,在所有的
Lisp
方言中,我最喜歡的是
Clojure
語言。我不想在這個問題上做更多的討論,這是個人喜好,說起來很麻煩。
Clojure
,就像其它種的
Lisp
語言一樣,有一個
REPL(Read Eval Print Loop)
環境,你可以在里面寫代碼,而且能馬上得到運行結果。例如:
|
|
|
|
|
|
|
|
|
通常,你會看到一個提示符,就像
user>
,但在本文中,我使用的是更實用的顯示風格。這篇文章中的任何
REPL
代碼你都可以直接拷貝到
Try Clojure
運行。
我們可以像這樣調用一個函數:
|
|
|
|
|
|
程序打印出“
Hello World”
,並返回
nil
。我知道,這里的括弧看起來好像放錯了地方,但這是有原因的,你會發現,他跟
Java
風格的代碼沒有多少不同:
|
|
這種
Clojure
在執行任何操作時都要用到括弧:
|
|
|
|
在
Clojure
中,我們同樣能使用向量
(vector):
|
|
|
|
還有符號
(symbol):
|
|
|
|
這里要用引號
(')
,因為
Symbol
跟變量一樣,如果不用引號前綴,
Clojure
會把它變成它的值。
list
數據類型也一樣:
|
|
|
|
以及嵌套的
list
:
|
|
|
|
定義變量和使用變量的方法像這樣:
|
|
|
|
|
|
|
|
|
我的講解會很快,很多細節問題都會忽略掉,有些我講的東西可能完全是錯誤的。請原諒,我盡力做到最好。在
Clojure
中,創建函數的方法是這樣:
|
|
|
|
這顯示的又長又難看的東西是被編譯后的函數被打印出的樣子。不要擔心,你不會經常看到它們。這是個函數,使用
fn
操作符創建,有一個參數
n
。這個參數和
2
相乘,並當作結果返回。
Clojure
和其它所有的
Lisp
語言一樣,函數的最后一個表達式產生的值會被當作返回值返回。
如果你查看一個函數如何被調用:
|
|
你會發現它的形式是,括弧,函數,參數,反括弧。或者用另一種方式描述,這是一個列表序列,序列的第一位是操作符,其余的都是參數。
讓我們來調用這個函數:
|
|
|
|
我在這里所做的是定義了一個匿名函數,並立即應用它。讓我們來給這個函數起個名字:
|
|
|
|
現在我們通過這個名字來使用它:
|
|
|
|
正像你看到的,函數就像其它數據一樣被存放到了
變量里。因為有些操作會反復使用,我們可以使用簡化寫法:
|
|
|
|
|
|
|
|
|
我們使用
if
來給這個函數設定一個最大值:
|
|
if
操作符有三個參數:斷言,當斷言是
true
時將要執行的語句,當斷言是
false
時將要執行的語句。也許寫成這樣更容易理解:
|
|
|
|
|
|
|
|
非常基礎的東西。讓我們來看一下更有趣的東西。假設說你想把
Lisp
語句反着寫。把操作符放到最后,像這樣:
|
|
我們且把這種語言叫做
Psil(
反着寫的
Lisp...
我很聰明吧
)
。很顯然,如果你試圖執行這條語句,它會報錯:
|
|
|
|
Clojure
會告訴你
4
不是一個函數
(
函數是必須是
clojure.lang.IFn
接口的實現
)
。
我們可以寫一個簡單的函數把
Psil
轉變成
Lisp
:
|
|
|
|
當我執行它時出現了問題:
|
|
|
|
很明顯,我弄錯了一個地方,因為在
psil
被調用之前,
Clojure
會先去執行它的參數,也就是
(4 5 +)
,於是報錯了。我們可以顯式的把這個參數轉化成
list
,像這樣:
|
|
|
|
這回它就沒有被執行,但卻反轉了。要想運行它並不困難:
|
|
|
|
你開始發現
Lisp
的強大之處了。事實上,
Lisp
代碼就是一堆層層嵌套的列表序列,你可以很容易從這些序列數據中產生可以運行的程序。
如果你還沒明白,你可以在你常用的語言中試一下。在數組里放入
2
個數和一個加號,通過數組來執行這個運算。你最終得到的很可能是一個被連接的字符串,或是其它怪異的結果。這種編程方式在
Lisp
是如此的非常的常見,於是
Lisp
就提供了叫做
宏(macro)的可重用的東西來抽象出這種功能。宏是一種函數,它接受未執行的參數,而返回的結果是可執行的
Lisp
代碼。
讓我們把
psil
傳化成宏:
|
|
|
|
唯一不同之處是我們現在使用
defmacro
來替換
defn
。這是一個非常大的改動:
|
|
|
|
請注意,雖然參數並不是一個有效的
Clojure
參數,但程序並沒有報錯。這是因為參數並沒有被執行,只有當
psil
處理它時才被執行。
psil
把它的參數按數據看待。如果你聽說過有人說
Lisp
里代碼就是數據,這就是我們現在在討論的東西了。數據可以被編輯,產生出其它的程序。這種特征使你可以在
Lisp
語言上創建出任何你需要的新型語法語言。
在
Clojure
里有一種操作符叫做
macroexpand
,它可以使一個宏跳過可執行部分,這樣你就能看到是什么樣的代碼將會被執行:
|
|
|
|
你可以把宏看作一個在編譯期運行的函數。事實上,在
Lisp
里,編譯期和運行期是雜混在一起的,你的程序可以在這兩種狀態下來回切換。我們可以讓
psil
宏變的羅嗦些,讓我們看看代碼是如何運行的,但首先,我要先告訴你
do
這個東西。
do
是一個很簡單的操作符,它接受一批語句,依次運行它們,但這些語句是被整體當作一個表達式,例如:
|
|
|
|
|
|
|
|
通過使用
do
,我們可以使宏返回多個表達式,我們能看到更多的東西:
|
|
|
|
|
|
|
|
新宏會打印出“
compile time”
,並且返回一個
do
代碼塊,這個代碼塊打印出“
run time”
,並且反着運行一個表達式。這個反引號
`
的作用很像引號
'
,但它的獨特之處是你可以使用
~
符號在其內部解除引號。如果你聽不明白,不要擔心,讓我們來運行它一下:
|
|
|
|
|
|
|
|
如預期的結果,編譯期發生在運行期之前。如果我們使用
macroexpand
,或得到更清晰的信息:
|
|
|
|
|
|
可以看出,編譯階段已經發生,得到的是一個將要打印出“
run time”
的語句,然后會執行
(+ 5 4)
。
println
也被擴展成了它的完整形式,
clojure.core/println
,不過你可以忽略這個。然后代碼在運行期被執行。
這個宏的輸出本質上是:
|
|
|
|
而在宏里,它需要被寫成這樣:
|
|
|
|
反引號實際上是產生了一種模板形式的代碼,而波浪號讓其中的某些部分被執行
((reverse exp))
,而其余部分被保留。對於宏,其實還有更令人驚奇的東西,但現在,它已經很能變戲法了。
這種技術的力量還沒有被完全展現出來。按着
"
為什么我喜歡
Smalltalk
?
"
的思路,我們假設
Clojure
里沒有
if
語法,只有
cond
語法。也許在這里,這並不是一個太好的例子,但這個例子很簡單。
cond
功能跟其它語言里的
switch
或
case
很相似:
|
|
|
|
|
|
使用
cond
,我們可以直接創建出
my-if
函數:
|
|
|
|
|
|
初看起來似乎好使:
|
|
|
|
|
|
|
|
但有一個問題。你能發現它嗎?
my-if
執行了它所有的參數,所以,如果我們像這樣做,它就不能產生預期的結果了:
|
|
|
|
|
|
|
|
把
my-if
轉變成宏:
|
|
|
|
|
|
問題解決了:
|
|
|
|
|
|
這只是對宏的強大功能的窺豹一斑。一個非常有趣的案例是,當面向對象編程被發明出來后
(Lisp
的出現先於這概念
)
,
Lisp
程序員想使用這種技術。
C
程序員不得不使用他們的編譯器發明出新的語言
,C++
和
Object C
。
Lisp
程序員卻創建了一堆宏,就像
defclass
,
defmethod
等。這全都要歸功於宏。變革,在
Lisp
里,只是一種進化。
from: http://www.vaikan.com/why-i-love-lisp/
http://www.cnblogs.com/Chaobs/p/4851580.html