Skip to content

LISP - 结构体

结构体(Structures)是用户定义的复合数据类型,用于将相关数据组织成一个单一对象。在 Common Lisp 中,它们是创建记录状数据(record-like data)的最简单且通常性能最高的方式,尤其适用于不需要 Common Lisp 对象系统(CLOS)全部功能的情形。

defstruct 宏用于定义一个新的结构体类型。当你定义一个结构体时,Lisp 会自动生成一组有用的函数,用于创建和操作该结构体的实例。

让我们定义一个 book 结构体来跟踪图书馆的藏书:

(defstruct book
title
author
subject
(book-id -1) ; 提供默认值
(available-p t))
  • 构造函数 (make-book):用于创建结构体新实例的函数。
  • 类型谓词 (book-p):一个函数,如果其参数是 book 结构体,则返回 T;否则返回 NIL。
  • 访问器 (book-title, book-author 等):用于从结构体实例中检索特定槽(slot)值的函数。
  • 设置器(Setters):访问器是可 setf 的,允许你修改槽(slot)的值。
  • 复制器 (copy-book):一个函数,用于创建结构体实例的(浅)副本。

此示例演示了结构体实例的完整生命周期:创建、访问、修改和类型检查。我们使用 let 来保持变量的局部性。

;; 定义 book 结构体(应位于文件的顶层)
(defstruct book title author subject (book-id -1) (available-p t))
(defun run-book-demo ()
(let ((book1 (make-book :title "The Pragmatic Programmer"
:author "Andy Hunt & Dave Thomas"
:subject "Software Engineering"
:book-id 101)))
;; 1. 打印创建的结构体
(format t "Created Book:~% ~s~%~%" book1)
;; 2. 访问槽(slot)值
(format t "Title: ~s~%" (book-title book1))
(format t "Is available? ~a~%~%" (book-available-p book1))
;; 3. 使用 SETF 修改槽(slot)值
(format t "Checking out the book...~%")
(setf (book-available-p book1) nil)
(format t "Is available now? ~a~%~%" (book-available-p book1))
;; 4. 使用类型谓词
(format t "Is book1 a 'book' type? ~a~%" (book-p book1))
(format t "Is the string \"hello\" a 'book' type? ~a~%" (book-p "hello"))))
;; 运行演示函数
(run-book-demo)
Created Book:
#S(BOOK :TITLE "The Pragmatic Programmer" :AUTHOR "Andy Hunt & Dave Thomas" :SUBJECT "Software Engineering" :BOOK-ID 101 :AVAILABLE-P T)
Title: "The Pragmatic Programmer"
Is available? T
Checking out the book...
Is available now? NIL
Is book1 a 'book' type? T
Is the string "hello" a 'book' type? NIL

defstruct 提供了多种选项来定制其行为,使其具有高度灵活性。

  • :conc-name:控制访问器名称的前缀。(:conc-name book-) 是默认值。将其设置为 nil 将创建没有前缀的访问器(例如 title 而不是 book-title)。
  • :constructor:定制构造函数名称,例如 (:constructor create-book)。如果为 nil,则不创建构造函数。
  • :include:允许一个结构体从另一个结构体继承槽(slot)。这是一种简单的继承形式。
  • :type:指定底层表示形式。(:type vector) 创建一个由向量表示的结构体,这可能更快。(:type list) 使用列表。
;; 基础结构体
(defstruct vehicle (num-wheels 4))
;; Car 从 vehicle 继承
(defstruct (car (:include vehicle) (:conc-name car-))
make
model)
(let ((my-car (make-car :num-wheels 4 :make "Honda" :model "Civic")))
(format t "My car: ~a, Make: ~a, Wheels: ~a"
my-car
(car-make my-car) ; 来自 car 的访问器
(vehicle-num-wheels my-car) ; 来自被包含的 vehicle 的访问器
))

什么时候应该使用 defstruct,什么时候应该使用 defclass?

  • 在以下情况使用 defstruct:简单的数据容器、需要紧凑数据(packed data)的性能关键代码,以及不需要多重继承或方法特化等特性时。
  • 在以下情况使用 defclass:复杂的对象模型、需要多重继承的系统、通过泛型函数(多方法)实现动态行为,以及通过元对象协议 (MOP) 进行自省时。