[TOC=2]
## 9.1?簡介
通過前面章節的學習,你已經可以在Scheme的交互式前端中編寫并執行程序了。在本章中,我講介紹如何輸入和輸出。使用這個特性,你可以從文件中讀取數據或向文件中寫入數據。
## 9.2?從文件輸入
### open-input-file,read-char和eof-object?
函數`(open-input-file filename)`可以用于打開一個文件。此函數返回一個用于輸入的端口。函數`(read-char port)`用于從端口中讀取一個字符。當讀取到**文件結尾(EOF)**時,此函數返回`eof-object`,你可以使用`eof-object?`來檢查。函數`(close-input-port port)`用于關閉輸入端口。[代碼片段1]展示了一個函數,該函數以字符串形式返回了文件內容。
[代碼片段1] 以字符串的形式返回文件內容
~~~
(define (read-file file-name)
(let ((p (open-input-file file-name)))
(let loop((ls1 '()) (c (read-char p)))
(if (eof-object? c)
(begin
(close-input-port p)
(list->string (reverse ls1)))
(loop (cons c ls1) (read-char p))))))
~~~
比如,在[范例1]中展示的結果就是將[代碼片段1]應用于文件hello.txt。由于換行符是由`'\n'`表示的,這就很容易閱讀。然而,像格式化輸出[范例2],我們也可使用`display`函數。
[文件]hello.txt
~~~
Hello world!
Scheme is an elegant programming language.
~~~
[范例1]
~~~
(cd "C:\\doc")
(read-file "hello.txt")
;Value 14: "Hello world!\nScheme is an elegant programming language.\n"
~~~
[范例2]
~~~
(display (read-file "hello.txt"))
Hello world!
Scheme is an elegant programming language.
;Unspecified return value
~~~
### 9.2.2?語法call-with-input-file和with-input-from-file
你通過使用語法`call-with-input-file`和`with-input-from-file`來打開文件以供讀取輸入。這些語法是非常方便的,因為它們要處理錯誤。
> `(call-with-input-file?filename?procedure)`
>
> 該函數將名為`filename`的文件打開以供讀取輸入。函數`procedure`接受一個輸入端口作為參數。文件有可能再次使用,因此當`procedure`結束時文件不會自動關閉,文件應該顯式地關閉。[代碼片段1]可以按照[代碼片段2]那樣用`call-with-input-file`編寫。
[代碼片段2] call-with-input-file版本
~~~
(define (read-file file-name)
(call-with-input-file file-name
(lambda (p)
(let loop((ls1 '()) (c (read-char p)))
(if (eof-object? c)
(begin
(close-input-port p)
(list->string (reverse ls1)))
(loop (cons c ls1) (read-char p)))))))
~~~
> `(with-input-from-file filename procedure)`?該函數將名為`filename`的文件作為標準輸入打開。函數`procedure`不接受任何參數。當`procedure`退出時,文件自動被關閉。[代碼片段3]展示了如何用`with-input-from-file`來重寫[代碼片段1]。
[代碼片段3] with-input-from-file版本
~~~
(define (read-file file-name)
(with-input-from-file file-name
(lambda ()
(let loop((ls1 '()) (c (read-char)))
(if (eof-object? c)
(list->string (reverse ls1))
(loop (cons c ls1) (read-char)))))))
~~~
### 9.2.3?read
函數`(read port)`從端口`port`中讀入一個S-表達式。用它來讀諸如”paren.txt”中帶括號的內容就很方便。
~~~
'(Hello world!
Scheme is an elegant programming language.)
'(Lisp is a programming language ready to evolve.)
~~~
[代碼片段4]
~~~
(define (s-read file-name)
(with-input-from-file file-name
(lambda ()
(let loop ((ls1 '()) (s (read)))
(if (eof-object? s)
(reverse ls1)
(loop (cons s ls1) (read)))))))
~~~
下面展示了用`s-read`讀取”paren.txt”的結果。
~~~
(s-read "paren.txt")
? ((quote (hello world! scheme is an elegant programming language.))
(quote (lisp is a programming language ready to evolve.)))
~~~
> 練習1
>
> 編寫函數`(read-lines)`,該函數返回一個由字符串構成的表,分別代表每一行的內容。在Scheme中,換行符是由`#\Linefeed`表示。下面演示了將該函數用于”hello.txt”的結果。
>
> `(read-lines "hello.txt") ? ("Hello world!" "Scheme is an elegant programming language.")`
## 9.3?輸出至文件
### 9.3.1?打開一個用于輸出的port
輸出有和輸入類似的函數,比如:
`(open-output-file filename)`
該函數打開一個文件用作輸出,放回該輸出端口。
`(close-output-port port)`
關閉用于輸出的端口。
`(call-with-output-file?filename?procedure)`
打開文件`filename`用于輸出,并調用過程`procedure`。該函數以輸出端口為參數。
`(with-output-to-file?filename?procedure)`
打開文件`filename`作為標準輸出,并調用過程`procedure`。該過程沒有參數。當控制權從過程`procedure`中返回時,文件被關閉。
### 9.3.2?用于輸出的函數
下面的函數可用于輸出。如果參數`port`被省略的話,則輸出至標準輸出。
`(write obj port)`
該函數將`obj`輸出至`port`。字符串被雙引號括起而字符具有前綴`#\`。
`(display obj port)`
該函數將`obj`輸出至`port`。字符串*不被*雙引號括起而字符*不*具有前綴`#\`。
`(newline port)`
以新行起始。
`(write-char char port)`
該函數向`port`寫入一個字符。
> 練習2
>
> 編寫函數`(my-copy-file)`實現文件的拷貝。
> 練習3
>
> 編寫函數`(print-line)`,該函數具有任意多的字符作為參數,并將它們輸出至標準輸出。輸出的字符應該用新行分隔。
## 9.4?小結
因為Scheme的IO設施非常的小,所以本章也十分短。下一章中,我會講解賦值。
## 9.5?習題解答
### 9.5.1?答案1
~~~
(define (group-list ls sep)
(letrec ((iter (lambda (ls0 ls1)
(cond
((null? ls0) (list ls1))
((eqv? (car ls0) sep)
(cons ls1 (iter (cdr ls0) '())))
(else (iter (cdr ls0) (cons (car ls0) ls1)))))))
(map reverse (iter ls '()))))
(define (read-lines file-name)
(with-input-from-file file-name
(lambda ()
(let loop((ls1 '()) (c (read-char)))
(if (eof-object? c)
(map list->string (group-list (reverse ls1) #\Linefeed)) ; *
(loop (cons c ls1) (read-char)))))))
~~~
示例:
~~~
(group-list '(1 4 0 3 7 2 0 9 5 0 0 1 2 3) 0)
;Value 13: ((1 4) (3 7 2) (9 5) () (1 2 3))
(read-lines "hello.txt")
;Value 14: ("Hello world!" "Scheme is an elegant programming language." "")
~~~
### 9.5.2?答案2
~~~
(define (my-copy-file from to)
(let ((pfr (open-input-file from))
(pto (open-output-file to)))
(let loop((c (read-char pfr)))
(if (eof-object? c)
(begin
(close-input-port pfr)
(close-output-port pto))
(begin
(write-char c pto)
(loop (read-char pfr)))))))
~~~
### 9.5.3?答案3
~~~
(define (print-lines . lines)
(let loop((ls0 lines))
(if (pair? ls0)
(begin
(display (car ls0))
(newline)
(loop (cdr ls0))))))
~~~