位元詩人 [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) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。