Skip to content

LISP - 字符串

在 Common Lisp 中,字符串 (strings) 是一种特殊的向量 (vector) 类型,具体来说,它是一个元素为字符的一维数组。这种结构使其成为序列 (sequence),允许许多强大的序列操作函数直接对其进行操作。

字符串字面量 (String literals) 通过将字符序列用双引号 (") 括起来创建。特殊字符,例如双引号本身或反斜杠 (\),必须用前导反斜杠进行转义 (escape)。例如,"This is a \"quoted\" string."。

让我们从一些基本的字符串操作开始。创建一个名为 main.lisp 的新源文件,并添加以下代码。现代 Lisp 开发工作流程通常涉及交互式 REPL(读取-求值-打印循环),你可以在其中逐一评估这些表达式。

;; `write-line` 函数打印一个字符串,然后是换行符。
(write-line "Hello, Modern Lisp!")
;; `format` 函数是创建格式化字符串的更强大工具。
;; `~a` 是参数的占位符,`~%` 是换行符。
(format t "Welcome to ~a!~%" "the Lisp Tutorial")
;; 转义特殊字符
(write-line "This demonstrates \"escaping\" inside a string.")
Hello, Modern Lisp!
Welcome to the Lisp Tutorial!
This demonstrates "escaping" inside a string.

像 < 和 > 这样的数值比较运算符不适用于字符串。Common Lisp 提供了一组专门用于字典序字符串比较的函数,包括区分大小写和不区分大小写的变体。

区分大小写函数不区分大小写函数描述
string=string-equal如果所有参数字符串都相同,则返回 T。
string/=string-not-equal返回第一个不匹配的索引,如果任何两个字符串相同则返回 NIL。
string<string-lessp如果字符串按严格升序的字典序排列,则返回第一个不匹配的索引,否则返回 NIL。
string>string-greaterp如果字符串按严格降序的字典序排列,则返回第一个不匹配的索引,否则返回 NIL。
string<=string-not-greaterp如果字符串按升序(或相等)的字典序排列,则返回第一个不匹配的索引,否则返回 NIL。
string>=string-not-lessp如果字符串按降序(或相等)的字典序排列,则返回第一个不匹配的索引,否则返回 NIL。

让我们看看这些比较函数在实践中如何工作。

;; 区分大小写的相等检查
(format t "'alpha' = 'Alpha' -> ~a~%" (string= "alpha" "Alpha"))
;; 不区分大小写的相等检查
(format t "'alpha' equal 'Alpha' -> ~a~%" (string-equal "alpha" "Alpha"))
;; 小于检查(区分大小写)
;; 在 ASCII/Unicode 中 'A' 排在 'a' 之前
(format t "'Alpha' < 'alpha' -> ~a~%" (string< "Alpha" "alpha"))
;; 不匹配检查
;; 不匹配发生在索引 3 处('h' 对 'a')
(format t "'Lisp' /= 'Lisa' -> ~a~%" (string/= "Lisp" "Lisa"))
;; 不等式检查中,不匹配返回 NIL
(format t "'test' /= 'test' -> ~a~%" (string/= "test" "test"))
'alpha' = 'Alpha' -> NIL
'alpha' equal 'Alpha' -> T
'Alpha' < 'alpha' -> 0
'Lisp' /= 'Lisa' -> 3
'test' /= 'test' -> NIL

Common Lisp 提供了简单的函数来控制字符串中字符的大小写。

函数描述
string-upcase返回一个新字符串,其中所有字母字符都转换为大写。
string-downcase返回一个新字符串,其中所有字母字符都转换为小写。
string-capitalize返回一个新字符串,其中每个单词的首字母大写。
(let ((my-string "a modern lisp tutorial"))
(write-line (string-upcase my-string))
(write-line (string-capitalize my-string)))
A MODERN LISP TUTORIAL
A Modern Lisp Tutorial

这些函数对于清理用户输入或文件数据很有用,通过移除字符串开头或结尾不需要的字符。

函数描述
string-trim从字符串的开头和结尾移除 character-bag 中所有字符。
string-left-trim从字符串的开头(左侧)移除 character-bag 中的字符。
string-right-trim从字符串的结尾(右侧)移除 character-bag 中的字符。

这些函数的第一个参数是要移除的字符序列,通常称为“字符袋 (character-bag)”。

(let* ((char-bag '(#\Space #\Tab #\Newline))
(messy-string " some text
"))
(format t "原始字符串:'~a'~%" messy-string)
(format t "修剪后:'~a'~%" (string-trim char-bag messy-string))
(format t "左侧修剪后:'~a'~%" (string-left-trim char-bag messy-string))
(format t "右侧修剪后:'~a'~%" (string-right-trim char-bag messy-string)))
Original: ' some text
'
Trimmed: 'some text'
Left-Trimmed: 'some text
'
Right-Trimmed: ' some text'

由于字符串是序列,因此可以在其上使用各种强大的函数。以下是一些最常用的函数。

(let ((s "Hello World"))
;; `length` 返回字符串中的字符数。
(format t "Length: ~d~%" (length s))
;; `subseq` 提取字符串的一部分。索引是基于 0 的。
;; 这将从索引 6 提取到字符串末尾。
(format t "Substring: ~s~%" (subseq s 6))
;; `char` 访问特定索引处的单个字符。
(format t "Character at index 6: ~c~%" (char s 6)))
Length: 11
Substring: "World"
Character at index 6: W

这些操作展示了将字符串视为序列的强大功能。

;; `sort` 对序列进行排序。它是破坏性的,因此我们排序一个副本。
;; `char<` 是用于比较的谓词。
(let ((original "ecbda"))
(let ((sorted (sort (copy-seq original) #'char<)))
(format t "Sorted '~a' -> '~a'~%" original sorted)))
;; `reverse` 反转序列中元素的顺序。
(write-line (reverse "live"))
;; `concatenate` 连接序列。第一个参数指定结果类型。
(write-line (concatenate 'string "Hello, " "Lisp " "World!"))
Sorted 'ecbda' -> 'abcde'
evil
Hello, Lisp World!
  • 使用 format 进行复杂构建:虽然 concatenate 适用于简单的连接,但 format 在从各种数据类型构建复杂字符串时更加灵活和可读。
  • 理解字符串不可变性:像 string-upcase 或 reverse 这样的函数返回一个新字符串。像 sort 这样的破坏性函数应谨慎使用,通常在原始字符串的 copy-seq(副本)上使用,以避免意外的副作用 (side effects)。
  • 探索字符串库:对于正则表达式等高级需求,请考虑使用 Quicklisp 中的库,Quicklisp 是 Common Lisp 的事实标准包管理器 (package manager)。一个流行的选择是 cl-ppcre。