Lisp簡明教程

此教程是我花了一點時間和功夫整理出來的,但願可以幫到喜歡Lisp(Common Lisp)的朋友們。本人排版很爛還望多多海涵!php

  《Lisp簡明教程》PDF格式下載html

  《Lisp簡明教程》ODT格式下載java

  具體的內容我已經編輯好了,想下載的朋友能夠用上面的連接。本人水平有限,若有疏漏還望之處(要是有誰幫我排排版就行了)還望指出!資料雖然是我整理的,但都是網友的智慧,若是有人須要轉載,請至少保留其中的「鳴謝」頁(若是能有我就更好了:-))。node







Lisp簡明教程ios















整理人:Chaobs程序員

郵箱:chaobs@outlook.com算法

博客:www.cnblogs.com/Chaobsexpress

資料主要來源:http://www.yiibai.com/lisp/編程

版本:0.1.0小程序















前言



爲何Lisp語言如此先進?

 

(節選自《黑客與畫家》中譯本)

譯者原文:http://www.ruanyifeng.com/blog/2010/10/why_lisp_is_superior.html

1、

若是咱們把流行的編程語言,以這樣的順序排列:Java、Perl、Python、Ruby。你會發現,排在越後面的語言,越像Lisp

Python模仿Lisp,甚至把許多Lisp黑客認爲屬於設計錯誤的功能,也一塊兒模仿了。至於Ruby,若是回到1975年,你聲稱它是一種Lisp方言,沒有人會反對。

編程語言如今的發展,不過剛剛遇上1958年Lisp語言的水平。

2、

1958年,John McCarthy設計了Lisp語言。我認爲,當前最新潮的編程語言,只是實現了他在1958年的設想而已。

這怎麼可能呢?計算機技術的發展,不是突飛猛進嗎?1958年的技術,怎麼可能超過今天的水平呢?

讓我告訴你緣由。

這是由於John McCarthy原本沒打算把Lisp設計成編程語言,至少不是咱們如今意義上的編程語言。他的原意只是想作一種理論演算,用更簡潔的方式定義圖靈機。

因此,爲何上個世紀50年代的編程語言,到如今尚未過期?簡單說,由於這種語言本質上不是一種技術,而是數學。數學是不會過期的。你不該該把Lisp語言與50年代的硬件聯繫在一塊兒,而是應該把它與快速排序(Quicksort)算法進行類比。這種算法是1960年提出的,至今仍然是最快的通用排序方法。

3、

Fortran語言也是上個世紀50年代出現的,而且一直使用至今。它表明了語言設計的一種徹底不一樣的方向。Lisp是無心中從純理論發展爲編程語言,而Fortran從一開始就是做爲編程語言設計出來的。可是,今天咱們把Lisp當作高級語言,而把Fortran當作一種至關低層次的語言。

1956年,Fortran剛誕生的時候,叫作Fortran I,與今天的Fortran語言差異極大。Fortran I其實是彙編語言加上數學,在某些方面,還不現在天的彙編語言強大。好比,它不支持子程序,只有分支跳轉結構(branch)。

Lisp和Fortran表明了編程語言發展的兩大方向。前者的基礎是數學,後者的基礎是硬件架構。從那時起,這兩大方向一直在互相靠攏。Lisp剛設計出來的時候,就很強大,接下來的二十年,它提升了本身的運行速度。而那些所謂的主流語言,把更快的運行速度做爲設計的出發點,而後再用超過四十年的時間,一步步變得更強大。

直到今天,最高級的主流語言,也只是剛剛接近Lisp的水平。雖然已經很接近了,但仍是沒有Lisp那樣強大。

4、

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格式的出現使得這個概念被從新"發明"出來了。

5、

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把語言設計成這個樣子,就是爲了讓這種推導成爲可能。

6、

就算Lisp確實表明了目前主流編程語言不斷靠近的一個方向,這是否意味着你就應該用它編程呢?

若是使用一種不那麼強大的語言,你又會有多少損失呢?有時不採用最尖端的技術,不也是一種明智的選擇嗎?這麼多人使用主流編程語言,這自己不也說明那些語言有可取之處嗎?

另外一方面,選擇哪種編程語言,許多項目是無所謂的,反正不一樣的語言都能完成工做。通常來講,條件越苛刻的項目,強大的編程語言就越能發揮做用。可是,無數的項目根本沒有苛刻條件的限制。大多數的編程任務,可能只要寫一些很小的程序,而後用膠水語言把這些小程序連起來就好了。你能夠用本身熟悉的編程語言,或者用對於特定項目來講有着最強大函數庫的語言,來寫這些小程序。若是你只是須要在Windows應用程序之間傳遞數據,使用Visual Basic照樣能達到目的。

那麼,Lisp的編程優點體如今哪裏呢?

7、

語言的編程能力越強大,寫出來的程序就越短(固然不是指字符數量,而是指獨立的語法單位)。

代碼的數量很重要,由於開發一個程序耗費的時間,主要取決於程序的長度。若是同一個軟件,一種語言寫出來的代碼比另外一種語言長三倍,這意味着你開發它耗費的時間也會多三倍。並且即便你多僱傭人手,也無助於減小開發時間,由於當團隊規模超過某個門檻時,再增長人手只會帶來淨損失。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

def foo (n)

  lambda {|i| n += i } end

 Perl 5的寫法則是:

1

2

3

4

sub foo {

  my ($n) = @_;

  sub {$n += shift}

}

 這比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

function foo (n) {

     return function (i) {

    return n += i } }

 (實事求是地說,Perl也保留了語句和表達式的區別,可是使用了典型的Perl方式處理,使你能夠省略return。)

若是想把Lisp/Ruby/Perl/Smalltalk/Javascript的版本改爲Python,你會遇到一些限制。由於Python並不徹底支持局部變量,你不得不創造一種數據結構,來接受n的值。並且儘管Python確實支持函數數據類型,可是沒有一種字面量的表示方式(literal representation)能夠生成函數(除非函數體只有一個表達式),因此你須要創造一個命名函數,把它返回。最後的寫法以下:

1

2

3

4

5

6

def foo (n):

  = [n]

  def bar (i):

    s[0+= i

    return s[0]

  return bar

 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

博客:www.cnblogs.com/Chaobs

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有三個部分:

  1. 信號的條件

  2. 處理條件

  3. 重啓進程

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 編寫處理程序

條件處理程序是用於處理信號的條件在其上的代碼。它通常寫在調用該函數出問題的上級功能之一。當條件信號發生時,該信號轉導機制中搜索基於所述條件的類合適的處理器。

每一個處理程序包括:

  1. 類型說明符,它指示條件,它能夠處理的類型

  2. 一個函數,它接受一個參數條件

  3. 當條件得到信號,該信號機制發現最近創建的處理程序與條件類型兼容,並調用它的函數。

宏處理程序的狀況創建了一個條件處理程序。一個處理程序的 handler-case 形式:

(handler-case expression

error-clause*)

那麼,每一個error從句的形式爲:

condition-type ([var]) code)

25.4 從新啓動階段

這是真正從錯誤的代碼中恢復程序,條件處理程序能夠經過調用一個適當的重啓處理的條件。重啓代碼通常是放置在中層或底層函數和條件處理程序被放置到應用程序的上層。

handler-bind宏容許提供一個重啓功能,並容許繼續在較低級的功能,無需解除函數的調用堆棧。換句話說,控制流將仍然處於較低水平的功能。

handler-bind的基本形式以下:

(handler-bind (binding*) form*)

其中每一個綁定如如下列表:

  1. 條件類型

  2. 一個參數的處理函數

  3. invoke-restart宏查找並調用具備指定名稱做爲參數最近綁定重啓功能。

  4. 能夠有多個從新啓動。

示例

在這個例子中,咱們演示了上述概念經過寫一個名爲劃分功能函數,則會建立錯誤條件,若是除數參數爲零。咱們有三個匿名的功能,提供三種方式來出它 – 經過返回一個值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 LispScheme,每一種都有無數種的語言實現。各類Common Lisp實現都大同小異,而各類Scheme實現表現各異,有些看起來很是的不一樣,但它們的基本規則都相同。這兩種語言都很是有趣,但我卻沒有在實際工做中用過其中的任何一種。這兩種語言中分別在不一樣的方面讓我苦惱,在全部的Lisp方言中,我最喜歡的是Clojure語言。我不想在這個問題上作更多的討論,這是我的喜愛,提及來很麻煩。Clojure,就像其它種的Lisp語言同樣,有一個REPL(Read Eval Print Loop)環境,你能夠在裏面寫代碼,並且能立刻獲得運行結果。例如:

1

5

2

;=> 5

3

 

4

"Hello world"

5

;=> "Hello world"

一般,你會看到一個提示符,就像user>,但在本文中,我使用的是更實用的顯示風格。這篇文章中的任何REPL代碼你均可以直接拷貝到Try Clojure運行。

咱們能夠像這樣調用一個函數:



1

(println "Hello World")

2

; Hello World

3

;=> nil

 

程序打印出「Hello World」,並返回nil。我知道,這裏的括弧看起來好像放錯了地方,但這是有緣由的,你會發現,他跟Java風格的代碼沒有多少不一樣:



1

println("Hello World")

這種Clojure在執行任何操做時都要用到括弧:



1

(+ 1 2)

2

;=> 3

 

Clojure中,咱們一樣能使用向量(vector):



1

[1 2 3 4]

2

;=> [1 2 3 4]

 

還有符號(symbol):



1

'symbol

2

;=> symbol



這裏要用引號('),由於Symbol跟變量同樣,若是不用引號前綴,Clojure會把它變成它的值。list數據類型也同樣:



1

'(li st)

2

;=> (li st)



以及嵌套的list



1

'(l (i s) t)

2

;=> (l (i s) t)



定義變量和使用變量的方法像這樣:



1

(def hello-world "Hello world")

2

;=> #'user/hello-world

3

 

4

hello-world

5

;=> "Hello world"



個人講解會很快,不少細節問題都會忽略掉,有些我講的東西可能徹底是錯誤的。請原諒,我盡力作到最好。在Clojure中,建立函數的方法是這樣:



1

(fn [n] (* n 2))

2

;=> #<user$eval1$fn__2 user$eval1$fn__2@175bc6c8>



這顯示的又長又難看的東西是被編譯後的函數被打印出的樣子。不要擔憂,你不會常常看到它們。這是個函數,使用fn操做符建立,有一個參數n。這個參數和2相乘,並看成結果返回。Clojure和其它全部的Lisp語言同樣,函數的最後一個表達式產生的值會被看成返回值返回。

若是你查看一個函數如何被調用:



1

(println "Hello World")

 

你會發現它的形式是,括弧,函數,參數,反括弧。或者用另外一種方式描述,這是一個列表序列,序列的第一位是操做符,其他的都是參數。

讓咱們來調用這個函數:



1

((fn [n] (* n 2)) 10)

2

;=> 20



我在這裏所作的是定義了一個匿名函數,並當即應用它。讓咱們來給這個函數起個名字:



1

(def twice (fn [n] (* n 2)))

2

;=> #'user/twice



如今咱們經過這個名字來使用它:



1

(twice 32)

2

;=> 64



正像你看到的,函數就像其它數據同樣被存放到了變量裏。由於有些操做會反覆使用,咱們能夠使用簡化寫法:



1

(defn twice [n] (* 2 n))

2

;=> #'user/twice

3

 

4

(twice 32)

5

;=> 64



咱們使用if來給這個函數設定一個最大值:



1

(defn twice [n] (if (> n 50) 100 (* n 2))))



if操做符有三個參數:斷言,當斷言是true時將要執行的語句,當斷言是 false 時將要執行的語句。也許寫成這樣更容易理解:



1

(defn twice [n]

2

  (if (> n 50)

3

      100

4

      (* n 2)))



很是基礎的東西。讓咱們來看一下更有趣的東西。假設說你想把Lisp語句反着寫。把操做符放到最後,像這樣:



1

(4 5 +)



咱們且把這種語言叫作Psil(反着寫的Lisp...我很聰明吧)。很顯然,若是你試圖執行這條語句,它會報錯:



1

(4 5 +)

2

;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)



Clojure會告訴你4不是一個函數(函數是必須是clojure.lang.IFn接口的實現)

咱們能夠寫一個簡單的函數把Psil轉變成Lisp

1

(defn psil [exp]

2

  (reverse exp))



當我執行它時出現了問題:



1

(psil (4 5 +))

2

;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)



很明顯,我弄錯了一個地方,由於在psil被調用以前,Clojure會先去執行它的參數,也就是(4 5 +),因而報錯了。咱們能夠顯式的把這個參數轉化成list,像這樣:



1

(psil '(4 5 +))

2

;=> (+ 5 4)



這回它就沒有被執行,但卻反轉了。要想運行它並不困難:



1

(eval (psil '(4 5 +)))

2

;=> 9



你開始發現Lisp的強大之處了。事實上,Lisp代碼就是一堆層層嵌套的列表序列,你能夠很容易從這些序列數據中產生能夠運行的程序。

若是你還沒明白,你能夠在你經常使用的語言中試一下。在數組裏放入2個數和一個加號,經過數組來執行這個運算。你最終獲得的極可能是一個被鏈接的字符串,或是其它怪異的結果。這種編程方式在Lisp是如此的很是的常見,因而Lisp就提供了叫作(macro)的可重用的東西來抽象出這種功能。宏是一種函數,它接受未執行的參數,而返回的結果是可執行的Lisp代碼。

讓咱們把psil傳化成宏:



1

(defmacro psil [exp]

2

  (reverse exp))



惟一不一樣之處是咱們如今使用defmacro來替換defn。這是一個很是大的改動:



1

(psil (4 5 +))

2

;=> 9



請注意,雖然參數並非一個有效的Clojure參數,但程序並無報錯。這是由於參數並無被執行,只有當psil處理它時才被執行。psil把它的參數按數據看待。若是你據說過有人說Lisp裏代碼就是數據,這就是咱們如今在討論的東西了。數據能夠被編輯,產生出其它的程序。這種特徵使你能夠在Lisp語言上建立出任何你須要的新型語法語言。

Clojure裏有一種操做符叫作macroexpand,它能夠使一個宏跳過可執行部分,這樣你就能看到是什麼樣的代碼將會被執行:



1

(macroexpand '(psil (4 5 +)))

2

;=> (+ 5 4)



你能夠把宏看做一個在編譯期運行的函數。事實上,在Lisp裏,編譯期和運行期是雜混在一塊兒的,你的程序能夠在這兩種狀態下來回切換。咱們可讓psil宏變的羅嗦些,讓咱們看看代碼是如何運行的,但首先,我要先告訴你do這個東西。

do是一個很簡單的操做符,它接受一批語句,依次運行它們,但這些語句是被總體看成一個表達式,例如:



1

(do (println "Hello") (println "world"))

2

; Hello

3

; world

4

;=> nil



經過使用do,咱們能夠使宏返回多個表達式,咱們能看到更多的東西:



1

(defmacro psil [exp]

2

  (println "compile time")

3

  `(do (println "run time")

4

       ~(reverse exp)))



新宏會打印出「compile time」,而且返回一個do代碼塊,這個代碼塊打印出「run time」,而且反着運行一個表達式。這個反引號`的做用很像引號',但它的獨特之處是你能夠使用~符號在其內部解除引號。若是你聽不明白,不要擔憂,讓咱們來運行它一下:



1

(psil (4 5 +))

2

; compile time

3

; run time

4

;=> 9



如預期的結果,編譯期發生在運行期以前。若是咱們使用macroexpand,或獲得更清晰的信息:



1

(macroexpand '(psil (4 5 +)))

2

; compile time

3

;=> (do (clojure.core/println "run time") (+ 5 4))



能夠看出,編譯階段已經發生,獲得的是一個將要打印出「run time」的語句,而後會執行(+ 5 4)println也被擴展成了它的完整形式,clojure.core/println,不過你能夠忽略這個。而後代碼在運行期被執行。

這個宏的輸出本質上是:



1

(do (println "run time")

2

    (+ 5 4))



而在宏裏,它須要被寫成這樣:



1

`(do (println "run time")

2

     ~(reverse exp))



反引號其實是產生了一種模板形式的代碼,而波浪號讓其中的某些部分被執行((reverse exp)),而其他部分被保留。對於宏,其實還有更使人驚奇的東西,但如今,它已經很能變戲法了。

這種技術的力量尚未被徹底展示出來。按着"爲何我喜歡Smalltalk"的思路,咱們假設Clojure裏沒有if語法,只有cond語法。也許在這裏,這並非一個太好的例子,但這個例子很簡單。

cond功能跟其它語言裏的switchcase很類似:



1

(cond (= x 0) "It's zero"

2

      (= x 1) "It's one"

3

      :else "It's something else")



使用 cond,咱們能夠直接建立出my-if函數:



1

(defn my-if [predicate if-true if-false]

2

  (cond predicate if-true

3

        :else if-false))

 

初看起來彷佛好使:



1

(my-if (= 0 0) "equals" "not-equals")

2

;=> "equals"

3

(my-if (= 0 1) "equals" "not-equals")

4

;=> "not-equals"



但有一個問題。你能發現它嗎?my-if執行了它全部的參數,因此,若是咱們像這樣作,它就不能產生預期的結果了:



1

(my-if (= 0 0) (println "equals") (println "not-equals"))

2

; equals

3

; not-equals

4

;=> nil



my-if轉變成宏:



1

(defmacro my-if [predicate if-true if-false]

2

  `(cond ~predicate ~if-true

3

         :else ~if-false))



問題解決了:



1

(my-if (= 0 0) (println "equals") (println "not-equals"))

2

; equals

3

;=> nil

這只是對宏的強大功能的窺豹一斑。一個很是有趣的案例是,當面向對象編程被髮明出來後(Lisp的出現先於這概念)Lisp程序員想使用這種技術。C程序員不得不使用他們的編譯器發明出新的語言,C++Object CLisp程序員卻建立了一堆宏,就像defclass, defmethod等。這全都要歸功於宏。變革,在Lisp裏,只是一種進化。

 

 

from: http://www.vaikan.com/why-i-love-lisp/

http://www.cnblogs.com/Chaobs/p/4851580.html

相關文章
相關標籤/搜索