位元詩人 [Common Lisp] 程式設計教學:使用運算子 (Operator)

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

Lisp 家族語言 (含 Common Lisp) 不嚴格區分運算子和函式,因為 Lisp 使用前綴表示法,不需要區分兩者。像是 Common Lisp 的 + 就是函式而非運算子。

本文仍會介紹一些在 Algol 家族語言視為運算子的符號,讓讀者學習幾個 Common Lisp 中實用的指令。

代數運算 (Arithmetic Operation)

以下是 Common Lisp 提供的代數運算指令:

  • +:相加
  • -:相減
  • *:相乘
  • /:相除 (註)
  • mod:取餘數

(註) 整數相除會得到整數或有理數,不會自動轉為浮點數。

在 Common Lisp 中,代數運算是以函式實作,可接收兩個以上的運算元。例如,(+ 1 2 3 4 5) 會轉為 1 + 2 + 3 + 4 + 5,(- 1 2 3 4 5) 會轉為 1 - 2 - 3 - 4 - 5。

以下是使用例:

(defun main ()
  (assert (equal 6 (+ 1 2 3)))
  (assert (equal -4 (- 1 2 3)))
  (assert (equal 6 (* 1 2 3)))
  (assert (equal 1/6 (/ 1 2 3)))
  (assert (equal 3 (mod 3 4)))
  (quit))

(main)

二元運算 (Bitwise Operation)

二元運算是指使用二進位數的代數運算。以下是 Common Lisp 的二元運算指令:

  • logand:且 (bitwise and)
  • logior:或 (bitwise or)
  • logxor:互斥或 (bitwise exclusive or)
  • logeqv:相等 (bitwise equal)
  • ash:平移,可左移和右移
  • complement:取補數

logand 的運算方式如下:

p q p and q
1 1 1
1 0 0
0 1 0
0 0 0

logior 的運算方式如下:

p q p ior q
1 1 1
1 0 1
0 1 1
0 0 0

logxor 的運算方式如下:

p q p xor q
1 1 0
1 0 1
0 1 1
0 0 0

logeqv 的運算方式如下:

p q p eqv q
1 1 1
1 0 0
0 1 0
0 0 1

由於 logeqv 函式在其他語言較少見,讀者可注意一下。

complement (取補數) 的運算方式如下:

p comp p
1 0
0 1

在了解二元運算的運算方式後,可以參考以下實例:

(defun main ()
  #|    0000 0101
   and) 0000 0011
  ----------------
        0000 0001 |#
  (assert (= 1 (logand 5 3))) 
  #|    0000 0101
    or) 0000 0011
  ----------------
        0000 0111 |#
  (assert (= 7 (logior 5 3)))
  #|    0000 0101
   xor) 0000 0010
  ----------------
        0000 0110 |#
  (assert (= 6 (logxor 5 3)))
  #|<<) 0000 0101
  ----------------
        0000 1010 |#
  (assert (= 10 (ash 5 1)))
  #|>>) 0000 0101
  ----------------
        0000 0010 |#
  (assert (= 2 (ash 5 -1)))
  (quit))

(main)

二元運算在平時較少用,因為較不直觀。但二元運算比等效的代數運算還快,在低階運算 (low-level computing) 中大量使用二元運算來加速。

由於 Common Lisp 是較高階抽象的語言,在其標準文件中未提到二元運算是否會比代數運算快。所以,不太需要刻意用二元運算來加速,只要在需要二元運算時才使用即可。

比較運算 (Comparison Operation)

比較數字

以下是用在數字間比較大小關係的指令:

  • =:相等 (註)
  • /=:不等
  • >:大於
  • >=:大於等於
  • <:小於
  • <=:小於等於
  • max:最大值
  • min:最小值

(註) Common Lisp 有多個檢查值是否相等的函式,= 用於比較數字。

要注意比較運算可子放入多個值。例如

(> 3 2 1)
-> 3 > 2 > 1
-> 3 > 2 && 2 > 1

所以,在 Common Lisp 的比較運算子中,隱含著多個數之間的比較。

以下是使用例:

(defun main ()
  (assert (= 3 3 3))
  (assert (/= 3 4 5))
  (assert (> 3 2 1))
  (assert (>= 3 2 1))
  (assert (< 1 2 3))
  (assert (<= 1 2 3))
  (assert (= 5 (max 1 2 3 4 5)))
  (assert (= 1 (min 1 2 3 4 5)))
  (quit))

(main)

比較字元

以下字元比較函式會考慮大小寫差異:

  • char=:相等
  • char/=:不等
  • char<:小於
  • char<=:小於或等於
  • char>:大於
  • char>=:大於或等於

以下字元比較函式會忽略大小寫差異:

  • char-equal:相等
  • char-not-equal:不等
  • char-lessp:小於
  • char-not-greaterp:不大於 (小於或等於)
  • char-greaterp:大於
  • char-not-lessp:不小於 (大於或等於)

比較字串

以下字串比較函式會考慮大小寫差異:

  • string=:相等
  • string/=:不等
  • string<:小於
  • string<=:小於或等於
  • string>:大於
  • string>=:大於或等於

以下字串比較函式會忽略大小寫差異:

  • string-equal:相等
  • string-not-equal:不等
  • string-lessp:小於
  • string-not-greaterp:不大於 (小於或等於)
  • string-greaterp:大於
  • string-not-lessp:不小於 (大於或等於)

通用的比較運算 (Equality)

Common Lisp 對於相等的概念相當細微,所以使用多種函式來比較 (不同概念的) 相等。以下是 Common Lisp 的通用比較運算:

  • eq
    • 相同的物件 (記憶體位罝)
  • eql
    • 數字:相同值
    • 字元:相同值
    • 其他:等同 eq
  • equal
    • 數字:等同 eql
    • 字元:等同 eql
    • 字串:相同值,會區分大小寫
    • 列表:元素的值相同,會區分大小寫
  • equalp
    • equal 相等的皆相等
    • 字串:相同值,不區分大小寫
    • 列表:元素的值相同,不區分大小寫

在大部分情形下,使用 equal 是最安全的選擇。除非有明確的理由,才會去用其他的相等運算函式。

以下是實例,讀者可花點時間閱讀一下:

;; Comparison between two equal integers.
(let ((ia 4)
      (ib 4))
     ; implementation-dependent.
     ; (assert (eq ia ib))
     (assert (eql ia ib))
     (assert (equal ia ib))
     (assert (equalp ia ib)))

;; Comparison between two equal floats.
(let ((fa 4.0)
      (fb 4.0))
     ; implementation-dependent.
     ; (assert (eq fa fb))
     (assert (eql fa fb))
     (assert (equal fa fb))
     (assert (equalp fa fb)))

;; Comparison between two equal complex numbers.
(let ((ca #c(3 -4))
      (cb #c(3 -4)))
     ; implementation-dependent.
     ; (assert (eq ca cb))
     (assert (eql ca cb))
     (assert (equal ca cb))
     (assert (equalp ca cb)))

;; Comparison between two equal symbols.
(let ((sa 'sym)
      (sb 'sym))
     (assert (eq sa sb))
     (assert (eql sa sb))
     (assert (equal sa sb))
     (assert (equalp sa sb)))

;; Comparison between two equal strings.
(let ((sa "foo")
      (sb "foo"))
     ; implementation-dependent.
     ; (assert (eq sa sb))
     ; (assert (eql sa sb))
     (assert (equal sa sb))
     (assert (equalp sa sb)))

;; Comparison between two equal strings
;;  in case-insensitive context.
(let ((sa "foo")
      (sb "Foo"))
     ; implementation-dependent.
     ; (assert (eq sa sb))
     ; (assert (eql sa sb))
     (assert (not (equal sa sb)))
     (assert (equalp sa sb)))

;; Comparison between two element-wisely equal lists.
(let ((la '(a b c))
      (lb '(a b c)))
     (assert (not (eq la lb)))
     (assert (not (eql la lb)))
     (assert (equal la lb))
     (assert (equalp la lb)))

;; Comparison between two element-wisely equal lists
;;  in case-insensitive context.
(let ((la '("foo" "bar" "baz"))
      (lb '("Foo" "Bar" "baz")))
     (assert (not (eq la lb)))
     (assert (not (eql la lb)))
     (assert (not (equal la lb)))
     (assert (equalp la lb)))

在 Common Lisp the Language 這本 Common Lisp 聖經中,對相等運算有更詳盡的說明,有興趣的讀者可以自行查閱。

邏輯運算 (Logic Operation)

以下是 Common Lisp 的邏輯運算指令:

  • and:且 (logical and)
  • or:或 (logical or)
  • not:否 (logical not)

and 的運算方式如下:

p q p and q
true true true
true false false
false true false
false false false

or 的運算方式如下:

p q p or q
true true true
true false true
false true true
false false false

not 的運算方式如下:

p not p
true false
false true

以下是實例,讀者可和運算方式對照著看:

(defun main ()
  ; AND
  (assert (equal t (and t t)))
  (assert (equal nil (and t nil)))
  (assert (equal nil (and nil t)))
  (assert (equal nil (and nil nil)))
  ; OR
  (assert (equal t (or t t)))
  (assert (equal t (or t nil)))
  (assert (equal t (or nil t)))
  (assert (equal nil (or nil nil)))
  ; NOT
  (assert (equal nil (not t)))
  (assert (equal t (not nil)))
  (quit))

(main)
關於作者

位元詩人 (ByteBard) 是資訊領域碩士,專注於從探索到產品的開發過程,並以工具驅動的方式改善專案。喜歡以開源專案作為成果,回饋社群。

主要方向包括:自用工具的打磨 (dogfooding)、編譯器技術在工具開發中的應用,以及將研究轉化為可維護的開源成果。

除了技術之外,也喜歡日本料理和黑咖啡,偶爾自助旅行,將生活中的靈感融入技術隨筆。