common Lisp学习笔记(十二)
12 Structure and The Type Syetem
总结一下前面学习过的lisp的基本类型,有number, symbol, cons, string, function, stream等,这些都是最基本的 数据类型. structures
结构体是用户定义的数据类型,与其他语言类似
12.2 typep, type-of
(typep 3 'number) -> t (typep 3 'integer) -> t (typep 'foo 'symbol) -> t (type-of 'foo) -> symbol (type-of '3.5) -> short-float
typep, type-of的用法都很简单。在lisp中,有以下规则
- 所有的对象都继承于t,即都是t的子类型
- NULL类型只包含符号nil
- list类型包含cons和null, null是symbol和list的子类型
12.3 defining structures
defstruct
macro定义新的结构体的名字和变量的默认值
(defstruct starship (name nil) (speed 0) (condition 'green) (shields 'down)
starship是结构体的名字,里面有4个成员变量,每个成员变量和初始值放在一个小括号中
使用defstruct定义好结构体之后,会自动定义一个make函数可以用来构造一个结构体,如make-starship
> (setf s1 (make-starship)) #S(starship name nil speed 0 condition green shields down)
输出的结构体前面有#S
表示,也可以直接用#S结构体对变量进行赋值
> (setf s2 '#s(starship speed (warp 3))) #S(starship ...)
defstruct之后还会自动生成starship-p这个predicate,可以判断对象是否是这个类型
12.5 accessing, modifying structs
定义结构体之后,使用structname-member可以访问成员变量,如(starship-speed s2) -> (warp 3)
对structname-member可以使用setf赋值
12.6 kwargs to constructor functions
defstruct里面定义的是成员变量的默认值,我们在构造一个新对象的时候可以使用关键字参数来设置成员变量的初始 值,回顾前面使用关键字参数要在前面加上冒号
> (setf s3 (make-starship :name "Jack" :shields 'damaged)) #S(starship name "Jack" ...)
12.7 修改结构体定义
使用defstruct定义好结构体之后,如果再次定义同名的结构体,会对之前已经创建好的实例造成无法预测的影响 可以重新使用make-structname构造函数来修改实例的值
ex 12.4
(defstruct node (name) (question) (yes-case) (no-case)) (setf *node-list* nil) (defun init () (setf *node-list* nil)) (defun add-node (name question yes-case no-case) (push (make-node :name name :question question :yes-case yes-case :no-case no-case) *node-list*) name) (defun find-node (name) (find-if #'(lambda (e) (equal name (node-name e))) *node-list*)) (add-node 'start "Does the engine turn over?" 'engine-turns-over 'engine-wont-turn-over) (add-node 'engine-turns-over "Will the engine run for any period of time?" 'engine-will-run-briefly 'engine-wont-run) (add-node 'engine-wont-run "Is there gas in the tank?" 'gas-in-tank "Fill the tank and try starting the engine again.") (add-node 'engine-wont-turn-over "Do you hear any sound when you turn the key?" 'sound-when-turn-key 'no-sound-when-turn-key) (add-node 'no-sound-when-turn-key "Is the battery voltage low?" "Replace the battery" 'battery-voltage-ok) (add-node 'battery-voltage-ok "Are the battery cables dirty or loose?" "Clean the cables and tighten the connections." 'battery-cables-good) (defun process-node (name) (let ((node-exist (find-node name))) (if node-exist (if (y-or-n-p "~&~A " (node-question node-exist)) (node-yes-case node-exist) (node-no-case node-exist)) (format t "~&node ~S not yet defined." name)))) (defun run () (do ((current-node 'start (process-node current-node))) ((null current-node) nil) (when (stringp current-node) (format t "~&~S" current-node) (return nil))))) (defun prompt-for (s) (format t "~&~A~%" s) (read)) (defun interactive-add () (let* ((name (prompt-for "node name?")) (question (prompt-for "question?")) (yes-action (prompt-for "if yes?")) (no-action (prompt-for "if no?"))) (add-node name question yes-action no-action)))
12.8 print func for structs
默认输出一个结构体的时候,会把整个结构体的所有成员都输出,但是有时我们只需要其中一些信息,因此可以通过修改结构体的输出函数来实现
clisp中一个简化输出的结构体习惯用"#<...>"来表示,如#<starship enterprise>
下面重新定义输出的函数,里面要有三个参数,要输出的结构体,输出的stream,还有输出的深度限制。输出到屏幕则stream为t,深度暂时不做讨论。如只需要输出starship的name
(defun print-starship (x stream depth) (format stream "#<starship ~A>" (starship-name x)))
重新定义结构体,使用:print-function参数
(defstruct (starship (:print-function print-starship)) (captain nil) (name nil) (shields 'down) (condition 'green) (speed 0)) > (setf s4 (make-starship :name "hello")) #<starship hello>
注意上面的括号,starship左边的括号在print-starship之后已经闭合,后面参数还是一样
重新定义输出还有一个作用,比如一些结构体中包含另一个结构体,而另一个结构体也反过来包含这个结构体,比如一个船长拥有一艘船,一艘船则属于一个船长,类似于一些数据库,这种情况下普通的输出会形成无限的循环。
ex 12.5
(defstruct starship (captain nil) (name nil) (speed 0) (condition 'green) (shields 'down)) (defstruct (captain (:print-function print-captain)) name age ship) (defun print-captain (x stream depth) (format t "#<captain ~A>" (captain-name x))) (setf s1 (make-starship :name "Enterprise")) (setf c1 (make-captain :name "James T.Kirk" :age 35 :ship s1)) (setf (starship-captain s1) c1)
12.9 equality of structs
之前equal函数只要两个对象的值相等就会返回t,但是对于结构体比较特殊,即使两个结构体的成员的值完全相同,equal函数也会返回nil,只有比较同一个结构体对象的时候,才会返回t
如果要比较两个结构体的值相等,可以使用equalp
,eg
(setf s1 (make-starship)) (setf s2 (make-starship)) > (equalp s1 s2) t
equalp函数还有一点与equal不同,就是比较两个字符串的时候是忽略大小写的
(equal "hello" "Hello") -> nil (equalp "hello" "Hello") -> t
12.10 inheritance
lisp的结构体和其他语言一样,也可以继承于基类,继承时加上关键字:include,类似于重定义输出函数的用法
(defstruct ship (name nil) (captain nil) (crew-size nil)) (defstruct (starship (:include ship)) (weapons nil) (shields nil)) (defstruct (supply-ship (:include ship)) (cargo nil))
在子类中可以用同样的方法来反问基类的成员变量