基本类型

Table of Contents

Lisp 的数据组成:

  1. 原子
  2. CONS
  3. Bool类型
  4. NIL

Common Lisp 操作符分三类:

  1. 函数
  2. 特殊形式

1. 对象

1.1. defconstant

定义一个常量。Common Lisp 里常量名一般用“+”包围,最好是遵守这个命名约定,定义如下:

(defconstant name value &optional doc)

示例:

(defconstant +a-constant+ 10)
(defconstant +a-constant+ 10 "定义一个常量")
+a-constant+                            ; => 10

1.2. set

(set symbol value)

改变 symbol 的值。symbol 必须加单引号(用 setq 可以省略单引号)。

(set 'a 1)                              ; => 1
a                                       ; => 1

1.3. describe

查看对象的信息(包括结构体等),定义如下:

(describe obj &optional output)

示例:

;;; 查看 funcall 函数的信息
(describe 'funcall)
;; =>
;; COMMON-LISP:FUNCALL
;;   [symbol]

;; FUNCALL names a compiled function:
;;   Lambda-list: (FUNCTION &REST ARGUMENTS)
;;   Declared type: (FUNCTION ((OR FUNCTION SYMBOL) &REST T) *)
;;   Documentation:
;;     Call FUNCTION with the given ARGUMENTS.
;;   Known attributes: call, unwind, any
;;   Source file: SYS:SRC;CODE;EVAL.LISP

1.4. shiftf

(shiftf &rest args)

从右向左重新赋值:将最后一个表达式的值赋值给倒数第二个的 place,倒数第二个 place 的值赋值给倒数第三个 place,依次类推。

(defvar a 1)
(defvar b 2)
(defvar c 3)
(shiftf a b c 4)                        ; => 1
a                              ; => 2
b                              ; => 3
c                              ; => 4
;;;; 第二个例子:修改列表
(defvar x '(a b c))
(defvar y 'd)
(defvar z 0)
(shiftf z (nth 1 x) y)                  ; => 0
z                                       ; => B
x                                       ; => (A D Z)
y                                       ; => D

1.5. rotatef

(rotatef &rest args)

旋转式交换变量的值

注意:rotatef 有副作用,是直接修改符号的值

(defvar a 1)
(defvar b 2)
(defvar c 3)
a                                       ; => 1
b                              ; => 2
c                              ; => 3
(rotatef a b c)                ; => NIL
a                              ; => 2
b                              ; => 3
;;; 因为是轮旋交换变量,c 是最后个变量,所以应该取第一个变量的值
c                              ; => 1

技巧 1:交换两个变量的值:

(defvar var1 1)
(defvar var2 2)
(rotatef var1 var2)
var1                                    ; => 2
var2                                    ; => 1

技巧2:交换列表中的值:

(defvar a-list '(a 1 b 2 c 3))
(rotatef (nth 0 a-list) (nth 1 a-list)) ; => NIL
a-list                                  ; => (1 A B 2 C 3)

1.6. write-to-string

返回对象的字符打印形式,定义如下:

(write-to-string obj)

示例:

(write-to-string '(1 2 3))     ; => "(1 2 3)"
;; 可以作为数字和字符串的转换
(write-to-string 123)          ; => "123"
(write-to-string (make-hash-table)) ; => "#<HASH-TABLE :TEST EQL :COUNT 0 {10035DC8B3}>"
(write-to-string #'and) ; => "#<CLOSURE (:MACRO AND) {1000BDFB8B}>"
(write-to-string #(1 2 3))     ; => "#(1 2 3)"

2. 原子(Atom)

指不可分割的那部分,Lisp 是由列表组成的结构,列表是可分割的,因为它由一个个元素组成,那些元素就是原子,它们是组成列表的最小单位,除了列表外,都是原子,包括数组等结构都是原子。

谓词函数 atom 判断 object 是否是一个原子:

(atom "123")                   ; => T
(atom '1)                      ; => T
(atom "1")                     ; => T
(atom #\1)                     ; => T
(atom 1)                       ; => T
(atom nil)                     ; => T
;;; 非空列表不是原子
(atom '(1 2 3))                ; => NIL

3. 符号(Symbol)

每个符号(symbol)都属于某一个包(package)

符号属于某个包,叫“符号被 intern了”,可调用 intern 函数;不是所有符号都被 intern 了,没有被 intern 的符号是 uninterned,gensym 便是。

每个符号对象都有个名字称为“print name”

为什么使用符号?一个有值的符号被称作变量。

为什么不用字符串表示?因为字符串在对比时,要一个字符一个字符做判断,效率低;符号只用判断是否同一内存地址即可。另外符号可以帮助使用宏。

当我们第一次输入一个符号名字时,解析器就在当前package里创建了一个符号对象。可以用一段代码来测试:

(defun list-symbols ()
  (do-all-symbols (s)
    (when (eq *package*
              (symbol-package s))
      (print s))))

3.1. 符号引用

引用:拒绝被求值,(atom 'a) 表达的是代码,第一个元素叫操作符,其余的叫自变量;'(atom 'a) 表达的是数据,这种代码和数据用同样结构表达的叫作同像性。引用的作用就是区分代码和数据。'(atom a b c) 作为数据,它是一个 CONS,我们就可以定义操作这种结构的操作符了——car 和 cdr。

Lisp 定义的七个原始操作符:quote、atom、eq、car、cdr、cons 和 cond,除了 quote 和 cond,其他都叫作“函数”,因为它的自变量总是要被求值的,而 quote 是不求值,并且 cond 有自己的求值方式——它是一个特殊形式。

例,'foo 等同于(quote foo)。

3.2. 属性列表(property list)

属性列表现在已经基本上被 hash 取代了,因为太老了,起源于 Lisp 1.5。每个符号都有属性列表,详细见“数据结构”一节。

3.3. 相关函数

3.3.1. symbol-value

(symbol-value symbol)

返回符号对应的值。

(defvar hi "hi")
(symbol-value 'hi)                      ; => "hi"

4. 布尔(Boolean)

在 Common Lisp 中,有 4 种代表了 false:'()、()、'nil 和 nil:

(eq 'nil nil) ; => T
(eq 'nil ()) ; => T
(eq 'nil '()) ; => T
(eq nil '()) ; => T

nil:来自拉丁语,意指“什么也没有”。

可以用谓词函数 null 来判断是否为 nil:

(null nil)                              ; => T
;;; 空列表等同于 nil
(null '())                              ; => T
(null 1)                                ; => NIL

5. 字符(Character)

Common Lisp 有 STANDARD-CHAR 和 EXTENDED-CHAR 两种字符,ASCII 码范围内的字符属于 STANDARD-CHAR。

#\c 表示一个字符 c,每个字符都对应一个数值(ASCII 码)

(type-of #\a) ; => STANDARD-CHAR
(type-of #\哈) ; => EXTENDED-CHAR

5.1. character

返回对象的字符表示:

(character #\a)                         ; => #\a
;;; 字符串只能包含一个字符,包含多个字符会出错
(character "a")                         ; => #\a
(character 'a)                          ; => #\A
(character 100) ; 错误,不能是数字

注意,在 CLISP 实现里,如果参数为数字就返回对应的 ASCII 字符:

(character 97)                          ; => #\a
(character 0)                           ; => #\Null

5.2. char-code

(char-code character)

返回字符对应的 ASCII 码。

(char-code #\a)                         ; => 97
(char-code #\A)                         ; => 65

6. 字符串(String)

双引号包围起来的就是字符串,如:"123"。

这里主要记录一些字符串操作。

字符串连接

(concatenate 'string "a" "b") ; => "ab"

字符串替换

(defun string-replace (string replace new-string)
  (with-output-to-string (out)
    (loop for part-len = (length replace)
       for pos = 0 then (+ pos1 part-len)
       for pos1 = (search replace string
                          :start pos)
       do (write-string string out
                        :start pos
                        :end (or pos1 (length string)))
       when pos1 do (write-string new-string out)
       while pos1)))

按空格分割字符串

(defun split-string-by-space (string)
  "按空格分割字符串"
  (loop for i = 0 then (1+ n)
     for n = (position #\Space string :start i)
     collect (subseq string i n)
     while n))

列表转成字符串

(format nil "~{~A~}" '(1 2 3)) ; => "123"

join 操作

(format nil "~{~A~^.~}" '(1 2 3)) ; => "1.2.3"

遍历字符串每个字符

(loop for c across "hello world" do (print c))
;; 或:
(map nil (lambda (c) (print (char-code c))) "hello world")
;; 或:
(map nil (lambda (c) (print (char-code c))) "hello world")

6.1. 大小写字母转换

(string-downcase "Hello World") ; => "hello world"
(string-upcase "hello world") ; => "HELLO WORLD"

6.2. 删除字符串

string-trim (string-trim char-bag string)

从 string 左边或右边开始将 char-bag 指定的内容剔除掉。

(string-trim "hello" "hello, world")    ; => ", world"
(string-trim "world" "hello, world")    ; => "hello, "

string-left-trim (string-left-trim char-bag string)

从 string 左边开始将 char-bag 指定的内容剔除掉,如果 string 的起始内容和 char-bag 不一样,就原样返回,否则返回一个新的字符串。

;;; 只会剔除第一次匹配到的
(string-left-trim "Hi" "Hi,girl. Hi,boy.") ; => ",girl. Hi,boy."

string-right-trim (string-right-trim char-bag string)

同 string-left-trim 一样,只是方向是从右边。

(string-right-trim "boy." "Hi,girl. Hi,boy.") ; => "Hi,girl. Hi,"

6.3. string-capitalize

(string-capitalize stirng &key (start 0) end)

将字符串中英文单词首字母大写。

(string-capitalize "hello world")       ; => "Hello World"

6.4. make-string

(make-string count &key (element-type 'character) initial-element)

返回包含重复了 count 次的参数 initial-element 的字符串。

(make-string 10 :initial-element #\a)   ; => "aaaaaaaaaa"

注意:如果 initial-element 不指定值,各个 Common Lisp 实现对返回的结果是不一样的。

7. 数字(Number)

Common Lisp 提供了整数、浮点数、比值和复数四种数字类型,Common Lisp 的数字类型集成关系来源于数学思想里的术语。

一个 Interger 被称为 fixnum,fixnum 的值范围由 most-negative-fixnum 和 most-positive-fixnum 两个常量定义,每种 Common Lisp 实现上,这两个常量取值都不一定是一样的,其值不小于 -215 和 215。如果一个数值超过 fixnum 的范围,就变成 bignum 类型。

Common Lisp 实现了 4 种浮点类型:short-float、single-float、double-float 和 long-float,其范围大小都是由 most-negative-* 和 most-positive-* 定义

类型判断:

(typep most-positive-fixnum 'fixnum) ; => T
(typep (1+ most-positive-fixnum) 'bignum) ; => T

有理数(rational)类型包含了 integer 和 ratios 类型:

(rationalp 1) ; => T
(rationalp 1/2) ; => T
(rationalp 1.1) ; => NIL

实数(real)类型包含了 rational 和浮点型(floating-point):

(realp 1) ; => T
(realp 1.0) ; => T
(realp 1/2) ; => T

number 类型包含了 real 和复数:

(numberp #c(5 -1)) ; => T
(numberp (complex 1 2)) ; => T
(numberp 1) ; => T

除法:

(float 3/2) ; => 1.5

指数:

(expt 2 5) ; => 32

对数:

(log 32 2) ; => 5.0

平方根:

(sqrt 4) ; => 2.0

位移操作: bit-vector 位移操作,Common Lisp 提供了几个位移操作的函数:

  • bit-and,逻辑与
  • bit-ior,逻辑或
  • bit-xor,异或
  • bit-not,逻辑非

等等

(bit-and #*0011 #*1101) ; => #*0001

boole 提供了位移操作,第二个参数需要指定位操作符,如 boole-and,boole-xor 等。

(boole boole-and 1 9) ; => 1
(boole boole-xor 1 1) ; => 0

进制:

;; 16 进制
#x5fbcbb                                ; => 6274235

7.1. 基本运算

7.1.1. +

(+ &rest numbers)

加法。

(+ 1 1)                                 ; => 2
(+ 1 2 3)                               ; => 6

7.1.2. -

(- number &rest more-numbers)

减法。

(- 2 1)                                 ; => 1
;;; 等同 10 - 8 - 1
(- 10 8 1)                            ; => 1
;;; 如果只有一个数字,就取它的负数
(- 2)                                   ; => -2

7.1.3. *

(* &rest numbers)

乘法。

(* 3 2 5)                               ; => 30

7.1.4. /

(/ number &rest more-numbers)

除法。

(/ 3)                                   ; => 1/3
;;; 无法整除时返回有理数
(/ 3 2)                                 ; => 3/2
(/ 10 5)                                ; => 2

7.1.5. 1+

(1+ number)

返回 number 加 1 后的值。

(1+ 1.0)                                ; => 2.0
(1+ 10)                                 ; => 11

7.1.6. 1-

(1- number)

返回 number 减 1 后的值。

(1- 10)                                 ; => 9

7.2. 一些谓词函数

7.2.1. numberp

(numberp object)

判断 object 是否是 number:

(numberp 123)                           ; => T
(numberp 123.0)                         ; => T
(numberp 0.1)                           ; => T
(numberp "1")                           ; => NIL
(numberp 1/2)                           ; => T

7.2.2. floatp

判断浮点类型:

(floatp 1.0) ; => T
(floatp 1) ; => NIL
(floatp 1/2) ; => NIL

7.2.3. zerop

判断是否等于 0:

(zerop 0)                               ; => T
(zerop 1)                               ; => NIL

7.2.4. plusp

(plusp number)

如果 number 大于 0,返回 T:

(plusp 1)                      ; => T
(plusp 0)                      ; => NIL
(plusp -1)                     ; => NIL

7.2.5. minusp

(minusp number)

如果number小于0,返回T

(minusp 1)                              ; => NIL
(minusp 0)                              ; => NIL
(minusp -1)                             ; => T

8. 序列(Sequence)

序列,包括 list 和 vector,可用 make-sequence 函数创建。

判断是否为序列:

(typep '(1 2 3) 'sequence) ; => T
(typep (vector) 'sequence) ; => T
(typep (make-array 10) 'sequence) ; => T

详细函数可以见:http://clhs.lisp.se/Body/17_a.htm#sequencefunctions

9. 向量(Vector)

使用 mismatch 函数可判断两个向量元素是否相等,如果相等则返回 NIL:

(mismatch #(1 2 3) #(1 2 3)) ; => NIL
(mismatch #(1 2 3) #(4 5 6)) ; => 0
(mismatch #(1 2 3) #(2)) ; => 0
(mismatch #(1 2 3) #(1)) ; => 1

10. 类型操作

10.1. type-of

有时需要知道对象的具体数据类型,可以借助 type-of,它的定义如下:

(type-of object)

示例:

(type-of 1) ; => BIT
(type-of 12345) ; => (INTEGER 0 4611686018427387903)
(type-of "abc") ; => (SIMPLE-ARRAY CHARACTER (3))
(type-of #\a) ; => nSTANDARD-CHAR
(type-of '(1 2 3)) ; => CONS

10.2. coerce

(coerce ojb output-type)

转换 obj 的类型。

;;; 如果 obj 的类型和 output-type 一样的,就原样返回:
(coerce 1 'integer)                     ; => 1
(coerce "1" 'string)                    ; => "1"

;;; 把 obj 转换成序列:
(coerce "hello world" 'simple-vector) ; => #(#\h #\e #\l #\l #\o #\  #\w #\o #\r #\l #\d)
(coerce "hello world" 'list) ; => (#\h #\e #\l #\l #\o #\  #\w #\o #\r #\l #\d)
(coerce "hello world" 'array)           ; => "hello world"

;;; 字符列表转成字符串:
(coerce '(#\h #\e #\l #\l #\o) 'string) ; => "hello"

;;; 转换成字符:
(coerce 'a 'character)                  ; => #\A
(coerce 'hello 'character)              ; 错误,obj 必须是单字符
(coerce :a 'character)                  ; => #\A

;;; 数字类型转换:
;; 转换成复数类型
(coerce 1.23 'complex)         ; => #C(1.23 0.0)'
;; 转换成浮点数类型
(coerce 1 'float)              ; => 1.0

;;; 根据符号名转换成已绑定的函数。注意必须是函数,不能用在宏上:
(coerce '+ 'function)                        ; => #<FUNCTION +>
;;; 调用返回的函数类型
(funcall (coerce '+ 'function) 1 1)     ; => 2
(coerce "+" 'function)                  ; 错误,名字不能是字符串

10.3. parse-integer

(parse-integer string &key (start 0) end (radix 10) junk-allowed)

将字符串转换成数字。这个函数会返回两个值,第一个值是转换结果,第二个值是转换的字符数。

(parse-integer "123")                   ; => 123
                                        ;    3
;;; radix 指定进制,这里转换二进制
(parse-integer "1101" :radix 2)         ; => 13
                                        ;    4

11. 对象比较,eq、eql、euqal 和 equalp

这几个谓词函数容易让人区分不清,区别在与对数据对比时的严格程度。

11.1. eq

(eq obj1 obj2)

用于内存比较,判断 obj1 和 obj2 是否为同一对象,或指向同个对象,是则返回 t,否则返回 nil。

(defvar hello "hello world")
(defvar a hello)
(defvar b hello)
(eq a b)                       ; => T
(defvar c a)
(eq a c)                       ; => T
(eq b c)                       ; => T
c                              ; => "hello world"
(defvar d "hi")
(eq d c)                       ; => NIL
(defvar a-list '(a b a))
(eq (car a-list) (caddr a-list)) ; => T

;;; 两个元素相同的列表是不同的 List 对象,所以返回 nil
(eq '(1 2 3) '(1 2 3))                  ; => NIL

11.2. eql

(eql obj1 obj2)

判断 obj1 和 obj2 是否相等,规则:

1、和 eq 有相同的判断规则——是否为同一内存对象。

2、obj1 和 obj2 是两个相同类型的数字,并且值也相等。

3、obj1 和 obj2 是两个相同的字符。

(defvar a 1)
(defvar b a)
;;; 指向相同对象
(eql a b)                               ; => T
;;; 相同类型、相同值的数字
(eql 1 1)                               ; => T
;;; 类型不同,所以返回 nil
(eql 1 1.0)                             ; => NIL
;;; 相同的字符
(eql #\a #\a)                           ; => T
(defvar a-list '(1 2 3))
;;; 值相同,但并不是一个对象,所以返回 nil
(eql a-list '(1 2 3))                   ; => NIL

11.3. euqal

(euqal x y)

判断 x 和 y 是否相等,比 eql 判断得更深入一些。对于复合的数据结构,如 List、字符,会递归比较元素的值。

;;; 符号判断和 eq 的规则一样
(equal 'a 'a)                  ; => T
;;; 数字判断和 eql 规则一样
(equal 100 100)                ; => T
;;; 字符判断和 eql 规则一样
(equal #\a #\a)                ; => T

;;; 判断 cons
;; 元素相同也为 t
(equal '(1 2 3) '(1 2 3))      ; => T
;; 元素顺序必须一致才返回 t
(equal '(1 2 3) '(3 2 1))      ; => NIL

;;; 判断 array
;;; 只有字符串和 bit-vector 才能判断是否相等
(equal "test" "test")          ; => T
;;; 字符串比较时不会忽略大小写
(equal "Test" "test")          ; => NIL
(equal #*1001 #*1001) ; => T
;;; 其他 array 无法比较
(equal #(1 2 3) #(1 2 3))      ; => NIL

;;; 判断 pathname 是否相等
(equal #p"/tmp/test" #p"/tmp/test") ; => T

11.4. equalp

(equalp x y)

比较两个对象是否相等。比 equal 判断得还要多一些,会忽略字符串大小写、合理范围内的类型差异(如 3 和 3.0)。

;;; 字符串比较会忽略大小写
(equalp "Test" "test")         ; => T
;;; 比较 array 是否相等
(equalp #(1 2 3) #(1 2 3))     ; => T
;;; 字符比较
(equalp #\a #\a)               ; => T
;;; 数字比较
(equalp 1 1)                   ; => T
;;; 整数和浮点数比较
(equalp 1 1.0)                 ; => T
;;; cons 比较
(equalp '(1 2 3) '(1 2 3))     ; => T

;;; 比较结构体
(make-my-info :name "lu4nx" :blog "shellcodes.org") ; => #S(MY-INFO :NAME "lu4nx" :BLOG "shellcodes.org")
(defvar a (make-my-info :name "lu4nx" :blog "shellcodes.org"))
(defvar b (make-my-info :name "lu4nx" :blog "shellcodes.org"))
(equalp a b)                   ; => T
;;; 元素不相等返回 nil
(equalp a (make-my-info :name "luanx" :blog "shellcodes.org")) ; => NIL

;;; 比较 hash 表
(defvar x (make-hash-table))
(defvar y (make-hash-table))
;;; 两个 hash 表都是空元素,所以相等
(equalp x y)                     ; => T
;;; 给其中一个 hash 表设置键值后再比较
(setf (gethash :name x) "lu4nx") ; => "lu4nx"
(equalp x y)                     ; => NIL

12. 相关函数

12.1. aref

(aref array &rest subscripts)

访问数组元素。参数 subscripts 是有效的下标,如果是多维数组,subscripts 应该有多个下标值,具体看示例。

;;; 访问一维数组中下标0和下标1的元素

(aref #(A B C) 0)                       ; => A
(aref #(A B C) 1)                       ; => B

;;; 访问二维数组
(defvar a-rank-array (make-array '(2 2))) ; 创建一个二维数组
(aref a-rank-array 0 1)                   ; => 0
(setf (aref a-rank-array 0 1) 1)          ; => 1
a-rank-array                              ; => #2A((0 1) (0 0))

;;; 由于 aref 是直接指向数组元素的内存地址,所以借助它可修改数组元素:
(defvar a-array #(1 2 3))
(setf (aref a-array 2) 'A)
a-array                                 ; => #(1 2 A)

12.2. decf

(decf place delta)

对 place 指向的内存位置做减法操作。delta 是减去的值,默认是1,也可以是一个表达式计算结果。

(defvar a-value 10)
;;; 默认减1
(decf a-value)                          ; => 9
;;; 对象的值减2
(decf a-value (+ 1 1))                  ; => 7

12.3. incf

(incf place delta-form)

对 delta-form 形式进行求值,并把求值结果加到 place 上,delta-form 默认为1。

注意:incf 是有副作用的,它直接修改 place 的值,而不是返回一个新值

(incf n)                                ; 等同于 (setf n (1+ n))
(incf n 10)                             ; 等同于 (setf n (+ n 10))

(defvar a 1)
(incf a)                                ; => 2
a                                       ; => 2
(incf a 10)                             ; => 12
(incf a (+ 1 1))                        ; => 14
a                                       ; => 14

12.4. truncate

(truncate number &optional divisor)

返回浮点数整数和小数部分。参数 divisor 是除数,默认为 1。

(truncate pi)
;; 输出:
;; 3
;; 0.14159265358979312d0
(truncate pi 3)
;; 输出:
;; 1
;; 0.14159265358979312d0