Skip to content

LISP - 访问器与修改器

现代 Lisp:使用 defstruct 定义用户自定义结构体

Section titled “现代 Lisp:使用 defstruct 定义用户自定义结构体”

在 Lisp 中,defstruct 是定义新的、轻量级用户自定义数据类型的标准方式。结构体是命名槽位(named slots)的聚合,类似于 C 语言中的 struct 或 Python 中的简单 class。对于你定义的每个结构体,Common Lisp 都会自动生成一组有用的函数,包括构造函数(constructors)、访问器(accessors)和谓词(predicates),它们为与数据交互提供了清晰且受控的接口。

当你使用 defstruct 定义一个结构体时,Lisp 会自动创建访问器函数(accessor functions)来检索每个槽位(slot)的值。这些函数的命名方式是连接结构体名称和槽位名称。

让我们定义一个 book 结构体,包含书名(title)、作者(author)和出版年份(publication year)等槽位。我们还可以提供默认值和类型提示。

(defstruct book
(title "" :type string)
(author "" :type string)
(publication-year 0 :type (integer 0 *)))
;; 通过定义此结构体,Lisp 会自动提供:
;; - 构造函数:make-book
;; - 访问器:book-title, book-author, book-publication-year
;; - 复制函数:copy-book
;; - 谓词:book-p

现在我们可以创建一个 book 结构体的实例,并使用自动生成的访问器来读取其槽位值。

;; 创建一个 book 结构体的实例
(defparameter *my-book*
(make-book :title "Structure and Interpretation of Computer Programs"
:author "Abelson & Sussman"
:publication-year 1985))
;; 使用访问器获取书名
(book-title *my-book*) ; => "Structure and Interpretation of Computer Programs"

要修改结构体槽位中的值,Lisp 使用一个强大且通用的宏:setf。你将 setf 与槽位的访问器函数结合使用,以指定要修改的“位置”。这是 Common Lisp 中处理数据修改的惯用且通用的方式。

以下完整示例演示了结构体的定义、创建、访问和修改。

;; 定义一个二维点结构体
(defstruct point
(x 0.0 :type float)
(y 0.0 :type float))
;; 创建一个表示原点的实例
(defparameter *p1* (make-point))
;; 打印初始 Y 坐标
(format t "Initial Y: ~a~%" (point-y *p1*))
;; 使用 SETF 和访问器修改 Y 坐标
(setf (point-y *p1*) 10.5)
;; 打印更新后的 Y 坐标
(format t "Updated Y: ~a~%" (point-y *p1*))
;; point-p 谓词检查一个对象是否为 point 类型
(format t "Is *p1* a point? ~a~%" (point-p *p1*))
(format t "Is 42 a point? ~a~%" (point-p 42))

当你编译并加载此文件时,会看到:

Initial Y: 0.0
Updated Y: 10.5
Is *p1* a point? T
Is 42 a point? NIL
  • 封装性(Encapsulation): 尽管不如某些语言那样严格,但结合 setf 使用访问器函数能为你的数据提供受控的接口,提高代码清晰度。
  • 结构体与类(Structs vs. Classes): defstruct 非常适合简单的聚合数据。对于涉及继承、方法和多态等更复杂的行为,你应该使用 Common Lisp 对象系统 (CLOS) 中的 defclass。
  • 不变性(Immutability): 对于更函数式的编程风格,可以考虑将结构体视为不可变(immutable)的。与其修改现有结构体,不如创建一个带有所需更改的新结构体。自动生成的 copy-book 函数在此处会很有帮助。