開源技術教學文件網 使用運算子 (Operator)

最後修改日期為 JUL 19, 2021

前言

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

本文仍會介紹一些在 Algol 家族語言視為運算子的符號,讓讀者學習幾個 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)

比較運算子

比較數字

以下是用在數字間比較大小關係的運算子:

  • =:相等 (註)
  • /=:不等
  • >:大於
  • >=:大於等於
  • <:小於
  • <=:小於等於
  • 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:不小於 (大於或等於)

通用的比較運算

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 聖經中,對相等運算有更詳盡的說明,有興趣的讀者可以自行查閱。

邏輯運算子

以下是 Common Lisp 的邏輯運算子:

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

and 的運算方式如下:

pqp and q
truetruetrue
truefalsefalse
falsetruefalse
falsefalsefalse

or 的運算方式如下:

pqp or q
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse

not 的運算方式如下:

pnot p
truefalse
falsetrue

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

(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)

二元運算子

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

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

logand 的運算方式如下:

pqp and q
111
100
010
000

logior 的運算方式如下:

pqp ior q
111
101
011
000

logxor 的運算方式如下:

pqp xor q
110
101
011
000

logeqv 的運算方式如下:

pqp eqv q
111
100
010
001

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

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

pcomp p
10
01

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

(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 是較高階抽象的語言,在其標準文件中未提到二元運算是否會比代數運算快。所以,不太需要刻意用二元運算來加速,只要在需要二元運算時才使用即可。

分享本文
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Yahoo
追蹤本站
Facebook Facebook Twitter