第七章 输入输出

Scheme的输入/输出程序可以使你从输入端口读取或者将写入到输出端口。端口可以关联到控制台,文件和字符串。

7.1 读取

Scheme的读取程序带有一个可选的输入端口参数。如果端口没有特别指定,则假设为当前端口(一般是控制台)。

读取的内容可以是一个字符,一行数据或是S表达式。当每次执行读取时,端口的状态就会改变,因此下一次就会读取当前已读取内容后面的内容。如果没有更多的内容可读,读取程序将返回一个特殊的数据——文件结束符或EOF对象。这个对象只能用eof-object?函数来判断。

read-char程序会从端口读取下一个字符。read-line程序会读取下一行数据,并返回一个字符串(不包括最后的换行符),read程序则会读取下一个S表达式。

7.2 写入

Scheme的写入程序接受一个要被写入的对象和一个可选的输出端口参数。如果未指定端口,则假设为当前端口(一般为控制台)。

写入的对象可以是字符或是S表达式。

write-char程序可以向输出端口写入一个给定的字符(不包括#\)。
writedisplay程序都可以向端口写入一个给定的S表达式,唯一的区别是:write程序会使用机器可读型的格式而display程序却不用。例如,write用双引号表示字符串,用#\句法表示字符,但display却不这么做。

newline程序会在输出端口输出一个换行符。

7.3 文件端口

如果端口是标准的输入和输出端口,Scheme的I/O程序就不需要端口参数。但是,如果你明确需要这些端口,则current-input-portcurrent-output-port这些零参数程序会提供这个功能,例如:

  1. (display 9)
  2. (display 9 (current-output-port))

拥有相同的效果。

一个端口通过打开文件和这个文件关联在一起。open-input-file程序会接受一个文件名作为参数并返回一个和这个文件关联的新的输入端口。open-output-file程序会接受一个文件名作为参数并返回一个和这个文件关联的新的输出端口。如果打开一个不存在的输入文件,或者打开一个已经存在的输出文件,程序都会出错。

当你已经在一个端口执行完输入或输出后,你需要使用close-input-portclose-output-port程序将它关闭。

在下述例子中,假如文件hello.txt文件只包含一个单词hello

  1. (define i (open-input-file "hello.txt"))
  2. (read-char i)
  3. => #\h
  4. (define j (read i))
  5. j
  6. => ello

假如文件greeting.txt在下述程序运行前不存在:

  1. (define o (open-output-file "greeting.txt"))
  2. (display "hello" o)
  3. (write-char #\space o)
  4. (display 'world o)
  5. (newline o)
  6. (close-output-port o)

现在Greeting.txt文件将会包含这样一行:

  1. hello world

7.3.1 文件端口的自动打开和关闭

Scheme提供了call-with-input-filecall-with-output-file过程,这些过程会照顾好打开的端口并在你使用完后将端口关闭。

call-with-input-file程序接受一个文件名参数和一个过程。这个过程被应用在一个已打开的文件输入端口。当程序结束时,它的结果会在保证端口关闭后返回。

  1. (call-with-input-file "hello.txt"
  2. (lambda (i)
  3. (let* ((a (read-char i))
  4. (b (read-char i))
  5. (c (read-char i)))
  6. (list a b c))))
  7. => (#\h #\e #\l)

call-with-output-file程序会对输出文件提供类似的服务。

7.4 字符串端口

一般来说将字符串与端口相关联是很方便的。因此,open-input-string程序将一个给定的字符串和一个端口关联起来。读取这个端口的程序将读出下述字符串:

  1. (define i (open-input-string "hello world"))
  2. (read-char i)
  3. => #\h
  4. (read i)
  5. => ello
  6. (read i)
  7. => world

open-output-string创建了一个输出端口,最终可以用于创建一个字符串:

  1. (define o (open-output-string))
  2. (write 'hello o)
  3. (write-char #\, o)
  4. (display " " o)
  5. (display "world" o)

现在你可以使用get-output-string程序得到保留在字符串端口o中的字符串:

  1. (get-output-string o)
  2. => "hello, world"

字符串端口不需要显式地去关闭。

7.5 加载文件

我们已将看到load程序可以加载包含Scheme代码的文件。load一个文件意味着按顺序求值文件中每一个Scheme表达式。load中的路径参数是相对当前Scheme工作目录计算的,该工作目录一般是调用Scheme可执行文件时的目录。

一个文件可以加载其他的文件,这在包含许多文件的大项目中十分有用。但是,除非使用绝对路径,否则load参数中的文件位置将依赖于执行Scheme的当前目录。而提供绝对路径名并不是很方便,因为我们更愿意把项目文件作为一个单元(保留它们的相对路径名)在很多不同机器中运行。

Mzscheme提供了load-relative程序,可以很好的解决这个问题。load-relative,和load相似,带有一个路径名参数。当在foo.scm文件中出现load-relative调用时,它的参数的路径将根据文件foo.scm所在目录的路径来计算。特别注意的是,这个路径名和执行Scheme的当前目录无关,因此也就可以方便地进行多文件程序的开发。