
- LISP 教程
- LISP - 首頁
- LISP - 概述
- LISP - 環境
- LISP - 程式結構
- LISP - 基本語法
- LISP - 資料型別
- 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 有用資源
- Lisp - 快速指南
- Lisp - 有用資源
- Lisp - 討論
LISP 快速指南
LISP - 概述
John McCarthy 在 1958 年發明了 LISP,這緊隨 FORTRAN 的開發之後。它最初由 Steve Russell 在 IBM 704 計算機上實現。
它特別適用於人工智慧程式,因為它可以有效地處理符號資訊。
Common Lisp 起源於 20 世紀 80 年代和 90 年代,試圖統一幾個實現組的工作,這些組是 Maclisp 的繼任者,如 ZetaLisp 和 NIL(Lisp 的新實現)等。
它作為一種通用語言,可以輕鬆地擴充套件到特定實現。
用 Common LISP 編寫的程式不依賴於特定於機器的特性,例如字長等。
Common LISP 的特性
它是機器無關的
它使用迭代設計方法,並且易於擴充套件。
它允許動態更新程式。
它提供高階除錯。
它提供高階面向物件程式設計。
它提供了一個方便的宏系統。
它提供了廣泛的資料型別,例如物件、結構體、列表、向量、可調整陣列、雜湊表和符號。
它是基於表示式的。
它提供了一個面向物件的條件系統。
它提供了一個完整的 I/O 庫。
它提供了廣泛的控制結構。
用 LISP 構建的應用程式
用 Lisp 構建的大型成功應用程式。
Emacs
G2
AutoCad
Igor Engraver
Yahoo Store
LISP - 環境設定
本地環境設定
如果您仍然希望為 Lisp 程式語言設定您的環境,則需要您的計算機上有以下兩個軟體:(a) 文字編輯器和 (b) Lisp 執行器。
文字編輯器
這將用於鍵入您的程式。一些編輯器的示例包括 Windows 記事本、OS Edit 命令、Brief、Epsilon、EMACS 和 vim 或 vi。
文字編輯器的名稱和版本在不同的作業系統上可能有所不同。例如,Notepad 將在 Windows 上使用,而 vim 或 vi 可以在 Windows 以及 Linux 或 UNIX 上使用。
您使用編輯器建立的檔案稱為原始檔,其中包含程式原始碼。Lisp 程式的原始檔通常以副檔名“.lisp”命名。
在開始程式設計之前,請確保您已準備好一個文字編輯器,並且您有足夠的經驗編寫計算機程式,將其儲存在檔案中,最後執行它。
Lisp 執行器
原始檔中編寫的原始碼是程式的人類可讀原始碼。它需要“執行”,才能轉換為機器語言,以便您的 CPU 能夠根據給定的指令實際執行程式。
此 Lisp 程式語言將用於將您的原始碼執行為最終的可執行程式。我假設您具備程式語言的基本知識。
CLISP 是 GNU Common LISP 的多架構編譯器,用於在 Windows 中設定 LISP。Windows 版本在 Windows 下使用 MingW 模擬 unix 環境。安裝程式會處理此問題並自動將 clisp 新增到 Windows PATH 變數中。
您可以從此處獲取適用於 Windows 的最新 CLISP - https://sourceforge.net/projects/clisp/files/latest/download

預設情況下,它會在“開始”選單中建立一個快捷方式,用於逐行直譯器。
如何使用 CLISP
在安裝過程中,如果您選擇選項(推薦),則clisp會自動新增到您的 PATH 變數中。這意味著您可以簡單地開啟一個新的命令提示符視窗並鍵入“clisp”以啟動編譯器。
要執行 *.lisp 或 *.lsp 檔案,只需使用 −
clisp hello.lisp
LISP - 程式結構
LISP 表示式稱為符號表達式或 s 表示式。s 表示式由三個有效物件組成,原子、列表和字串。
任何 s 表示式都是一個有效的程式。
LISP 程式要麼在直譯器上執行,要麼作為編譯程式碼執行。
直譯器在重複迴圈中檢查原始碼,該迴圈也稱為讀取-求值-列印迴圈 (REPL)。它讀取程式程式碼,對其進行求值,並列印程式返回的值。
一個簡單的程式
讓我們編寫一個 s 表示式來找到三個數字 7、9 和 11 的總和。為此,我們可以在直譯器提示符下鍵入。
(+ 7 9 11)
LISP 返回結果 −
27
如果您想將同一個程式作為編譯程式碼執行,則建立一個名為 myprog.lisp 的 LISP 原始碼檔案,並在其中鍵入以下程式碼。
(write (+ 7 9 11))
當您單擊“執行”按鈕或鍵入 Ctrl+E 時,LISP 會立即執行它,並返回的結果為 −
27
LISP 使用字首表示法
您可能已經注意到 LISP 使用字首表示法。
在上述程式中,+ 符號作為求和過程的函式名稱。
在字首表示法中,運算子寫在其運算元之前。例如,表示式,
a * ( b + c ) / d
將寫成 −
(/ (* a (+ b c) ) d)
讓我們再舉一個例子,讓我們編寫將 60o F 的華氏溫度轉換為攝氏溫度的程式碼 −
此轉換的數學表示式將為 −
(60 * 9 / 5) + 32
建立一個名為 main.lisp 的原始碼檔案,並在其中鍵入以下程式碼。
(write(+ (* (/ 9 5) 60) 32))
當您單擊“執行”按鈕或鍵入 Ctrl+E 時,LISP 會立即執行它,並返回的結果為 −
140
LISP 程式的求值
LISP 程式的求值有兩個部分 −
由讀取器程式將程式文字轉換為 Lisp 物件
根據這些物件以評估器程式的形式實現語言的語義
求值過程採取以下步驟 −
讀取器將字元字串轉換為 LISP 物件或s 表示式。
評估器定義 Lisp形式的語法,這些形式由 s 表示式構建而成。這第二級的求值定義了一個語法,該語法確定哪些s 表示式是 LISP 形式。
評估器充當一個函式,它以一個有效的 LISP 形式作為引數並返回一個值。這就是我們將 LISP 表示式放在括號中的原因,因為我們將整個表示式/形式作為引數傳送給評估器。
“Hello World”程式
學習一門新的程式語言,直到你學會用這種語言問候全世界,才算真正開始,對吧!
所以,請建立一個名為 main.lisp 的新原始碼檔案,並在其中鍵入以下程式碼。
(write-line "Hello World") (write-line "I am at 'Tutorials Point'! Learning LISP")
當您單擊“執行”按鈕或鍵入 Ctrl+E 時,LISP 會立即執行它,並返回的結果為 −
Hello World I am at 'Tutorials Point'! Learning LISP
LISP - 基本語法
LISP 中的基本構建塊
LISP 程式由三個基本構建塊組成 −
- 原子
- 列表
- 字串
原子是一個數字或連續字元的字串。它包括數字和特殊字元。
以下是一些有效原子的示例 −
hello-from-tutorials-point 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 Point'! "
添加註釋
分號符號 (;) 用於指示註釋行。
例如,
(write-line "Hello World") ; greet the world ; tell them your whereabouts (write-line "I am at 'Tutorials Point'! Learning LISP")
當您單擊“執行”按鈕或鍵入 Ctrl+E 時,LISP 會立即執行它,並返回的結果為 −
Hello World I am at 'Tutorials Point'! Learning LISP
在繼續下一步之前的一些注意事項
以下是一些需要注意的重要事項 −
LISP 中的基本數值運算為 +、-、* 和 /
LISP 將函式呼叫 f(x) 表示為 (f x),例如 cos(45) 寫成 cos 45
LISP 表示式不區分大小寫,cos 45 或 COS 45 相同。
LISP 嘗試對所有內容進行求值,包括函式的引數。只有三種類型的元素是常量,並且始終返回它們自己的值
數字
字母t,代表邏輯真。
值nil,代表邏輯假,以及空列表。
關於 LISP 形式的更多資訊
在上一章中,我們提到 LISP 程式碼的求值過程採取以下步驟。
讀取器將字元字串轉換為 LISP 物件或s 表示式。
評估器定義 Lisp形式的語法,這些形式由 s 表示式構建而成。這第二級的求值定義了一個語法,該語法確定哪些 s 表示式是 LISP 形式。
現在,LISP 形式可以是。
- 一個原子
- 一個空列表或非列表
- 任何以符號作為其第一個元素的列表
評估器充當一個函式,它以一個有效的 LISP 形式作為引數並返回一個值。這就是我們將LISP 表示式放在括號中的原因,因為我們將整個表示式/形式作為引數傳送給評估器。
LISP 中的命名約定
名稱或符號可以包含任意數量的字母數字字元,除了空格、開括號和閉括號、雙引號和單引號、反斜槓、逗號、冒號、分號和豎線。要在名稱中使用這些字元,您需要使用跳脫字元 (\)。
名稱可以包含數字,但不能完全由數字組成,因為那樣它將被讀取為數字。類似地,名稱可以包含句點,但不能完全由句點組成。
單引號的使用
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))
當您單擊“執行”按鈕或鍵入 Ctrl+E 時,LISP 會立即執行它,並返回的結果為 −
single quote used, it inhibits evaluation (* 2 3) single quote not used, so expression evaluated 6
LISP - 資料型別
在 LISP 中,變數沒有型別,但資料物件有。
LISP 資料型別可以分類為。
標量型別− 例如,數字型別、字元、符號等。
資料結構− 例如,列表、向量、位向量和字串。
任何變數都可以將其值作為任何 LISP 物件,除非您已明確宣告它。
雖然沒有必要為 LISP 變數指定資料型別,但是,它有助於某些迴圈擴充套件、方法宣告和其他我們將在後續章節中討論的情況。
資料型別按層次結構排列。資料型別是一組 LISP 物件,許多物件可能屬於這樣一個集合。
typep謂詞用於查詢物件是否屬於特定型別。
type-of函式返回給定物件的型別。
LISP 中的型別說明符
型別說明符是系統定義的資料型別的符號。
陣列 | 定點整數 | 包 | 簡單字串 |
原子 | 浮點數 | 路徑名 | 簡單向量 |
大整數 | 函式 | 隨機狀態 | 單精度浮點數 |
位元 | 雜湊表 | 有理數 | 標準字元 |
位元向量 | 整數 | 有理數 | 流 |
字元 | 關鍵字 | 讀表 | 字串 |
[通用] | 列表 | 序列 | [字串字元] |
編譯後的函式 | 長浮點數 | 短浮點數 | 符號 |
複數 | 空 | 有符號位元組 | 真 |
cons單元 | 空 | 簡單陣列 | 無符號位元組 |
雙精度浮點數 | 數字 | 簡單位元向量 | 向量 |
除了這些系統定義的型別外,您還可以建立自己的資料型別。當使用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)
當您單擊“執行”按鈕或鍵入 Ctrl+E 時,LISP 會立即執行它,並返回的結果為 −
10 34.567 123.78 NIL 110000.0 62
示例 2
接下來,讓我們檢查前面示例中使用的變數的型別。建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(defvar x 10) (defvar y 34.567) (defvar ch nil) (defvar n 123.78) (defvar bg 11.0e+4) (defvar 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))
當您單擊“執行”按鈕或鍵入 Ctrl+E 時,LISP 會立即執行它,並返回的結果為 −
(INTEGER 0 281474976710655) SINGLE-FLOAT SINGLE-FLOAT NULL SINGLE-FLOAT (INTEGER 0 281474976710655)
LISP - 宏
宏允許您擴充套件標準LISP的語法。
從技術上講,宏是一個函式,它以s表示式作為引數,並返回一個LISP形式,然後對該形式進行求值。
定義宏
在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)
當您單擊“執行”按鈕或鍵入 Ctrl+E 時,LISP 會立即執行它,並返回的結果為 −
25 10
LISP - 變數
在LISP中,每個變數都由一個符號表示。變數的名稱是符號的名稱,它儲存在符號的儲存單元中。
全域性變數
全域性變數在整個LISP系統中具有永久值,並且保持有效,直到指定新值。
全域性變數通常使用defvar結構宣告。
例如
(defvar x 234) (write x)
當您單擊“執行”按鈕或鍵入Ctrl+E時,LISP會立即執行它,並返回結果
234
由於LISP中沒有變數的型別宣告,因此您可以使用setq結構直接為符號指定值。
例如
->(setq x 10)
上述表示式將值10賦給變數x。您可以使用符號本身作為表示式來引用變數。
symbol-value函式允許您提取儲存在符號儲存位置的值。
例如
建立一個名為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)
當您單擊“執行”按鈕或鍵入Ctrl+E時,LISP會立即執行它,並返回結果。
x = 10 y = 20 x = 100 y = 200
區域性變數
區域性變數在給定的過程中定義。在函式定義中作為引數命名的引數也是區域性變數。區域性變數僅在相應的函式內可訪問。
與全域性變數一樣,區域性變數也可以使用setq結構建立。
還有另外兩個結構 - let 和 prog 用於建立區域性變數。
let結構具有以下語法。
(let ((var1 val1) (var2 val2).. (varn valn))<s-expressions>)
其中var1、var2、..varn是變數名,val1、val2、..valn是分配給相應變數的初始值。
當執行let時,每個變數都被分配相應的值,最後對s表示式進行求值。返回最後求值的表示式的值。
如果您不為變數包含初始值,則將其分配給nil。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(let ((x 'a) (y 'b)(z 'c)) (format t "x = ~a y = ~a z = ~a" x y z))
當您單擊“執行”按鈕或鍵入Ctrl+E時,LISP會立即執行它,並返回結果。
x = A y = B z = C
prog結構也將其第一個引數作為區域性變數列表,後面跟著prog的主體,以及任意數量的s表示式。
prog函式按順序執行s表示式的列表,並返回nil,除非它遇到名為return的函式呼叫。然後對return函式的引數進行求值並返回。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(prog ((x '(a b c))(y '(1 2 3))(z '(p q 10))) (format t "x = ~a y = ~a z = ~a" x y z))
當您單擊“執行”按鈕或鍵入Ctrl+E時,LISP會立即執行它,並返回結果。
x = (A B C) y = (1 2 3) z = (P Q 10)
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)
當您單擊“執行”按鈕或鍵入Ctrl+E時,LISP會立即執行它,並返回結果。
Radius: 10.0 Area: 314.1592
LISP - 運算子
運算子是一個符號,它告訴編譯器執行特定的數學或邏輯操作。LISP允許對資料進行大量操作,這些操作由各種函式、宏和其他結構支援。
允許對資料執行的操作可以分類為:
- 算術運算
- 比較運算
- 邏輯運算
- 按位運算
算術運算
下表顯示了LISP支援的所有算術運算子。假設變數A持有10,變數B持有20,則:
運算子 | 描述 | 示例 |
---|---|---|
+ | 將兩個運算元相加 | (+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 |
比較運算
下表顯示了LISP支援的所有關係運算符,這些運算子用於比較數字之間的大小。但是,與其他語言中的關係運算符不同,LISP比較運算子可以接受兩個以上的運算元,並且它們僅對數字起作用。
假設變數A持有10,變數B持有20,則:
運算子 | 描述 | 示例 |
---|---|---|
= | 檢查運算元的值是否都相等,如果是,則條件變為真。 | (= A B)不為真。 |
/= | 檢查運算元的值是否都不同,如果值不相等,則條件變為真。 | (/= A B)為真。 |
> | 檢查運算元的值是否單調遞減。 | (> A B)不為真。 |
< | 檢查運算元的值是否單調遞增。 | (< A B)為真。 |
>= | 檢查任何左側運算元的值是否大於或等於其右側下一個運算元的值,如果是,則條件變為真。 | (>= A B)不為真。 |
<= | 檢查任何左側運算元的值是否小於或等於其右側運算元的值,如果是,則條件變為真。 | (<= A B)為真。 |
max | 它比較兩個或多個引數並返回最大值。 | (max A B)返回20 |
min | 它比較兩個或多個引數並返回最小值。 | (min A B)返回10 |
布林值的邏輯運算
Common LISP提供了三個邏輯運算子:and、or和not,它們對布林值進行操作。假設A的值為nil,B的值為5,則:
運算子 | 描述 | 示例 |
---|---|---|
and | 它接受任意數量的引數。引數從左到右求值。如果所有引數都求值為非nil,則返回最後一個引數的值。否則返回nil。 | (and A B)將返回NIL。 |
or | 它接受任意數量的引數。引數從左到右求值,直到一個引數求值為非nil,在這種情況下,返回該引數的值,否則返回nil。 | (or A B)將返回5。 |
not | 它接受一個引數,如果引數求值為nil,則返回t。 | (not A)將返回T。 |
數字的按位運算
按位運算子對位進行操作,並執行逐位運算。按位與、或和異或運算的真值表如下:
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 |
Assume if A = 60; and B = 13; now in binary format they will be as follows: A = 0011 1100 B = 0000 1101 ----------------- A and B = 0000 1100 A or B = 0011 1101 A xor B = 0011 0001 not A = 1100 0011
LISP支援的按位運算子列在下表中。假設變數A持有60,變數B持有13,則:
運算子 | 描述 | 示例 |
---|---|---|
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 |
LISP - 決策
決策結構要求程式設計師指定一個或多個要由程式評估或測試的條件,以及如果確定條件為真則要執行的語句或語句,以及可選地,如果確定條件為假則要執行的其他語句。
以下是大多數程式語言中發現的典型決策結構的一般形式:

LISP提供了以下型別的決策結構。點選以下連結檢視其詳細資訊。
序號 | 結構及描述 |
---|---|
1 | cond
此結構用於用於檢查多個測試-動作子句。它可以與其他程式語言中的巢狀if語句進行比較。 |
2 | if
if結構有多種形式。在最簡單的形式中,它後面跟著一個測試子句、一個測試動作和一些其他後續動作。如果測試子句求值為真,則執行測試動作,否則求值後續子句。 |
3 | when
在最簡單的形式中,它後面跟著一個測試子句和一個測試動作。如果測試子句求值為真,則執行測試動作,否則求值後續子句。 |
4 | case
此結構類似於 cond 結構,實現了多個測試-動作子句。但是,它會評估一個關鍵表單,並根據該關鍵表單的評估結果允許多個動作子句。 |
LISP - 迴圈
在某些情況下,您可能需要多次執行一段程式碼。迴圈語句允許我們多次執行一個語句或一組語句,以下是大多數程式語言中迴圈語句的一般形式。

LISP 提供以下幾種型別的結構來處理迴圈需求。點選以下連結檢視其詳細資訊。
序號 | 結構及描述 |
---|---|
1 | loop
loop 結構是 LISP 提供的最簡單的迭代形式。在其最簡單的形式中,它允許您重複執行某些語句,直到找到一個 return 語句。 |
2 | loop for
loop for 結構允許您實現類似於 for 迴圈的迭代,這在其他語言中最常見。 |
3 | do
do 結構也用於使用 LISP 執行迭代。它提供了一種結構化的迭代形式。 |
4 | dotimes
dotimes 結構允許迴圈執行固定次數的迭代。 |
5 | dolist
dolist 結構允許遍歷列表的每個元素。 |
優雅地退出程式碼塊
block 和 return-from 允許您在發生任何錯誤時優雅地退出任何巢狀的程式碼塊。
block 函式允許您建立一個命名塊,其主體由零個或多個語句組成。語法如下:
(block block-name( ... ... ))
return-from 函式接受一個塊名稱和一個可選的返回值(預設為 nil)。
以下示例演示了這一點:
示例
建立一個名為 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)
當您單擊“執行”按鈕或鍵入 Ctrl+E 時,LISP 會立即執行它,並返回的結果為 −
ENTERING-OUTER-BLOCK ENTERING-INNER-BLOCK ENTERING-OUTER-BLOCK ENTERING-INNER-BLOCK 5 LEFT-INNER-BLOCK LEAVING-OUTER-BLOCK
LISP - 函式
函式是一組共同執行一項任務的語句。
您可以將程式碼分成不同的函式。如何將程式碼劃分為不同的函式取決於您,但通常在邏輯上劃分是為了使每個函式執行特定的任務。
在 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 10 20 30 40))
執行程式碼後,它將返回以下結果:
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)
執行程式碼後,它將返回以下結果:
Radius: 10.0 Area: 314.1592
請注意:
您可以提供一個空列表作為引數,這意味著函式不接受任何引數,列表為空,寫成 ()。
LISP 還允許可選引數、多個引數和關鍵字引數。
文件字串描述了函式的目的。它與函式名稱相關聯,可以使用 documentation 函式獲取。
函式體可以包含任意數量的 Lisp 表示式。
主體中最後一個表示式的值作為函式的值返回。
您還可以使用 return-from 特殊運算子從函式中返回值。
讓我們簡要討論上述概念。點選以下連結查詢詳細資訊:
LISP - 謂詞
謂詞是測試其引數是否滿足某些特定條件的函式,如果條件為假則返回 nil,如果條件為真則返回某個非 nil 值。
下表顯示了一些最常用的謂詞:
序號 | 謂詞和描述 |
---|---|
1 | 原子 它接受一個引數,如果引數是原子則返回 t,否則返回 nil。 |
2 | equal 它接受兩個引數,如果它們在結構上相等則返回 t,否則返回 nil。 |
3 | eq 它接受兩個引數,如果它們是相同的物件,共享相同的記憶體位置則返回 t,否則返回 nil。 |
4 | eql 它接受兩個引數,如果引數是 eq,或者如果它們是相同型別且具有相同值的數字,或者如果它們是表示相同字元的字元物件,則返回 t,否則返回 nil。 |
5 | evenp 它接受一個數字引數,如果引數是偶數則返回 t,否則返回 nil。 |
6 | oddp 它接受一個數字引數,如果引數是奇數則返回 t,否則返回 nil。 |
7 | zerop 它接受一個數字引數,如果引數為零則返回 t,否則返回 nil。 |
8 | 空 它接受一個引數,如果引數計算結果為 nil 則返回 t,否則返回 nil。 |
9 | listp 它接受一個引數,如果引數計算結果為列表則返回 t,否則返回 nil。 |
10 | greaterp 它接受一個或多個引數,如果只有一個引數或引數從左到右依次變大,則返回 t,否則返回 nil。 |
11 | lessp 它接受一個或多個引數,如果只有一個引數或引數從左到右依次變小,則返回 t,否則返回 nil。 |
12 | numberp 它接受一個引數,如果引數是數字則返回 t,否則返回 nil。 |
13 | symbolp 它接受一個引數,如果引數是符號則返回 t,否則返回 nil。 |
14 | integerp 它接受一個引數,如果引數是整數則返回 t,否則返回 nil。 |
15 | rationalp 它接受一個引數,如果引數是有理數(比率或數字)則返回 t,否則返回 nil。 |
16 | floatp 它接受一個引數,如果引數是浮點數則返回 t,否則返回 nil。 |
17 | realp 它接受一個引數,如果引數是實數則返回 t,否則返回 nil。 |
18 | complexp 它接受一個引數,如果引數是複數則返回 t,否則返回 nil。 |
19 | characterp 它接受一個引數,如果引數是字元則返回 t,否則返回 nil。 |
20 | stringp 它接受一個引數,如果引數是字串物件則返回 t,否則返回 nil。 |
21 | arrayp 它接受一個引數,如果引數是陣列物件則返回 t,否則返回 nil。 |
22 | 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))
執行程式碼後,它將返回以下結果:
Factorial 6 is: 720
LISP - 數字
Common Lisp 定義了幾種型別的數字。number 資料型別包含 LISP 支援的各種型別的數字。
LISP 支援的數字型別有:
- 整數
- 比率
- 浮點數
- 複數
下圖顯示了數字層次結構和 LISP 中可用的各種數值資料型別:

LISP 中的各種數值型別
下表描述了 LISP 中可用的各種數字型別資料:
序號 | 資料型別和描述 |
---|---|
1 | 定點整數 此資料型別表示整數,這些整數不太大,並且大多在 -215 到 215-1 的範圍內(取決於機器)。 |
2 | 大整數 這些是非常大的數字,大小受分配給 LISP 的記憶體量限制,它們不是 fixnum 數字。 |
3 | 有理數 表示分子/分母形式的兩個數字的比率。當引數為整數時,/ 函式始終產生比率結果。 |
4 | 浮點數 它表示非整數。有四種浮點資料型別,精度逐漸提高。 |
5 | 複數 它表示複數,用 #c 表示。實部和虛部都可以是有理數或浮點數。 |
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (/ 1 2)) (terpri) (write ( + (/ 1 2) (/ 3 4))) (terpri) (write ( + #c( 1 2) #c( 3 -4)))
執行程式碼後,它將返回以下結果:
1/2 5/4 #C(4 -2)
數字函式
下表描述了一些常用的數值函式:
序號 | 函式和描述 |
---|---|
1 | +, -, *, / 相應的算術運算 |
2 | sin, cos, tan, acos, asin, atan 相應的三角函式。 |
3 | sinh, cosh, tanh, acosh, asinh, atanh 相應的雙曲函式。 |
4 | exp 指數函式。計算 ex |
5 | expt 指數函式,同時接受底數和指數。 |
6 | sqrt 它計算數字的平方根。 |
7 | log 對數函式。如果給出一個引數,則計算其自然對數,否則第二個引數用作底數。 |
8 | conjugate 它計算數字的複共軛。對於實數,它返回數字本身。 |
9 | abs 它返回數字的絕對值(或大小)。 |
10 | gcd 它計算給定數字的最大公約數。 |
11 | lcm 它計算給定數字的最小公倍數。 |
12 | isqrt 它給出小於或等於給定自然數的精確平方根的最大整數。 |
13 | floor, ceiling, truncate, round 所有這些函式都接受兩個引數作為數字並返回商;floor 返回不大於比率的最大整數,ceiling 選擇大於比率的較小整數,truncate 選擇與比率符號相同的整數,其絕對值小於比率的絕對值,round 選擇最接近比率的整數。 |
14 | ffloor, fceiling, ftruncate, fround 執行與上述相同的操作,但將商返回為浮點數。 |
15 | mod, rem 返回除法運算中的餘數。 |
16 | 浮點數 將實數轉換為浮點數。 |
17 | rational, rationalize 將實數轉換為有理數。 |
18 | numerator, denominator 返回有理數的相應部分。 |
19 | realpart, imagpart 返回複數的實部和虛部。 |
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (/ 45 78)) (terpri) (write (floor 45 78)) (terpri) (write (/ 3456 75)) (terpri) (write (floor 3456 75)) (terpri) (write (ceiling 3456 75)) (terpri) (write (truncate 3456 75)) (terpri) (write (round 3456 75)) (terpri) (write (ffloor 3456 75)) (terpri) (write (fceiling 3456 75)) (terpri) (write (ftruncate 3456 75)) (terpri) (write (fround 3456 75)) (terpri) (write (mod 3456 75)) (terpri) (setq c (complex 6 7)) (write c) (terpri) (write (complex 5 -9)) (terpri) (write (realpart c)) (terpri) (write (imagpart c))
執行程式碼後,它將返回以下結果:
15/26 0 1152/25 46 47 46 46 46.0 47.0 46.0 46.0 6 #C(6 7) #C(5 -9) 6 7
LISP - 字元
在 LISP 中,字元表示為型別為 character 的資料物件。
您可以表示在字元本身之前用 #\ 字首的字元物件。例如,#\a 表示字元 a。
空格和其他特殊字元可以透過在字元名稱前加上 #\ 來表示。例如,#\SPACE 表示空格字元。
以下示例演示了這一點:
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write 'a) (terpri) (write #\a) (terpri) (write-char #\a) (terpri) (write-char 'a)
執行程式碼後,它將返回以下結果:
A #\a a *** - WRITE-CHAR: argument A is not a character
特殊字元
Common LISP 允許在程式碼中使用以下特殊字元。它們被稱為半標準字元。
- #\Backspace
- #\Tab
- #\Linefeed
- #\Page
- #\Return
- #\Rubout
字元比較函式
數值比較函式和運算子(如 < 和 >)不適用於字元。Common LISP 提供了另外兩組函式用於在程式碼中比較字元。
一組區分大小寫,另一組不區分大小寫。
下表提供了這些函式 -
區分大小寫函式 | 不區分大小寫函式 | 描述 |
---|---|---|
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 #\b)) (terpri) (write (char= #\a #\a)) (terpri) (write (char= #\a #\A)) (terpri) ;case-insensitive comparision (write (char-equal #\a #\A)) (terpri) (write (char-equal #\a #\b)) (terpri) (write (char-lessp #\a #\b #\c)) (terpri) (write (char-greaterp #\a #\b #\c))
執行程式碼後,它將返回以下結果:
NIL T NIL T NIL T NIL
LISP - 陣列
LISP 允許您使用 **make-array** 函式定義一維或多維陣列。陣列可以儲存任何 LISP 物件作為其元素。
所有陣列都由連續的記憶體位置組成。最低地址對應於第一個元素,最高地址對應於最後一個元素。

陣列的維度數稱為其秩。
在 LISP 中,陣列元素由一系列非負整數索引指定。序列的長度必須等於陣列的秩。索引從零開始。
例如,要建立一個包含 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
讓我們建立一個 3x3 的陣列。
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(setf x (make-array '(3 3) :initial-contents '((0 1 2 ) (3 4 5) (6 7 8))) ) (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)
make-array 函式的完整語法
make-array 函式接受許多其他引數。讓我們看一下此函式的完整語法 -
make-array dimensions :element-type :initial-element :initial-contents :adjustable :fill-pointer :displaced-to :displaced-index-offset
除了 *dimensions* 引數之外,所有其他引數都是關鍵字。下表提供了引數的簡要說明。
序號 | 引數 & 描述 |
---|---|
1 | dimensions 它給出陣列的維度。對於一維陣列,它是一個數字;對於多維陣列,它是一個列表。 |
2 | :element-type 它是型別說明符,預設值為 T,即任何型別 |
3 | :initial-element 初始元素值。它將建立一個數組,所有元素都初始化為特定值。 |
4 | :initial-content 作為物件的初始內容。 |
5 | :adjustable 它有助於建立可調整大小(或可調整)的向量,其底層記憶體可以調整大小。該引數是一個布林值,指示陣列是否可調整,預設值為 NIL。 |
6 | :fill-pointer 它跟蹤可調整大小向量中實際儲存的元素數量。 |
7 | :displaced-to 它有助於建立與指定陣列共享其內容的置換陣列或共享陣列。這兩個陣列應具有相同的元素型別。:displaced-to 選項不能與 :initial-element 或 :initial-contents 選項一起使用。此引數預設為 nil。 |
8 | :displaced-index-offset 它給出建立的共享陣列的索引偏移量。 |
示例 4
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(setq myarray (make-array '(3 2 3) :initial-contents '(((a b c) (1 2 3)) ((d e f) (4 5 6)) ((g h i) (7 8 9)) )) ) (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) (1 2 3)) ((d e f) (4 5 6)) ((g h i) (7 8 9)) )) ) (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) (1 2 3)) ((d e f) (4 5 6)) ((g h i) (7 8 9)) )) ) (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 with 5 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 pointer 5, is 5 (write(length (make-array 14 :fill-pointer 5))) (terpri) ;however its length is 14 (write (array-dimensions (make-array 14 :fill-pointer 5))) (terpri) ; a bit array with all initial elements set to 1 (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))
LISP - 字串
Common Lisp 中的字串是向量,即字元的一維陣列。
字串文字用雙引號括起來。字元集中支援的任何字元都可以用雙引號括起來以構成字串,除了雙引號字元 (") 和跳脫字元 (\)。但是,您可以透過使用反斜槓 (\) 對它們進行轉義來包含它們。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write-line "Hello World") (write-line "Welcome to Tutorials Point") ;escaping the double quote character (write-line "Welcome to \"Tutorials Point\"")
執行程式碼後,它將返回以下結果:
Hello World Welcome to Tutorials Point Welcome to "Tutorials Point"
字串比較函式
數值比較函式和運算子(如 < 和 >)不適用於字串。Common LISP 提供了另外兩組函式用於在程式碼中比較字串。一組區分大小寫,另一組不區分大小寫。
下表提供了這些函式 -
區分大小寫函式 | 不區分大小寫函式 | 描述 |
---|---|---|
string= | string-equal | 檢查運算元的值是否都相等,如果是,則條件變為真。 |
string/= | string-not-equal | 檢查運算元的值是否都不同,如果值不相等,則條件變為真。 |
string< | string-lessp | 檢查運算元的值是否單調遞減。 |
string> | string-greaterp | 檢查運算元的值是否單調遞增。 |
string<= | string-not-greaterp | 檢查任何左側運算元的值是否大於或等於其右側下一個運算元的值,如果是,則條件變為真。 |
string>= | string-not-lessp | 檢查任何左側運算元的值是否小於或等於其右側運算元的值,如果是,則條件變為真。 |
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
; case-sensitive comparison (write (string= "this is test" "This is test")) (terpri) (write (string> "this is test" "This is test")) (terpri) (write (string< "this is test" "This is test")) (terpri) ;case-insensitive comparision (write (string-equal "this is test" "This is test")) (terpri) (write (string-greaterp "this is test" "This is test")) (terpri) (write (string-lessp "this is test" "This is test")) (terpri) ;checking non-equal (write (string/= "this is test" "this is Test")) (terpri) (write (string-not-equal "this is test" "This is test")) (terpri) (write (string/= "lisp" "lisping")) (terpri) (write (string/= "decent" "decency"))
執行程式碼後,它將返回以下結果:
NIL 0 NIL T NIL NIL 8 NIL 4 5
大小寫控制函式
下表描述了大小寫控制函式 -
序號 | 函式和描述 |
---|---|
1 | string-upcase 將字串轉換為大寫 |
2 | string-downcase 將字串轉換為小寫 |
3 | string-capitalize 將字串中每個單詞的首字母大寫 |
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write-line (string-upcase "a big hello from tutorials point")) (write-line (string-capitalize "a big hello from tutorials point"))
執行程式碼後,它將返回以下結果:
A BIG HELLO FROM TUTORIALS POINT A Big Hello From Tutorials Point
修剪字串
下表描述了字串修剪函式 -
序號 | 函式和描述 |
---|---|
1 | string-trim 它以字元字串作為第一個引數,以字串作為第二個引數,並返回一個子字串,其中第一個引數中的所有字元都從引數字串中刪除。 |
2 | String-left-trim 它以字元字串作為第一個引數,以字串作為第二個引數,並返回一個子字串,其中第一個引數中的所有字元都從引數字串的開頭刪除。 |
3 | String-right-trim 它以字元字串作為第一個引數,以字串作為第二個引數,並返回一個子字串,其中第一個引數中的所有字元都從引數字串的末尾刪除。 |
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write-line (string-trim " " " a big hello from tutorials point ")) (write-line (string-left-trim " " " a big hello from tutorials point ")) (write-line (string-right-trim " " " a big hello from tutorials point ")) (write-line (string-trim " a" " a big hello from tutorials point "))
執行程式碼後,它將返回以下結果:
a big hello from tutorials point a big hello from tutorials point a big hello from tutorials point big hello from tutorials point
其他字串函式
LISP 中的字串是陣列,因此也是序列。我們將在後續教程中介紹這些資料型別。所有適用於陣列和序列的函式也適用於字串。但是,我們將透過各種示例演示一些常用的函式。
計算長度
**length** 函式計算字串的長度。
提取子字串
**subseq** 函式返回一個子字串(因為字串也是一個序列),該子字串從特定索引開始,一直持續到特定結束索引或字串的末尾。
訪問字串中的字元
**char** 函式允許訪問字串的單個字元。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (length "Hello World")) (terpri) (write-line (subseq "Hello World" 6)) (write (char "Hello World" 6))
執行程式碼後,它將返回以下結果:
11 World #\W
字串的排序和合並
**sort** 函式允許對字串進行排序。它接受一個序列(向量或字串)和一個雙引數謂詞,並返回該序列的排序版本。
**merge** 函式接受兩個序列和一個謂詞,並返回一個序列,該序列是根據謂詞合併這兩個序列生成的。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
;sorting the strings (write (sort (vector "Amal" "Akbar" "Anthony") #'string<)) (terpri) ;merging the strings (write (merge 'vector (vector "Rishi" "Zara" "Priyanka") (vector "Anju" "Anuj" "Avni") #'string<))
執行程式碼後,它將返回以下結果:
#("Akbar" "Amal" "Anthony") #("Anju" "Anuj" "Avni" "Rishi" "Zara" "Priyanka")
反轉字串
**reverse** 函式反轉字串。
例如,建立一個名為 main.lisp 的新原始碼檔案,並在其中鍵入以下程式碼。
(write-line (reverse "Are we not drawn onward, we few, drawn onward to new era"))
執行程式碼後,它將返回以下結果:
are wen ot drawno nward ,wef ew ,drawno nward ton ew erA
連線字串
concatenate 函式連線兩個字串。這是一個通用的序列函式,您必須提供結果型別作為第一個引數。
例如,建立一個名為 main.lisp 的新原始碼檔案,並在其中鍵入以下程式碼。
(write-line (concatenate 'string "Are we not drawn onward, " "we few, drawn onward to new era"))
執行程式碼後,它將返回以下結果:
Are we not drawn onward, we few, drawn onward to new era
LISP - 序列
序列是 LISP 中的一種抽象資料型別。向量和列表是這種資料型別的兩個具體子型別。在序列資料型別上定義的所有功能實際上都應用於所有向量和列表型別。
在本節中,我們將討論序列上最常用的函式。
在開始以各種方式操作序列(即向量和列表)之前,讓我們先看一下所有可用函式的列表。
建立序列
make-sequence 函式允許您建立任何型別的序列。此函式的語法為 -
make-sequence sqtype sqsize &key :initial-element
它建立一個型別為 *sqtype* 且長度為 *sqsize* 的序列。
您可以選擇使用 *:initial-element* 引數指定一些值,然後每個元素都將初始化為該值。
例如,建立一個名為 main.lisp 的新原始碼檔案,並在其中鍵入以下程式碼。
(write (make-sequence '(vector float) 10 :initial-element 1.0))
執行程式碼後,它將返回以下結果:
#(1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0)
序列上的通用函式
序號 | 函式和描述 |
---|---|
1 | elt 它允許透過整數索引訪問單個元素。 |
2 | length 它返回序列的長度。 |
3 | subseq 它透過提取從特定索引開始並持續到特定結束索引或序列末尾的子序列來返回一個子序列。 |
4 | copy-seq 它返回一個序列,該序列包含與其引數相同的元素。 |
5 | fill 它用於將序列的多個元素設定為單個值。 |
6 | replace 它接受兩個序列,第一個引數序列透過從第二個引數序列中複製連續元素到其中來進行破壞性修改。 |
7 | count 它接受一個專案和一個序列,並返回該專案在序列中出現的次數。 |
8 | reverse 它返回一個序列,該序列包含與引數相同的元素,但順序相反。 |
9 | nreverse 它返回與序列相同的序列,包含與序列相同的元素,但順序相反。 |
10 | concatenate 它建立一個新的序列,包含任意數量序列的連線。 |
11 | position 它接受一個專案和一個序列,並返回該專案在序列中的索引或 nil。 |
12 | find 它接受一個專案和一個序列。它在序列中查詢該專案並返回它,如果未找到,則返回 nil。 |
13 | sort 它接受一個序列和一個雙引數謂詞,並返回該序列的排序版本。 |
14 | merge 它接受兩個序列和一個謂詞,並返回一個序列,該序列是根據謂詞合併這兩個序列生成的。 |
15 | map 它接受一個 n 引數函式和 n 個序列,並返回一個新序列,其中包含將該函式應用於序列後續元素的結果。 |
16 | some 它接受一個謂詞作為引數,並遍歷引數序列,並返回謂詞返回的第一個非 NIL 值,或者如果謂詞從未滿足,則返回 false。 |
17 | every 它接受一個謂詞作為引數,並遍歷引數序列,一旦謂詞失敗,它就會終止並返回 false。如果謂詞始終滿足,則返回 true。 |
18 | notany 它接受一個謂詞作為引數,並遍歷引數序列,一旦謂詞滿足就返回 false,或者如果從未滿足就返回 true。 |
19 | notevery 它接受一個謂詞作為引數,並遍歷引數序列,一旦謂詞失敗就返回 true,或者如果謂詞始終滿足就返回 false。 |
20 | reduce 它對映到單個序列,首先將雙引數函式應用於序列的前兩個元素,然後將函式返回的值和序列的後續元素應用於該值。 |
21 | search 它搜尋序列以定位一個或多個滿足某些測試的元素。 |
22 | remove 它接受一個專案和一個序列,並返回刪除了專案例項的序列。 |
23 | delete 它也接受一個專案和一個序列,並返回一個與引數序列型別相同的序列,該序列具有相同的元素,除了該專案。 |
24 | substitute 它接受一個新專案、一個現有專案和一個序列,並返回一個序列,其中現有專案的例項被新專案替換。 |
25 | nsubstitute 它接受一個新專案、一個現有專案和一個序列,並返回與該序列相同的序列,其中現有專案的例項被新專案替換。 |
26 | mismatch 它接受兩個序列,並返回第一個不匹配元素對的索引。 |
標準序列函式關鍵字引數
引數 | 含義 | 預設值 |
---|---|---|
:test | 它是一個雙引數函式,用於將專案(或 :key 函式提取的值)與元素進行比較。 | EQL |
:鍵(:key) | 一個引數函式,用於從實際序列元素中提取鍵值。NIL 表示按原樣使用元素。 | NIL |
:開始(:start) | 子序列的起始索引(包含)。 | 0 |
:結束(:end) | 子序列的結束索引(不包含)。NIL 表示序列的末尾。 | NIL |
:從末尾(:from-end) | 如果為真,則序列將以相反的順序遍歷,從末尾到開頭。 | NIL |
:計數(:count) | 指示要刪除或替換的元素數量,或 NIL 表示全部(僅限 REMOVE 和 SUBSTITUTE)。 | NIL |
我們剛剛討論了各種用作這些函式引數的函式和關鍵字,這些函式作用於序列。在接下來的章節中,我們將透過示例瞭解如何使用這些函式。
查詢長度和元素
length 函式返回序列的長度,elt 函式允許您使用整數索引訪問單個元素。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(setq x (vector 'a 'b 'c 'd 'e)) (write (length x)) (terpri) (write (elt x 3))
執行程式碼後,它將返回以下結果:
5 D
修改序列
一些序列函式允許遍歷序列並執行某些操作,例如搜尋、刪除、計數或過濾特定元素,而無需編寫顯式迴圈。
以下示例演示了這一點:
示例 1
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (count 7 '(1 5 6 7 8 9 2 7 3 4 5))) (terpri) (write (remove 5 '(1 5 6 7 8 9 2 7 3 4 5))) (terpri) (write (delete 5 '(1 5 6 7 8 9 2 7 3 4 5))) (terpri) (write (substitute 10 7 '(1 5 6 7 8 9 2 7 3 4 5))) (terpri) (write (find 7 '(1 5 6 7 8 9 2 7 3 4 5))) (terpri) (write (position 5 '(1 5 6 7 8 9 2 7 3 4 5)))
執行程式碼後,它將返回以下結果:
2 (1 6 7 8 9 2 7 3 4) (1 6 7 8 9 2 7 3 4) (1 5 6 10 8 9 2 10 3 4 5) 7 1
示例 2
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (delete-if #'oddp '(1 5 6 7 8 9 2 7 3 4 5))) (terpri) (write (delete-if #'evenp '(1 5 6 7 8 9 2 7 3 4 5))) (terpri) (write (remove-if #'evenp '(1 5 6 7 8 9 2 7 3 4 5) :count 1 :from-end t)) (terpri) (setq x (vector 'a 'b 'c 'd 'e 'f 'g)) (fill x 'p :start 1 :end 4) (write x)
執行程式碼後,它將返回以下結果:
(6 8 2 4) (1 5 7 9 7 3 5) (1 5 6 7 8 9 2 7 3 5) #(A P P P E F G)
排序和合並序列
排序函式接受一個序列和一個雙引數謂詞,並返回該序列的排序版本。
示例 1
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (sort '(2 4 7 3 9 1 5 4 6 3 8) #'<)) (terpri) (write (sort '(2 4 7 3 9 1 5 4 6 3 8) #'>)) (terpri)
執行程式碼後,它將返回以下結果:
(1 2 3 3 4 4 5 6 7 8 9) (9 8 7 6 5 4 4 3 3 2 1)
示例 2
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (merge 'vector #(1 3 5) #(2 4 6) #'<)) (terpri) (write (merge 'list #(1 3 5) #(2 4 6) #'<)) (terpri)
執行程式碼後,它將返回以下結果:
#(1 2 3 4 5 6) (1 2 3 4 5 6)
序列謂詞
every、some、notany 和 notevery 函式稱為序列謂詞。
這些函式迭代序列並測試布林謂詞。
所有這些函式都將謂詞作為第一個引數,其餘引數是序列。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (every #'evenp #(2 4 6 8 10))) (terpri) (write (some #'evenp #(2 4 6 8 10 13 14))) (terpri) (write (every #'evenp #(2 4 6 8 10 13 14))) (terpri) (write (notany #'evenp #(2 4 6 8 10))) (terpri) (write (notevery #'evenp #(2 4 6 8 10 13 14))) (terpri)
執行程式碼後,它將返回以下結果:
T T NIL NIL T
對映序列
我們已經討論了對映函式。類似地,map 函式允許您將函式應用於一個或多個序列的後續元素。
map 函式接受一個 n 引數函式和 n 個序列,並在將函式應用於序列的後續元素後返回一個新序列。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (map 'vector #'* #(2 3 4 5) #(3 5 4 8)))
執行程式碼後,它將返回以下結果:
#(6 15 16 40)
LISP - 列表
在傳統的 LISP 中,列表是最重要和最主要的複合資料結構。當今的 Common LISP 提供了其他資料結構,例如向量、雜湊表、類或結構。
列表是單向連結串列。在 LISP 中,列表被構建為一個名為 cons 的簡單記錄結構的鏈,這些結構連結在一起。
cons 記錄結構
cons 是一種記錄結構,包含兩個稱為 car 和 cdr 的元件。
Cons 單元或 cons 是使用函式 cons 建立的值對物件。
cons 函式接受兩個引數並返回一個新的 cons 單元,其中包含這兩個值。這些值可以是任何型別物件的引用。
如果第二個值不是 nil 或另一個 cons 單元,則這些值將列印為括號括起來的一對點對。
cons 單元中的兩個值分別稱為 car 和 cdr。car 函式用於訪問第一個值,cdr 函式用於訪問第二個值。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (cons 1 2)) (terpri) (write (cons 'a 'b)) (terpri) (write (cons 1 nil)) (terpri) (write (cons 1 (cons 2 nil))) (terpri) (write (cons 1 (cons 2 (cons 3 nil)))) (terpri) (write (cons 'a (cons 'b (cons 'c nil)))) (terpri) (write ( car (cons 'a (cons 'b (cons 'c nil))))) (terpri) (write ( cdr (cons 'a (cons 'b (cons 'c nil)))))
執行程式碼後,它將返回以下結果:
(1 . 2) (A . B) (1) (1 2) (1 2 3) (A B C) A (B C)
上面的示例顯示瞭如何使用 cons 結構建立單向連結串列,例如,列表 (A B C) 由三個 cons 單元組成,這些單元透過它們的 cdr 連結在一起。
圖示如下:
LISP 中的列表
雖然 cons 單元可用於建立列表,但是,從巢狀的 cons 函式呼叫構建列表可能不是最佳解決方案。list 函式更常用於在 LISP 中建立列表。
list 函式可以接受任意數量的引數,並且由於它是一個函式,因此它會計算其引數。
first 和 rest 函式分別給出列表的第一個元素和其餘部分。以下示例演示了這些概念。
示例 1
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (list 1 2)) (terpri) (write (list 'a 'b)) (terpri) (write (list 1 nil)) (terpri) (write (list 1 2 3)) (terpri) (write (list 'a 'b 'c)) (terpri) (write (list 3 4 'a (car '(b . c)) (* 4 -2))) (terpri) (write (list (list 'a 'b) (list 'c 'd 'e)))
執行程式碼後,它將返回以下結果:
(1 2) (A B) (1 NIL) (1 2 3) (A B C) (3 4 A B -8) ((A B) (C D E))
示例 2
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(defun my-library (title author rating availability) (list :title title :author author :rating rating :availabilty availability) ) (write (getf (my-library "Hunger Game" "Collins" 9 t) :title))
執行程式碼後,它將返回以下結果:
"Hunger Game"
列表操作函式
下表提供了一些常用的列表操作函式。
序號 | 函式和描述 |
---|---|
1 | car 它將列表作為引數,並返回其第一個元素。 |
2 | cdr 它將列表作為引數,並返回一個沒有第一個元素的列表。 |
3 | cons單元 它接受兩個引數,一個元素和一個列表,並返回一個在第一個位置插入該元素的列表。 |
4 | 列表 它接受任意數量的引數,並返回一個以這些引數作為列表成員元素的列表。 |
5 | append 它將兩個或多個列表合併為一個。 |
6 | last 它接受一個列表並返回一個包含最後一個元素的列表。 |
7 | member 它接受兩個引數,其中第二個引數必須是一個列表,如果第一個引數是第二個引數的成員,則它返回從第一個引數開始的列表的其餘部分。 |
8 | reverse 它接受一個列表並返回一個頂部元素按相反順序排列的列表。 |
請注意,所有序列函式都適用於列表。
示例 3
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (car '(a b c d e f))) (terpri) (write (cdr '(a b c d e f))) (terpri) (write (cons 'a '(b c))) (terpri) (write (list 'a '(b c) '(e f))) (terpri) (write (append '(b c) '(e f) '(p q) '() '(g))) (terpri) (write (last '(a b c d (e f)))) (terpri) (write (reverse '(a b c d (e f))))
執行程式碼後,它將返回以下結果:
A (B C D E F) (A B C) (A (B C) (E F)) (B C E F P Q G) ((E F)) ((E F) D C B A)
car 和 cdr 函式的連線
car 和 cdr 函式及其組合允許提取列表的任何特定元素/成員。
但是,car 和 cdr 函式的序列可以透過在字母 c 和 r 內連線表示 car 的字母 a 和表示 cdr 的字母 d 來縮寫。
例如,我們可以編寫 cadadr 來縮寫函式呼叫序列 - car cdr car cdr。
因此,(cadadr '(a (c d) (e f g))) 將返回 d
示例 4
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(write (cadadr '(a (c d) (e f g)))) (terpri) (write (caar (list (list 'a 'b) 'c))) (terpri) (write (cadr (list (list 1 2) (list 3 4)))) (terpri)
執行程式碼後,它將返回以下結果:
D A (3 4)
LISP - 符號
在 LISP 中,符號是表示資料物件的名稱,有趣的是,它本身也是一個數據物件。
使符號特殊的是它們有一個稱為 屬性列表或 plist 的元件。
屬性列表
LISP 允許您為符號分配屬性。例如,讓我們有一個“person”物件。我們希望此“person”物件具有名稱、性別、身高、體重、地址、職業等屬性。屬性類似於屬性名稱。
屬性列表實現為一個具有偶數(可能為零)個元素的列表。列表中的每一對元素構成一個條目;第一項是 指示符,第二項是 值。
建立符號時,其屬性列表最初為空。屬性是使用 setf 形式中的 get 建立的。
例如,以下語句允許我們將屬性 title、author 和 publisher 以及相應的值分配給名為(符號)'book' 的物件。
示例 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)
LISP - 向量
向量是一維陣列,因此是陣列的子型別。向量和列表統稱為序列。因此,我們迄今為止討論的所有序列泛型函式和陣列函式都適用於向量。
建立向量
vector 函式允許您使用特定值建立固定大小的向量。它接受任意數量的引數,並返回一個包含這些引數的向量。
示例 1
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(setf v1 (vector 1 2 3 4 5)) (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 中未定義對其進行修改。因此,對於程式設計,您應該始終使用 vector 函式或更通用的函式 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)
填充指標
make-array 函式允許您建立可調整大小的向量。
函式的 fill-pointer 引數跟蹤實際儲存在向量中的元素數量。當您向向量新增元素時,它是下一個要填充的位置的索引。
vector-push 函式允許您將元素新增到可調整大小向量的末尾。它將填充指標增加 1。
vector-pop 函式返回最近推送的專案並將填充指標減少 1。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(setq a (make-array 5 :fill-pointer 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)
向量是序列,所有序列函式都適用於向量。請參閱序列章節,瞭解向量函式。
LISP - 集合
Common Lisp 沒有提供集合資料型別。但是,它提供了一些允許對列表執行集合操作的函式。
您可以根據各種條件新增、刪除和搜尋列表中的專案。您還可以執行各種集合操作,例如:並集、交集和集合差。
在 LISP 中實現集合
集合與列表一樣,通常以 cons 單元的形式實現。但是,正因為如此,集合越大,集合操作的效率就越低。
adjoin 函式允許您構建集合。它接受一個專案和一個表示集合的列表,並返回一個表示包含該專案和原始集合中所有專案的集合的列表。
adjoin 函式首先在給定列表中查詢該專案,如果找到,則返回原始列表;否則,它會建立一個新的 cons 單元,其 car 為該專案,cdr 指向原始列表,並返回此新列表。
adjoin 函式還接受 :key 和 :test 關鍵字引數。這些引數用於檢查該專案是否在原始列表中。
由於 adjoin 函式不會修改原始列表,因此要對列表本身進行更改,您必須將 adjoin 返回的值分配給原始列表,或者,您可以使用宏 pushnew 將專案新增到集合中。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
; creating myset as an empty list (defparameter *myset* ()) (adjoin 1 *myset*) (adjoin 2 *myset*) ; adjoin did not 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 (2 1) (2 1) (3 2 1)
檢查成員資格
member 函式組允許您檢查元素是否為集合的成員。
以下是這些函式的語法:
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) (2 5/3 'A) ('A 'B 'C)
集合並集
並集函式組允許您根據測試對作為這些函式引數提供的兩個列表執行集合並集。
以下是這些函式的語法:
union list1 list2 &key :test :test-not :key nunion list1 list2 &key :test :test-not :key
union 函式接受兩個列表並返回一個新列表,其中包含這兩個列表中存在的全部元素。如果存在重複項,則在返回的列表中僅保留一個成員的副本。
nunion 函式執行相同的操作,但可能會破壞引數列表。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(setq set1 (union '(a b c) '(c d e))) (setq set2 (union '(#(a b) #(5 6 7) #(f h)) '(#(5 6 7) #(a b) #(g h)) :test-not #'mismatch) ) (setq set3 (union '(#(a b) #(5 6 7) #(f h)) '(#(5 6 7) #(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 #'mismatch 引數,則 union 函式無法按預期工作。這是因為列表由 cons 單元組成,儘管這些值對我們來說看起來相同,但單元的 cdr 部分並不匹配,因此它們對 LISP 直譯器/編譯器來說並不完全相同。這就是不建議使用列表實現大型集合的原因。但是,它適用於小型集合。
集合交集
交集函式組允許您根據測試對作為這些函式引數提供的兩個列表執行交集。
以下是這些函式的語法:
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)) '(#(5 6 7) #(a b) #(g h)) :test-not #'mismatch) ) (setq set3 (intersection '(#(a b) #(5 6 7) #(f h)) '(#(5 6 7) #(a b) #(g h))) ) (write set1) (terpri) (write set2) (terpri) (write set3)
執行程式碼後,它將返回以下結果:
(C) (#(A B) #(5 6 7)) NIL
intersection 函式是 intersection 的破壞性版本,即它可能會破壞原始列表。
集合差
集合差函式組允許您根據測試對作為這些函式引數提供的兩個列表執行集合差。
以下是這些函式的語法:
set-difference list1 list2 &key :test :test-not :key nset-difference list1 list2 &key :test :test-not :key
集合差函式返回第一個列表中未出現在第二個列表中的元素列表。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(setq set1 (set-difference '(a b c) '(c d e))) (setq set2 (set-difference '(#(a b) #(5 6 7) #(f h)) '(#(5 6 7) #(a b) #(g h)) :test-not #'mismatch) ) (setq set3 (set-difference '(#(a b) #(5 6 7) #(f h)) '(#(5 6 7) #(a b) #(g h))) ) (write set1) (terpri) (write set2) (terpri) (write set3)
執行程式碼後,它將返回以下結果:
(A B) (#(F H)) (#(A B) #(5 6 7) #(F H))
LISP - 樹
您可以從 cons 單元格構建樹形資料結構,作為列表的列表。
要實現樹形結構,您必須設計能夠以特定順序遍歷 cons 單元格的功能,例如二叉樹的先序遍歷、中序遍歷和後序遍歷。
列表表示的樹
讓我們考慮一個由 cons 單元格組成的樹形結構,它形成了以下列表的列表:
((1 2) (3 4) (5 6)).
圖示如下:

LISP 中的樹函式
雖然大多數情況下您需要根據自己的具體需求編寫自己的樹功能,但 LISP 提供了一些可用的樹函式。
除了所有列表函式之外,以下函式特別適用於樹形結構:
序號 | 函式和描述 |
---|---|
1 | copy-tree x & optional vecp 它返回 cons 單元格樹 x 的副本。它遞迴地複製 car 和 cdr 方向。如果 x 不是 cons 單元格,則該函式只是返回未更改的 x。如果可選引數 vecp 為真,則此函式也會遞迴地複製向量以及 cons 單元格。 |
2 | tree-equal x y & key :test :test-not :key 它比較兩個 cons 單元格樹。如果 x 和 y 都是 cons 單元格,則它們的 car 和 cdr 會被遞迴比較。如果 x 和 y 都不是 cons 單元格,則它們透過 eql 進行比較,或者根據指定的測試進行比較。如果指定了 :key 函式,則將其應用於兩棵樹的元素。 |
3 | subst new old tree & key :test :test-not :key 它在 tree(一個 cons 單元格樹)中用 new 項替換給定 old 項的出現。 |
4 | nsubst new old tree & key :test :test-not :key 它的作用與 subst 相同,但會破壞原始樹。 |
5 | sublis alist tree & key :test :test-not :key 它的作用類似於 subst,不同之處在於它接受一個 alist,其中包含舊-新對的關聯列表。樹的每個元素(如果適用,則在應用 :key 函式後)都與 alist 的 car 進行比較;如果匹配,則將其替換為相應的 cdr。 |
6 | nsublis alist tree & key :test :test-not :key 它的作用類似於 sublis,但它是破壞性的版本。 |
示例 1
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(setq lst (list '(1 2) '(3 4) '(5 6))) (setq mylst (copy-list lst)) (setq tr (copy-tree lst)) (write lst) (terpri) (write mylst) (terpri) (write tr)
執行程式碼後,它將返回以下結果:
((1 2) (3 4) (5 6)) ((1 2) (3 4) (5 6)) ((1 2) (3 4) (5 6))
示例 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)
執行程式碼後,它將返回以下結果:
((1 2 (3 4 5) ((7 8) (7 8 9)))) ((7 2 (3 4 5) ((7 8) (7 8 9))))
構建您自己的樹
讓我們嘗試使用 LISP 中可用的列表函式構建自己的樹。
首先讓我們建立一個包含一些資料的新節點
(defun make-tree (item) "it creates a new node with item." (cons (cons item nil) nil) )
接下來讓我們向樹中新增一個子節點 - 它將獲取兩個樹節點並將第二個樹作為第一個樹的子節點新增。
(defun add-child (tree child) (setf (car tree) (append (car tree) child)) tree)
此函式將返回給定樹的第一個子節點 - 它將獲取一個樹節點並返回該節點的第一個子節點,或者如果該節點沒有任何子節點,則返回 nil。
(defun first-child (tree) (if (null tree) nil (cdr (car tree)) ) )
此函式將返回給定節點的下一個兄弟節點 - 它以樹節點作為引數,並返回對下一個兄弟節點的引用,或者如果該節點沒有任何兄弟節點,則返回 nil。
(defun next-sibling (tree) (cdr tree) )
最後,我們需要一個函式來返回節點中的資訊:
(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)) ) ) (defun next-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 (3 4 5) ((7 8) (7 8 9))) ((1 2 (3 4 5) ((7 8) (7 8 9)) (10)))
LISP - 雜湊表
雜湊表資料結構表示 鍵值對 的集合,這些鍵值對根據鍵的雜湊碼進行組織。它使用鍵來訪問集合中的元素。
當您需要使用鍵訪問元素,並且您可以識別有用的鍵值時,可以使用雜湊表。雜湊表中的每個專案都有一個鍵/值對。鍵用於訪問集合中的專案。
在 LISP 中建立雜湊表
在 Common LISP 中,雜湊表是一個通用集合。您可以使用任意物件作為鍵或索引。
當您將值儲存在雜湊表中時,您會建立一個鍵值對,並將其儲存在該鍵下。稍後,您可以使用相同的鍵從雜湊表中檢索該值。每個鍵對映到一個值,儘管您可以在鍵中儲存新值。
根據鍵的比較方式,LISP 中的雜湊表可以分為三種類型:eq、eql 或 equal。如果雜湊表是根據 LISP 物件進行雜湊的,則鍵將使用 eq 或 eql 進行比較。如果雜湊表是根據樹結構進行雜湊的,則將使用 equal 進行比較。
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 函式。
從雜湊表中檢索專案和向雜湊表中新增專案
gethash 函式透過搜尋其鍵來從雜湊表中檢索專案。如果找不到鍵,則返回 nil。
它具有以下語法:
gethash key hash-table &optional default
其中:
key:是關聯的鍵
hash-table:是要搜尋的雜湊表
default:是如果未找到條目則要返回的值,如果未指定,則為 nil。
gethash 函式實際上返回兩個值,第二個值是謂詞值,如果找到條目則為真,如果未找到條目則為假。
要向雜湊表中新增專案,您可以將 setf 函式與 gethash 函式一起使用。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(setq empList (make-hash-table)) (setf (gethash '001 empList) '(Charlie Brown)) (setf (gethash '002 empList) '(Freddie Seal)) (write (gethash '001 empList)) (terpri) (write (gethash '002 empList))
執行程式碼後,它將返回以下結果:
(CHARLIE BROWN) (FREDDIE SEAL)
刪除條目
remhash 函式刪除雜湊表中特定鍵的任何條目。這是一個謂詞,如果存在條目則為真,如果不存在則為假。
此函式的語法如下:
remhash key hash-table
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(setq empList (make-hash-table)) (setf (gethash '001 empList) '(Charlie Brown)) (setf (gethash '002 empList) '(Freddie Seal)) (setf (gethash '003 empList) '(Mark Mongoose)) (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
maphash 函式
maphash 函式允許您對雜湊表上的每個鍵值對應用指定的函式。
它接受兩個引數 - 函式和雜湊表,並在雜湊表中的每個鍵/值對上呼叫一次該函式。
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(setq empList (make-hash-table)) (setf (gethash '001 empList) '(Charlie Brown)) (setf (gethash '002 empList) '(Freddie Seal)) (setf (gethash '003 empList) '(Mark Mongoose)) (maphash #'(lambda (k v) (format t "~a => ~a~%" k v)) empList)
執行程式碼後,它將返回以下結果:
3 => (MARK MONGOOSE) 2 => (FREDDIE SEAL) 1 => (CHARLIE BROWN)
LISP - 輸入與輸出
Common LISP 提供了許多輸入輸出函式。我們已經使用 format 函式和 print 函式進行輸出。在本節中,我們將探討 LISP 提供的一些最常用的輸入輸出函式。
輸入函式
下表提供了 LISP 最常用的輸入函式:
序號 | 函式和描述 |
---|---|
1 | read & optional input-stream eof-error-p eof-value recursive-p 它從 input-stream 讀取 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 它從 input-stream 獲取一個字元,並將其作為字元物件返回。 |
5 | unread-char character & optional input-stream 它將最近從 input-stream 讀取的字元放在 input-stream 的前面。 |
6 | peek-char & optional peek-type input-stream eof-error-p eof-value recursive-p 它返回 input-stream 中要讀取的下一個字元,而無需實際將其從 input stream 中移除。 |
7 | listen & optional input-stream 謂詞 listen 如果 input-stream 中立即有字元可用,則為真,否則為假。 |
8 | read-char-no-hang & optional input-stream eof-error-p eof-value recursive-p 它類似於 read-char,但如果它沒有獲取字元,則不會等待字元,而是立即返回 nil。 |
9 | clear-input & optional input-stream 它清除與 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 它從 binary-input-stream 讀取一個位元組,並以整數的形式返回它。 |
從鍵盤讀取輸入
read 函式用於從鍵盤獲取輸入。它可能不接受任何引數。
例如,考慮以下程式碼片段:
(write ( + 15.0 (read)))
假設使用者從 STDIN 輸入中輸入 10.2,它將返回:
25.2
read 函式從輸入流中讀取字元,並透過解析將其解釋為 Lisp 物件的表示形式。
示例
建立一個名為 main.lisp 的新原始碼檔案,並在其中鍵入以下程式碼:
; the function AreaOfCircle ; 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)
執行程式碼後,它將返回以下結果:
Enter Radius: 5 (STDIN Input) Area: 78.53999
示例
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(with-input-from-string (stream "Welcome to Tutorials Point!") (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-char nil stream nil 'the-end)) (values) )
執行程式碼後,它將返回以下結果:
#\W #\e #\l #\c #\o #\m #\e #\Space #\t #\o #\Space
輸出函式
LISP 中的所有輸出函式都接受一個名為 output-stream 的可選引數,輸出將傳送到該引數。如果未提及或為 nil,則 output-stream 預設為變數 *standard-output* 的值。
下表提供了 LISP 最常用的輸出函式:
序號 | 函式和描述 |
---|---|
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 指定的輸出流,預設為 *standard-output* 的值。其他值預設為為列印設定的相應全域性變數。 |
2 |
prin1 object & optional output-stream print object & optional output-stream pprint object & optional output-stream princ object & optional output-stream 所有這些函式都將物件的打印表示形式輸出到 output-stream。但是,存在以下差異:
|
3 | write-to-string object & key :escape :radix :base :circle :pretty :level :length :case :gensym :array write-to-string object & 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 它將字元輸出到output-stream,並返回該字元。 |
5 | write-string string & optional output-stream & key :start :end 它將指定string子字串的字元寫入output-stream。 |
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
|
10 | write-byte integer binary-output-stream 它寫入一個位元組,即integer的值。 |
示例
建立一個名為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)
執行程式碼後,它將返回以下結果:
Enter Number : 3456.78 (STDIN Input) The Number: 3456.78 The Number Doubled: 6913.56
格式化輸出
函式format用於生成格式良好的文字。它具有以下語法:
format destination control-string &rest arguments
其中,
- destination是標準輸出
- control-string包含要輸出的字元和列印指令。
格式指令由一個波浪號(~)、可選的字首引數(用逗號分隔)、可選的冒號(:)和at符號(@)修飾符以及一個指示此指令型別的單個字元組成。
字首引數通常是整數,表示為帶符號或無符號的十進位制數。
下表簡要描述了常用的指令:
序號 | 指令 & 描述 |
---|---|
1 | ~A 後跟ASCII引數。 |
2 | ~S 後跟S表示式。 |
3 | ~D 用於十進位制引數。 |
4 | ~B 用於二進位制引數。 |
5 | ~O 用於八進位制引數。 |
6 | ~X 用於十六進位制引數。 |
7 | ~C 用於字元引數。 |
8 | ~F 用於定點格式浮點數引數。 |
9 | ~E 指數浮點數引數。 |
10 | ~$ 美元符號和浮點數引數。 |
11 | ~% 列印一個換行符。 |
12 | ~* 忽略下一個引數。 |
13 | ~? 間接定址。下一個引數必須是一個字串,再下一個引數必須是一個列表。 |
示例
讓我們重寫計算圓面積的程式:
建立一個名為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)
執行程式碼後,它將返回以下結果:
Enter Radius: 10.234 (STDIN Input) Radius: = 10.234 Area = 329.03473
LISP - 檔案 I/O
我們已經討論了Common Lisp如何處理標準輸入和輸出。所有這些函式也適用於從文字和二進位制檔案讀取以及寫入文字和二進位制檔案。唯一的區別是,在這種情況下,我們使用的流不是標準輸入或輸出,而是為寫入或讀取檔案而建立的流。
在本章中,我們將瞭解Lisp如何建立、開啟、關閉文字或二進位制檔案以進行資料儲存。
檔案表示一系列位元組,無論它是文字檔案還是二進位制檔案。本章將引導您瞭解檔案管理的重要函式/宏。
開啟檔案
您可以使用open函式建立新檔案或開啟現有檔案。它是開啟檔案的最基本函式。但是,with-open-file通常更方便且更常用,我們將在本節後面看到。
開啟檔案時,會構造一個流物件以在Lisp環境中表示它。對流的所有操作基本上等同於對檔案的操作。
open函式的語法如下:
open filename &key :direction :element-type :if-exists :if-does-not-exist :external-format
其中,
filename引數是要開啟或建立的檔案的名稱。
keyword引數指定流的型別和錯誤處理方式。
:direction關鍵字指定流是否應處理輸入、輸出或兩者,它採用以下值:
:input - 用於輸入流(預設值)
:output - 用於輸出流
:io - 用於雙向流
:probe - 用於僅檢查檔案是否存在;流被開啟然後關閉。
:element-type指定流的事務單元的型別。
:if-exists引數指定如果:direction為:output或:io並且指定名稱的檔案已存在,則應採取的操作。如果方向為:input或:probe,則忽略此引數。它採用以下值:
:error - 它發出錯誤訊號。
:new-version - 它建立一個具有相同名稱但版本號更大的新檔案。
:rename - 它重新命名現有檔案。
:rename-and-delete - 它重新命名現有檔案,然後將其刪除。
:append - 它追加到現有檔案。
:supersede - 它取代現有檔案。
nil - 它不建立檔案甚至流,只是返回nil以指示失敗。
:if-does-not-exist引數指定如果指定名稱的檔案不存在,則應採取的操作。它採用以下值:
:error - 它發出錯誤訊號。
:create - 它建立一個具有指定名稱的空檔案,然後使用它。
nil - 它不建立檔案甚至流,而是簡單地返回nil以指示失敗。
:external-format引數指定實現識別的用於在檔案中表示字元的方案。
例如,您可以開啟儲存在/tmp資料夾中的名為myfile.txt的檔案,如下所示:
(open "/tmp/myfile.txt")
寫入和讀取檔案
with-open-file允許讀取或寫入檔案,使用與讀/寫事務關聯的流變數。工作完成後,它會自動關閉檔案。使用它非常方便。
它具有以下語法:
with-open-file (stream filename {options}*) {declaration}* {form}*
filename是要開啟的檔案的名稱;它可以是字串、路徑名或流。
options與函式open的關鍵字引數相同。
示例 1
建立一個名為main.lisp的新原始碼檔案,並在其中鍵入以下程式碼。
(with-open-file (stream "/tmp/myfile.txt" :direction :output) (format stream "Welcome to Tutorials Point!") (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 Point! This is a tutorials database Submit your Tutorials, White Papers and Articles into our Tutorials Directory.
關閉檔案
close函式關閉流。
LISP - 結構體
結構是使用者定義的資料型別之一,它允許您組合不同型別的資料項。
結構用於表示記錄。假設您想跟蹤圖書館中的書籍。您可能希望跟蹤每本書的以下屬性:
- 標題
- 作者
- 主題
- 圖書ID
定義結構
Lisp中的defstruct宏允許您定義抽象記錄結構。defstruct語句定義了一種新的資料型別,為您的程式提供了多個成員。
為了討論defstruct宏的格式,讓我們編寫Book結構的定義。我們可以將book結構定義為:
(defstruct book title author subject book-id )
請注意
上述宣告建立了一個具有四個命名元件的book結構。因此,建立的每個book都將是此結構的物件。
它定義了四個名為book-title、book-author、book-subject和book-book-id的函式,這些函式將接受一個引數(一個book結構),並返回book物件的title、author、subject和book-id欄位。這些函式稱為訪問函式。
符號book成為一種資料型別,您可以使用typep謂詞來檢查它。
還將有一個名為book-p的隱式函式,它是一個謂詞,如果其引數是book則為真,否則為假。
將建立另一個名為make-book的隱式函式,它是一個建構函式,當被呼叫時,它將建立一個具有四個元件的資料結構,適合與訪問函式一起使用。
#S語法指的是結構,您可以使用它來讀取或列印book的例項。
還定義了一個名為copy-book的隱式函式,它只有一個引數。它接受一個book物件並建立一個另一個book物件,它是第一個物件的副本。此函式稱為複製函式。
您可以使用setf來更改book的元件,例如
(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 100)
LISP - 包
在程式語言的一般術語中,包旨在提供一種方法來將一組名稱與另一組名稱分開。在一個包中宣告的符號不會與另一個包中宣告的相同符號衝突。這樣,包減少了獨立程式碼模組之間的命名衝突。
Lisp讀取器維護一個包含其找到的所有符號的表。當它找到一個新的字元序列時,它會建立一個新符號並將其儲存在符號表中。此表稱為包。
當前包由特殊變數*package*引用。
Lisp中有兩個預定義的包:
common-lisp - 它包含所有定義的函式和變數的符號。
common-lisp-user - 它使用common-lisp包和所有其他包以及編輯和除錯工具;簡稱cl-user
Lisp中的包函式
下表提供了用於建立、使用和操作包的最常用函式:
序號 | 函式和描述 |
---|---|
1 | make-package package-name &key :nicknames :use 它建立並返回一個具有指定包名稱的新包。 |
2 | in-package package-name &key :nicknames :use 使包成為當前包。 |
3 | in-package name 此宏導致*package*設定為名為name的包,name必須是符號或字串。 |
4 |
find-package name 它搜尋包。返回具有該名稱或暱稱的包;如果不存在此類包,則find-package返回nil。 |
5 |
rename-package package new-name &optional new-nicknames 它重新命名包。 |
6 |
list-all-packages 此函式返回Lisp系統中當前存在的所有包的列表。 |
7 |
delete-package package 它刪除包。 |
建立Lisp包
defpackage函式用於建立使用者定義的包。它具有以下語法:
(defpackage :package-name (:use :common-lisp ...) (:export :symbol1 :symbol2 ...) )
其中,
package-name是包的名稱。
:use關鍵字指定此包需要的包,即定義此包中的程式碼使用的函式的包。
:export關鍵字指定在此包中外部的符號。
make-package函式也用於建立包。此函式的語法如下:
make-package package-name &key :nicknames :use
引數和關鍵字與之前含義相同。
使用包
建立包後,您可以透過將其設為當前包來使用此包中的程式碼。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 Point") ) (hello) (in-package dick) (defun hello () (write-line "Hello! This is Dick's Tutorials Point") ) (hello) (in-package harry) (defun hello () (write-line "Hello! This is Harry's Tutorials Point") ) (hello) (in-package tom) (hello) (in-package dick) (hello) (in-package harry) (hello)
執行程式碼後,它將返回以下結果:
Hello! This is Tom's Tutorials Point Hello! This is Dick's Tutorials Point Hello! This is Harry's Tutorials Point
刪除包
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 Point") ) (in-package dick) (defun hello () (write-line "Hello! This is Dick's Tutorials Point") ) (in-package harry) (defun hello () (write-line "Hello! This is Harry's Tutorials Point") ) (in-package tom) (hello) (in-package dick) (hello) (in-package harry) (hello) (delete-package tom) (in-package tom) (hello)
執行程式碼後,它將返回以下結果:
Hello! This is Tom's Tutorials Point Hello! This is Dick's Tutorials Point Hello! This is Harry's Tutorials Point *** - EVAL: variable TOM has no value
LISP - 錯誤處理
在Common Lisp術語中,異常稱為條件。
事實上,在傳統的程式語言中,條件比異常更普遍,因為一個條件表示任何可能影響函式呼叫棧各個級別的事件、錯誤或非錯誤。
LISP 中的條件處理機制以這樣一種方式處理此類情況:條件用於發出警告(例如列印警告),而呼叫棧上的上層程式碼可以繼續其工作。
LISP 中的條件處理系統包含三個部分:
- 發出條件訊號
- 處理條件
- 重新啟動程序
處理條件
讓我們以處理由除以零條件引起的條件的示例來解釋這裡的概念。
處理條件需要執行以下步驟:
定義條件 - “條件是一個物件,其類指示條件的總體性質,其例項資料攜帶有關導致發出條件的特定情況的詳細資訊。”
define-condition 宏用於定義條件,其語法如下:
(define-condition condition-name (error) ((text :initarg :text :reader text)) )
新的條件物件使用 MAKE-CONDITION 宏建立,該宏根據:initargs引數初始化新條件的槽。
在我們的示例中,以下程式碼定義了條件:
(define-condition on-division-by-zero (error) ((message :initarg :message :reader message)) )
編寫處理程式 - 條件處理程式是用於處理發出訊號的條件的程式碼。它通常編寫在呼叫錯誤函式的較高層函式之一中。當發出條件訊號時,發出訊號機制根據條件的類搜尋合適的處理程式。
每個處理程式包含:
- 型別說明符,指示它可以處理的條件型別
- 一個函式,它接受一個引數,即條件
當發出條件訊號時,發出訊號機制找到與條件型別相容的最近建立的處理程式,並呼叫其函式。
handler-case 宏建立條件處理程式。handler-case 的基本形式:
(handler-case expression error-clause*)
其中,每個錯誤子句都具有以下形式:
condition-type ([var]) code)
重新啟動階段
這是實際從錯誤中恢復程式的程式碼,然後條件處理程式可以透過呼叫適當的重啟來處理條件。重啟程式碼通常放置在中間層或底層函式中,條件處理程式放置在應用程式的上層。
handler-bind 宏允許您提供一個重啟函式,並允許您在底層函式中繼續,而無需展開函式呼叫棧。換句話說,控制流仍然在底層函式中。
handler-bind 的基本形式如下:
(handler-bind (binding*) form*)
其中每個繫結都是以下內容的列表:
- 條件型別
- 一個帶有一個引數的處理程式函式
invoke-restart 宏查詢並呼叫最近繫結的重啟函式,並使用指定的名稱作為引數。
您可以有多個重啟。
示例
在此示例中,我們透過編寫一個名為 division-function 的函式來演示上述概念,如果除數引數為零,該函式將建立錯誤條件。我們有三個匿名函式,它們提供了三種退出方式 - 返回值 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-function 10 0)) (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.
除了上面討論的“條件系統”之外,Common LISP 還提供了各種可以用於發出錯誤訊號的函式。但是,發出訊號後錯誤的處理方式取決於實現。
LISP 中的錯誤發出訊號函式
下表提供了常用函式,用於發出警告、中斷、非致命和致命錯誤的訊號。
使用者程式指定錯誤訊息(字串)。這些函式處理此訊息,並且可能會或可能不會將其顯示給使用者。
錯誤訊息應透過應用format函式來構造,在開頭或結尾處不應包含換行符,並且不必指示錯誤,因為 LISP 系統將根據其首選樣式處理這些錯誤。
序號 | 函式和描述 |
---|---|
1 |
error format-string &rest args 它發出致命錯誤訊號。無法從這種錯誤中繼續;因此,error 永遠不會返回到其呼叫方。 |
2 |
cerror continue-format-string error-format-string &rest args 它發出錯誤訊號並進入偵錯程式。但是,它允許程式在解決錯誤後從偵錯程式中繼續。 |
3 |
warn format-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 *** - -1 is a negative number.
LISP - CLOS
Common LISP 比面向物件程式設計的進步早了幾十年。但是,面向物件在後期被整合到其中。
定義類
defclass 宏允許建立使用者定義的類。它將類建立為資料型別。其語法如下:
(defclass class-name (superclass-name*) (slot-description*) class-option*))
槽是儲存資料或欄位的變數。
槽描述具有 (slot-name slot-option*) 的形式,其中每個選項都是一個關鍵字,後跟名稱、表示式和其他選項。最常用的槽選項是:
:accessor function-name
:initform expression
:initarg symbol
例如,讓我們定義一個 Box 類,它包含三個槽 length、breadth 和 height。
(defclass Box () (length breadth height) )
為槽提供訪問和讀/寫控制
除非槽具有可以訪問、讀取或寫入的值,否則類幾乎沒有用處。
您可以在定義類時為每個槽指定訪問器。例如,以我們的 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 :writer set-height) ) )
建立類的例項
泛型函式make-instance建立並返回類的新的例項。
它具有以下語法:
(make-instance class {initarg value}*)
示例
讓我們建立一個 Box 類,它包含三個槽 length、breadth 和 height。我們將使用三個槽訪問器來設定這些欄位中的值。
建立一個名為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 Box is 10 Breadth of the Box is 10 Height of the Box is 5
定義類方法
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 Box is 10 Breadth of the Box is 10 Height of the Box is 5 Volume of the Box is 500
繼承
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 Wooden Box is 10 Breadth of the Wooden Box is 10 Height of the Wooden Box is 5 Volume of the Wooden Box is 500 Price of the Wooden Box is 1000