Skip to content

LISP - 字符串 I/O

字符串 I/O 指的是将字符串视为数据源或接收器的操作。这是 Lisp 中一个强大的特性,可以轻松实现数据序列化、解析和动态字符串构建。这对于 Web 开发、配置文件管理和生成格式化文本至关重要。

Common Lisp 提供了两种主要方法来将字符串视为输入源。

这是最常用且灵活的方法。它从字符串创建一个临时字符输入流,允许你对其使用标准流函数,如 read、read-line 和 read-char。当宏的主体退出时,流会自动关闭。

;; 示例:解析一串数字的字符串
(let ((data-string "10 20 30.5 an-atom"))
(with-input-from-string (s data-string)
(loop for item = (read s nil :eof)
until (eq item :eof)
do (format t "Read item: ~a (Type: ~a)~%" item (type-of item)))))
Read item: 10 (Type: (INTEGER 0 4611686018427387903))
Read item: 20 (Type: (INTEGER 0 4611686018427387903))
Read item: 30.5 (Type: SINGLE-FLOAT)
Read item: AN-ATOM (Type: SYMBOL)

(read s nil :eof) 模式是 Lisp 中读取对象直到流末尾的惯用方式。

此函数从字符串中解析一个 Lisp 对象。它对于快速解析很有用,但不如完整的流灵活。

安全警告:read 和 read-from-string 之所以强大,是因为它们使用了 Lisp 读取器,该读取器可以执行代码(例如,#.(+ 1 2))。**切勿在不受信任的用户输入上使用它们。**对于不受信任的数据,请使用 read-line 逐行解析并安全处理它。

(let* ((str "(list 1 2 3) and more")
(obj (read-from-string str)))
(print obj))
;; 它还返回读取停止的位置。
(multiple-value-bind (expr pos) (read-from-string str)
(format t "Expression: ~s~%Stopped at index: ~d~%" expr pos))
(LIST 1 2 3)
Expression: (LIST 1 2 3)
Stopped at index: 13

类似地,Lisp 提供了优雅的方式来将输出捕获到字符串中。

format 是 Lisp 多功能的打印和字符串格式化利器。通过将 nil 作为其第一个参数,它会返回格式化后的结果作为新字符串,而不是打印到流中。

(let* ((name "Alice")
(items 3)
(message (format nil "Hello, ~a! You have ~d item~:p in your cart." name items)))
(print message))
;; 演示 1 个项目以显示复数指令 `~:p`
(let ((message-singular (format nil "Hello, Bob! You have ~d item~:p." 1)))
(print message-singular))
"Hello, Alice! You have 3 items in your cart."
"Hello, Bob! You have 1 item."

当你需要通过多次 print、write 或 format 调用增量构建字符串时,此宏是完美的工具。它会创建一个临时字符输出流,并将写入其中的所有内容作为单个字符串返回。

;; 示例:生成一个简单的 HTML 列表
(let ((items '("Apples" "Bananas" "Cherries")))
(let ((html (with-output-to-string (s)
(write-string "<ul>~%" s)
(dolist (item items)
(format s " <li>~a</li>~%" item))
(write-string "</ul>" s))))
(print html)))
"<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Cherries</li>
</ul>"
  • read vs. read-line:当你的字符串包含要直接解析的有效 Lisp 数据结构时,使用 read。当处理纯文本、面向行的数据(如日志或 CSV 文件)或不受信任的输入时,使用 read-line。
  • 性能:对于在循环中构建复杂的字符串,with-output-to-string 通常比重复的字符串连接更高效。
  • 集成:Lisp 基于流的 I/O 的美妙之处在于其统一性。一个接受流参数的函数可以无需修改地处理文件、字符串或网络连接。这使得代码具有高度的可重用性。
  • 安全第一:再次重申安全警告:切勿将来自外部源(如 Web 请求)的原始、未清理的输入传递给 read 或 read-from-string。