7.2 输入 (Input)

两个最受欢迎的输入函数是 read-lineread 。前者读入换行符 (newline)之前的所有字符,并用字符串返回它们。它接受一个选择性流参数 (optional stream argument);若流忽略时,缺省为 *standard-input* :

  1. > (progn
  2. (format t "Please enter your name: ")
  3. (read-line))
  4. Please enter your name: Rodrigo de Bivar
  5. "Rodrigo de Bivar"
  6. NIL

译注:Rodrigo de Bivar 人称熙德 (El Cid),十一世纪的西班牙民族英雄。

如果你想要原封不动的输出,这是你该用的函数。(第二个返回值只在 read-line 在遇到换行符之前,用尽输入时返回真。)

在一般情况下, read-line 接受四个选择性参数: 一个流;一个参数用来决定遇到 end-of-file 时,是否产生错误;若前一个参数为 nil 时,该返回什么;第四个参数 (在 235 页讨论)通常可以省略。

所以要在顶层显示一个文件的内容,我们可以使用下面这个函数:

  1. (defun pseudo-cat (file)
  2. (with-open-file (str file :direction :input)
  3. (do ((line (read-line str nil 'eof)
  4. (read-line str nil 'eof)))
  5. ((eql line 'eof))
  6. (format t "~A~%" line))))

如果我们想要把输入解析为 Lisp 对象,使用 read 。这个函数恰好读取一个表达式,在表达式结束时停止读取。所以可以读取多于或少于一行。而当然它所读取的内容必须是合法的 Lisp 语法。

如果我们在顶层使用 read ,它会让我们在表达式里面,想用几个换行符就用几个:

  1. > (read)
  2. (a
  3. b
  4. c)
  5. (A B C)

换句话说,如果我们在一行里面输入许多表达式, read 会在第一个表达式之后,停止处理字符,留下剩余的字符给之后读取这个流的函数处理。所以如果我们在一行输入多个表达式,来回应 ask-number (20 页。译注:2.10 小节)所印出提示符,会发生如下情形:

  1. > (ask-number)
  2. Please enter a number. a b
  3. Please enter a number. Please enter a number. 43
  4. 43

两个连续的提示符 (successive prompts)在第二行被印出。第一个 read 调用会返回 a ,而它不是一个数字,所以函数再次要求一个数字。但第一个 read 只读取到 a 的结尾。所以下一个 read 调用返回 b ,导致了下一个提示符。

你或许想要避免使用 read 来直接处理使用者的输入。前述的函数若使用 read-line 来获得使用者输入会比较好,然后对结果字符串调用 read-from-string 。这个函数接受一个字符串,并返回第一个读取的表达式:

  1. > (read-from-string "a b c")
  2. A
  3. 2

它同时返回第二个值,一个指出停止读取字符串时的位置的数字。

在一般情况下, read-from-string 可以接受两个选择性参数与三个关键字参数。两个选择性参数是 read 的第三、第四个参数: 一个 end-of-file (这个情况是字符串) 決定是否报错,若不报错该返回什么。关键字参数 :start:end 可以用来划分从字符串的哪里开始读。

所有的这些输入函数是由基本函数 (primitive) read-char 所定义的,它读取一个字符。它接受四个与 readread-line 一样的选择性参数。Common Lisp 也定义一个函数叫做 peek-char ,跟 read-char 类似,但不会将字符从流中移除。