4.6 结构 (Structures)
结构可以想成是豪华版的向量。假设你要写一个程序来追踪长方体。你可能会想用三个向量元素来表示长方体:高度、宽度及深度。与其使用原本的 svref
,不如定义像是下面这样的抽象,程序会变得更容易阅读,
(defun block-height (b) (svref b 0))
而结构可以想成是,这些函数通通都替你定义好了的向量。
要想定义结构,使用 defstruct
。在最简单的情况下,只要给出结构及字段的名字便可以了:
(defstruct point
x
y)
这里定义了一个 point
结构,具有两个字段 x
与 y
。同时隐式地定义了 make-point
、 point-p
、 copy-point
、 point-x
及 point-y
函数。
2.3 节提过, Lisp 程序可以写出 Lisp 程序。这是目前所见的明显例子之一。当你调用 defstruct
时,它自动生成了其它几个函数的定义。有了宏以后,你将可以自己来办到同样的事情(如果需要的话,你甚至可以自己写出 defstruct
)。
每一个 make-point
的调用,会返回一个新的 point
。可以通过给予对应的关键字参数,来指定单一字段的值:
(setf p (make-point :x 0 :y 0))
#S(POINT X 0 Y 0)
存取 point
字段的函数不仅被定义成可取出数值,也可以搭配 setf
一起使用。
> (point-x p)
0
> (setf (point-y p) 2)
2
> p
#S(POINT X 0 Y 2)
定义结构也定义了以结构为名的类型。每个点的类型层级会是,类型 point
,接着是类型 structure
,再来是类型 atom
,最后是 t
类型。所以使用 point-p
来测试某个东西是不是一个点时,也可以使用通用性的函数,像是 typep
来测试。
> (point-p p)
T
> (typep p 'point)
T
我们可以在本来的定义中,附上一个列表,含有字段名及缺省表达式,来指定结构字段的缺省值。
(defstruct polemic
(type (progn
(format t "What kind of polemic was it? ")
(read)))
(effect nil))
如果 make-polemic
调用没有给字段指定初始值,则字段会被设成缺省表达式的值:
> (make-polemic)
What kind of polemic was it? scathing
#S(POLEMIC :TYPE SCATHING :EFFECT NIL)
结构显示的方式也可以控制,以及结构自动产生的存取函数的字首。以下是做了前述两件事的 point
定义:
(defstruct (point (:conc-name p)
(:print-function print-point))
(x 0)
(y 0))
(defun print-point (p stream depth)
(format stream "#<~A, ~A>" (px p) (py p)))
:conc-name
关键字参数指定了要放在字段前面的名字,并用这个名字来生成存取函数。预设是 point-
;现在变成只有 p
。不使用缺省的方式使代码的可读性些微降低了,只有在需要常常用到这些存取函数时,你才会想取个短点的名字。
:print-function
是在需要显示结构出来看时,指定用来打印结构的函数 ── 需要显示的情况比如,要在顶层显示时。这个函数需要接受三个实参:要被印出的结构,在哪里被印出,第三个参数通常可以忽略。 [2] 我们会在 7.1 节讨论流(stream)。现在来说,只要知道流可以作为参数传给 format
就好了。
函数 print-point
会用缩写的形式来显示点:
> (make-point)
#<0,0>