開放原始碼技術文件網 宣告和使用變數 (Variable)

前言

在電腦程式中,資料存在於記憶體。變數本身不是資料,而是資料的標籤。電腦程式藉由變數存取資料。本文介紹在 Common Lisp 中使用變數的方式。

宣告全域變數 (Global Variable)

雖然 Common Lisp 是函數式語言,必要時還是可以使用全域變數來控制程式的狀態。使用 defvar 可宣告變數。

以下範例宣告全域變數 *v*,其值為 5.0

(defvar *v* 5.0)

注意變數 *v** (星號,asterisk) 也是變數的一部分。Common Lisp 對識別字 (identifier) 的建立規則比一般程式語言還寬鬆,詳見下文。

依照慣例,在 Common Lisp 中建立全域變數時,會在變數中加上一對 * 符號。雖然這沒有強制性,這是一種常見的風格。這種寫法讓別的 Common Lisp 一看就知道該變數是全域變數。

如果要更動變數的值,可以用 setq 指令。承上,我們修改全琙變數 *v* 的值:

(setq *v* 7.0)

在 Common Lisp 中,有數個內建的全域變數,用來調整程式的設置。像是 *load-verbose* 變數可用來控制載入 Lisp 命令稿時,是否要輸出文字訊息。

宣告局部變數 (Local Variable)

比起全域變數,寫 Common Lisp 程式時更常使用局部變數。局部變數對程式的影響僅限於特定指令所在處,不太會因不當修改造成 bug。

從函式傳入的變數又稱為參數。以下函式 average 接收一個參數 seq。利用平均數的算式求得 seq 的元素的平均數:

(defun average (lst)
  "Get the average of a number list."
  (check-type lst list)
  (assert (every #'numberp lst))
  (/ (apply #'+ lst) (length lst)))

在計算平均值前,函式 average 用兩行指令檢查變數 lst 是數字型態列表 (list)。(check-type lst list) 會確認 list 的型態是列表。

(assert (every #'numberp lst)) 會檢查列表內的元素是否為數字。every 具有函數式程式的概念,該指令第一個參數是 predicate (註) 類型的函式,第二個參數則是序列 (含列表)。該指令的目的是確認序列的每個元素都符合 predicate。

(註) Predicate 是確認物件是否符合條件的函式,相當於「xx 是否為真?」。

在本例中,every 指令的第一個參數是 #'numberp#' (sharpsign single-quote) 等同於 function 指令,把函式 numberp 當成值傳給 every 指令。

實際計算平均值的指令是 (/ (apply #'+ lst) (length lst))。該指令會分別求得數字列表的和 (sum) 和數量 (count)。然後將兩者相除以求得平均數。

apply 指令也有函數式程式的概念。該指令的第一個變數是函數物件,第二個參數是可展開列表。本例的 apply 指令對列表使用 + 指令,即為相加列表的元素。

當我們需要局部變數,但不需要從該區域回傳值時,可以用 prog 指令。參考以下範例:

(prog ((x 3))
  (write-line (princ-to-string x)))

相對來說,當我們需要從區域回傳值時,則會改用 let 指令。以下函式用來建立範圍在 smalllarge 間的隨機整數:

(defun random-integer (small large &optional seed)
  "Get a random integer between small and large."
  (check-type small integer)
  (check-type large integer)
  (assert (< small large))
  (let (answer)
       (setq answer
             (+ small
                (random (1+ (- large small))
                        (or seed (make-random-state t)))))))

函式 random-integer 有三者變數 smalllarge 和 (選擇性的) state。前兩者用來決定隨機整數的範圍。state 則是接收外界傳入的隨機狀態物件。當外界沒有傳入隨機狀態時,該函式會自行生成新的隨機物件。

在計算隨機數前,會先確認 smalllarge 是合法的 (valid)。然後利用 random 指令來計算隨機整數。在預設情形下,random 指令取得的值是零至參數 (不含) 間的隨機數。如果要調整隨機數的範圍,得自行加減偏移值。

random 若沒有傳入隨機物件,每次計算的結果會相同。為了達到隨機的效果,要傳入隨機物件或建立隨機物件。隨機物件即為亂數演算法的隨機種子 (random seed)。

在求隨機數時,最好只用單一隨機種子,不要重覆建立隨機種子。當有多個程式需要共用隨機種子時,就從外界傳入隨機種子。反之,則在函式內直接建立隨機種子即可。

宣告常數 (Constant)

當我們希望變數的值在整個程式中固定不變時,可以用 defconstant 指令宣告常數,這時候 Common Lisp 編譯器會協助程式設計者檢查該變數是否有被修改到,並發出錯誤訊息。

例如,圓周率歐拉數的值是固定不變的,就很適合宣告成常數。以下程式碼片段宣告歐拉數 (Euler’s number):

(defconstant e 2.7182818284 "The base of natural logarithm")

Common Lisp 的識別字 (Identifier) 建立規則

在程式語言中,識別字是有特定意義的符號。除了程式語言內建的識別字外,程式設計者可藉由多種指令建立新的識別字。識別字可能代表變數、運算子、函式、類別、巨集等。Lisp 家族語言不嚴格區分運算子、函式、巨集等語法,比 Algol 家族語言 (註) 自由得多。

(註) 即 C 家族語言。

Common Lisp 的識別字的規則比其他程式語言寬鬆得多,其規則如下:

  • 不是數字
  • 不是套件名稱 (package marker) (註)
  • 不全由 . (dot) 組成

(註) 以 : 開頭的符號,如 :cl

實務上,大多數識別字會以小寫英文字母和 - (hyphen) 組成。雖然 Common Lisp 不區分大小寫,以小寫英文字母撰寫程式碼比較容易閱讀。

以下是一些例子:

  • amount:單一字
  • circle-area:複合字
  • integer-or-null-p:複合字,(慣例上) 用來命名 predicate
  • 1+:非數字
  • +:非文字符號
分享本文
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Yahoo
追蹤本站
Facebook Facebook Twitter