Common Lisp 有著威力強(qiáng)大的 I/O 工具弧呐。針對輸入以及一些普遍讀取字符的函數(shù),我們有?read?瓢湃,包含了一個完整的解析器 (parser)矢炼。針對輸出以及一些普遍寫出字符的函數(shù),我們有?format?慷垮,它自己幾乎就是一個語言勋又。本章介紹了所有基本的概念。
Common Lisp 有兩種流 (streams)换帜,字符流與二進(jìn)制流楔壤。本章描述了字符流的操作;二進(jìn)制流的操作涵蓋在 14.2 節(jié)惯驼。
7.1 流 (Streams)
流是用來表示字符來源或終點(diǎn)的 Lisp 對象蹲嚣。要從文件讀取或?qū)懭氲莨澹銓⑽募鳛榱鞔蜷_。但流與文件是不一樣的隙畜。當(dāng)你在頂層讀入或印出時抖部,你也可以使用流。你甚至可以創(chuàng)建可以讀取或?qū)懭胱址牧鳌?/p>
輸入缺省是從?*standard-input*?流讀取议惰。輸出缺省是在?*standard-output*?流慎颗。最初它們大概會在相同的地方:一個表示頂層的流。
我們已經(jīng)看過?read?與?format?是如何在頂層讀取與印出言询。前者接受一個應(yīng)是流的選擇性參數(shù)俯萎,缺省是?*standard-input*?。?format?的第一個參數(shù)也可以是一個流运杭,但當(dāng)它是?t?時夫啊,輸出被送到?*standard-output*?。所以我們目前為止都只用到缺省的流而已辆憔。我們可以在任何流上面做同樣的 I/O 操作撇眯。
路徑名(pathname)是一種指定一個文件的可移植方式。路徑名包含了六個部分:host虱咧、device熊榛、directory、name腕巡、type 及 version玄坦。你可以通過調(diào)用make-pathname?搭配一個或多個對應(yīng)的關(guān)鍵字參數(shù)來產(chǎn)生一個路徑。在最簡單的情況下逸雹,你可以只指明名字营搅,讓其他的部分留為缺省:
[1]> (setf path (make-pathname :name "myfile"))
#P"myfile"
開啟一個文件的基本函數(shù)是?open?梆砸。它接受一個路徑名?[1]?以及大量的選擇性關(guān)鍵字參數(shù)转质,而若是開啟成功時,返回一個指向文件的流帖世。
你可以在創(chuàng)建流時休蟹,指定你想要怎么使用它。 無論你是要寫入流日矫、從流讀取或者同時進(jìn)行讀寫操作赂弓,都可以通過?direction?參數(shù)設(shè)置。三個對應(yīng)的數(shù)值是?:input,?:output?,?:io?哪轿。如果是用來輸出的流盈魁,?if-exists?參數(shù)說明了如果文件已經(jīng)存在時該怎么做;通常它應(yīng)該是?:supersede?(譯注: 取代)窃诉。所以要創(chuàng)建一個可以寫至?"myfile"?文件的流杨耙,你可以:
[2]> (setf str (open path :direction :output
? ? ? ? ? ? ? ? ? ? ? :if-exists :supersede))
#<OUTPUT BUFFERED FILE-STREAM CHARACTER #P"myfile">
流的打印表示法因?qū)崿F(xiàn)而異赤套。
現(xiàn)在我們可以把這個流作為第一個參數(shù)傳給?format?,它會在流印出珊膜,而不是頂層:
[3]> (format str "Something~%")
NIL
如果我們在此時檢查這個文件容握,可能有輸出,也可能沒有车柠。某些實(shí)現(xiàn)會將輸出累積成一塊 (chunks)再輸出剔氏。直到我們將流關(guān)閉,它也許一直不會出現(xiàn):
[4]> (close str)
T
當(dāng)你使用完時竹祷,永遠(yuǎn)記得關(guān)閉文件谈跛;在你還沒關(guān)閉之前,內(nèi)容是不保證會出現(xiàn)的∪芡剩現(xiàn)在如果我們檢查文件 “myfile” 币旧,應(yīng)該有一行:
Something
在我的電腦輸入vim myfile践险,看到的結(jié)果如下圖所示猿妈。
如果我們只想從一個文件讀取,我們可以開啟一個具有?:direction?:input?的流 :
[5]> (setf str (open path :direction :input))
#<INPUT BUFFERED FILE-STREAM CHARACTER #P"myfile" @1>
我們可以對一個文件使用任何輸入函數(shù)巍虫。7.2 節(jié)會更詳細(xì)的描述輸入彭则。這里作為一個示例,我們將使用?read-line?從文件來讀取一行文字:
[6]> (read-line str)
"Something" ;
NIL
[7]> (close str)
T
當(dāng)你讀取完畢時占遥,記得關(guān)閉文件俯抖。
大部分時間我們不使用?open?與?close?來操作文件的 I/O 。?with-open-file?宏通常更方便瓦胎。它的第一個參數(shù)應(yīng)該是一個列表芬萍,包含了變數(shù)名、伴隨著你想傳給open?的參數(shù)搔啊。在這之后柬祠,它接受一個代碼主體,它會被綁定至流的變數(shù)一起被求值负芋,其中流是通過將剩余的參數(shù)傳給?open?來創(chuàng)建的漫蛔。之后這個流會被自動關(guān)閉。所以整個文件寫入動作可以表示為:
[8]> (with-open-file (str path :direction :output
? ? ? ? ? ? ? ? ? ? ? ? ? :if-exists :supersede)
? (format str "Something~%"))
with-open-file?宏將?close?放在?unwind-protect?里 (參見 92 頁旧蛾,譯注: 5.6 節(jié))莽龟,即使一個錯誤打斷了主體的求值,文件是保證會被關(guān)閉的锨天。
7.2 輸入 (Input)
兩個最受歡迎的輸入函數(shù)是?read-line?及?read?毯盈。前者讀入換行符 (newline)之前的所有字符,并用字符串返回它們病袄。它接受一個選擇性流參數(shù) (optional stream argument)搂赋;若流忽略時迟赃,缺省為?*standard-input*?:
[9]> (progn
? ? (format t "Please enter your name: ")
? ? (read-line))
Please enter your name: Chan
"Chan" ;
NIL
如果你想要原封不動的輸出,這是你該用的函數(shù)厂镇。(第二個返回值只在?read-line?在遇到換行符之前纤壁,用盡輸入時返回真。)
在一般情況下捺信,?read-line?接受四個選擇性參數(shù): 一個流酌媒;一個參數(shù)用來決定遇到?end-of-file?時,是否產(chǎn)生錯誤迄靠;若前一個參數(shù)為?nil?時秒咨,該返回什么;第四個參數(shù) (在 235 頁討論)通痴浦浚可以省略雨席。
所以要在頂層顯示一個文件的內(nèi)容,我們可以使用下面這個函數(shù):
(defun pseudo-cat (file)
? (with-open-file (str file :direction :input)
? ? (do ((line (read-line str nil 'eof)
? ? ? ? ? ? ? (read-line str nil 'eof)))
? ? ? ? ((eql line 'eof))
? ? ? (format t "~A~%" line))))
如果我們想要把輸入解析為 Lisp 對象吠式,使用?read?陡厘。這個函數(shù)恰好讀取一個表達(dá)式,在表達(dá)式結(jié)束時停止讀取特占。所以可以讀取多于或少于一行糙置。而當(dāng)然它所讀取的內(nèi)容必須是合法的 Lisp 語法。
如果我們在頂層使用?read?是目,它會讓我們在表達(dá)式里面谤饭,想用幾個換行符就用幾個:
[15]> (read)
(a
b
c)
(A B C)
換句話說,如果我們在一行里面輸入許多表達(dá)式懊纳,?read?會在第一個表達(dá)式之后揉抵,停止處理字符,留下剩余的字符給之后讀取這個流的函數(shù)處理嗤疯。所以如果我們在一行輸入多個表達(dá)式冤今,來回應(yīng)?ask-number?(20 頁。譯注:2.10 小節(jié))所印出提示符身弊,會發(fā)生如下情形:
>(ask-number)
Please enter a number. a b
Please enter a number. Please enter a number. 43
43
兩個連續(xù)的提示符 (successive prompts)在第二行被印出辟汰。第一個?read?調(diào)用會返回?a?,而它不是一個數(shù)字阱佛,所以函數(shù)再次要求一個數(shù)字帖汞。但第一個?read?只讀取到?a?的結(jié)尾。所以下一個?read?調(diào)用返回?b?凑术,導(dǎo)致了下一個提示符翩蘸。
你或許想要避免使用?read?來直接處理使用者的輸入。前述的函數(shù)若使用?read-line?來獲得使用者輸入會比較好淮逊,然后對結(jié)果字符串調(diào)用?read-from-string?催首。這個函數(shù)接受一個字符串扶踊,并返回第一個讀取的表達(dá)式:
[18]> (read-from-string "a b c")
A ;
2
它同時返回第二個值,一個指出停止讀取字符串時的位置的數(shù)字郎任。
在一般情況下秧耗,?read-from-string?可以接受兩個選擇性參數(shù)與三個關(guān)鍵字參數(shù)。兩個選擇性參數(shù)是?read?的第三舶治、第四個參數(shù): 一個 end-of-file (這個情況是字符串) 決定是否報錯分井,若不報錯該返回什么。關(guān)鍵字參數(shù)?:start?及?:end?可以用來劃分從字符串的哪里開始讀霉猛。
所有的這些輸入函數(shù)是由基本函數(shù) (primitive)?read-char?所定義的尺锚,它讀取一個字符。它接受四個與?read?及?read-line?一樣的選擇性參數(shù)惜浅。Common Lisp 也定義一個函數(shù)叫做?peek-char?瘫辩,跟?read-char?類似,但不會將字符從流中移除坛悉。
7.3 輸出 (Output)
三個最簡單的輸出函數(shù)是?prin1?,?princ?以及?terpri?伐厌。這三個函數(shù)的最后一個參數(shù)皆為選擇性的流參數(shù),缺省是?*standard-output*?吹散。
prin1?與?princ?的差別大致在于?prin1?給程序產(chǎn)生輸出弧械,而?princ?給人類產(chǎn)生輸出八酒。所以舉例來說空民,?prin1?會印出字符串左右的雙引號,而?princ?不會:
[19]> (prin1 "Hello")
"Hello"
"Hello"
[20]> (princ "Hello")
Hello
"Hello"
兩者皆返回它們的第一個參數(shù) (譯注: 第二個值是返回值) ── 順道一提羞迷,是用?prin1?印出界轩。?terpri?僅印出一新行。
有這些函數(shù)的背景知識在解釋更為通用的?format?是很有用的衔瓮。這個函數(shù)幾乎可以用在所有的輸出浊猾。他接受一個流 (或?t?或?nil?)、一個格式化字符串 (format string)以及零個或多個額外的參數(shù)热鞍。格式化字符串可以包含特定的格式化指令 (format directives)葫慎,這些指令前面有波浪號?~?。某些格式化指令作為字符串的占位符 (placeholder)使用薇宠。這些位置會被格式化字符串之后偷办,所給入?yún)?shù)的表示法所取代。
如果我們把?t?作為第一個參數(shù)澄港,輸出會被送至?*standard-output*?椒涯。如果我們給?nil?,?format?會返回一個它會如何印出的字符串回梧。為了保持簡短废岂,我們會在所有的示例里演示怎么做祖搓。
由于每人的觀點(diǎn)不同,?format?可以是令人驚訝的強(qiáng)大或是極為可怕的復(fù)雜湖苞。有大量的格式化指令可用拯欧,而只有少部分會被大多數(shù)程序設(shè)計師使用。兩個最常用的格式化指令是?~A?以及?~%?财骨。(你使用?~a?或?~A?都沒關(guān)系哈扮,但后者較常見,因?yàn)樗尭袷交噶羁雌饋硪荒苛巳弧? 一個?~A?是一個值的占位符蚓再,它會像是用?princ印出一般滑肉。一個?~%?代表著一個換行符 (newline)。
[21]> (format nil "Dear ~A, ~% Our records indicate..."
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "Mr. Malatesta")
"Dear Mr. Malatesta,
Our records indicate..."
這里?format?返回了一個值摘仅,由一個含有換行符的字符串組成靶庙。
~S?格式化指令像是?~A?,但它使用?prin1?印出對象娃属,而不是?princ?印出:
[22]> (format t "~S ~A" "a" "b")
"a"? b
NIL
格式化指令可以接受參數(shù)六荒。?~F?用來印出向右對齊 (right-justified)的浮點(diǎn)數(shù),可接受五個參數(shù):
1)要印出字符的總數(shù)矾端。缺省是數(shù)字的長度掏击。
2)小數(shù)之后要印幾位數(shù)。缺省是全部秩铆。
3)小數(shù)點(diǎn)要往右移幾位 (即等同于將數(shù)字乘 10)砚亭。缺省是沒有。
4)若數(shù)字太長無法滿足第一個參數(shù)時殴玛,所要印出的字符捅膘。如果沒有指定字符,一個過長的數(shù)字會盡可能使用它所需的空間被印出滚粟。
5)數(shù)字開始印之前左邊的字符寻仗。缺省是空白。
下面是一個有五個參數(shù)的罕見例子:
[23]> (format nil "~10,2,0,'*,' F" 26.21875)
"? ? 26.22"
這是原本的數(shù)字取至小數(shù)點(diǎn)第二位凡壤、(小數(shù)點(diǎn)向左移 0 位)署尤、在 10 個字符的空間里向右對齊,左邊補(bǔ)滿空白亚侠。注意作為參數(shù)給入是寫成?'*?而不是?#\*?曹体。由于數(shù)字塞得下 10 個字符,不需要使用第四個參數(shù)盖奈。
所有的這些參數(shù)都是選擇性的混坞。要使用缺省值你可以直接忽略對應(yīng)的參數(shù)。如果我們想要做的是,印出一個小數(shù)點(diǎn)取至第二位的數(shù)字究孕,我們可以說:
[24]> (format nil "~,2,,,F" 26.21875)
"26.22"
你也可以忽略一系列的尾隨逗號 (trailing commas)啥酱,前面指令更常見的寫法會是:
[25]> (format nil "~,2F" 26.21875)
"26.22"
警告:?當(dāng)?format?取整數(shù)時,它不保證會向上進(jìn)位或向下舍入厨诸。就是說?(format?nil?"~,1F"?1.25)?可能會是?"1.2"?或?"1.3"?镶殷。所以如果你使用?format?來顯示資訊時,而使用者期望看到某種特定取整數(shù)方式的數(shù)字 (如: 金額數(shù)量)微酬,你應(yīng)該在印出之前先顯式地取好整數(shù)绘趋。
7.4 示例:字符串代換 (Example: String Substitution)
作為一個 I/O 的示例,本節(jié)演示如何寫一個簡單的程序來對文本文件做字符串替換颗管。我們即將寫一個可以將一個文件中陷遮,舊的字符串?old?換成某個新的字符串new?的函數(shù)。最簡單的實(shí)現(xiàn)方式是將輸入文件里的每一個字符與?old?的第一個字符比較垦江。如果沒有匹配帽馋,我們可以直接印出該字符至輸出。如果匹配了比吭,我們可以將輸入的下一個字符與?old?的第二個字符比較绽族,等等。如果輸入字符與?old?完全相等時衩藤,我們有一個成功的匹配吧慢,則我們印出?new?至文件。
而要是?old?在匹配途中失敗了赏表,會發(fā)生什么事呢检诗?舉例來說,假設(shè)我們要找的模式 (pattern)是?"abac"?底哗,而輸入文件包含的是?"ababac"?岁诉。輸入會一直到第四個字符才發(fā)現(xiàn)不匹配,也就是在模式中的?c?以及輸入的?b?才發(fā)現(xiàn)跋选。在此時我們可以將原本的?a?寫至輸出文件,因?yàn)槲覀円呀?jīng)知道這里沒有匹配哗蜈。但有些我們從輸入讀入的字符還是需要留著: 舉例來說前标,第三個?a?,確實(shí)是成功匹配的開始炼列。所以在我們要實(shí)現(xiàn)這個算法之前,我們需要一個地方來儲存俭尖,我們已經(jīng)從輸入讀入的字符,但之后仍然需要的字符。
一個暫時儲存輸入的隊(duì)列 (queue)稱作緩沖區(qū) (buffer)焰望。在這個情況里已亥,因?yàn)槲覀冎牢覀儾恍枰獌Υ娉^一個預(yù)定的字符量,我們可以使用一個叫做環(huán)狀緩沖區(qū)?ring?buffer?的資料結(jié)構(gòu)虑椎。一個環(huán)狀緩沖區(qū)實(shí)際上是一個向量。是使用的方式使其成為環(huán)狀: 我們將之后的元素所輸入進(jìn)來的值儲存起來捆姜,而當(dāng)我們到達(dá)向量結(jié)尾時,我們重頭開始泥技。如果我們不需要儲存超過?n?個值墨缘,則我們只需要一個長度為?n?或是大于?n?的向量,這樣我們就不需要覆寫正在用的值零抬。
在圖 7.1 的代碼镊讼,實(shí)現(xiàn)了環(huán)狀緩沖區(qū)的操作。?buf?有五個字段 (field): 一個包含存入緩沖區(qū)的向量平夜,四個其它字段用來放指向向量的索引 (indices)蝶棋。兩個索引是start?與?end?,任何環(huán)狀緩沖區(qū)的使用都會需要這兩個索引:?start?指向緩沖區(qū)的第一個值忽妒,當(dāng)我們?nèi)〕鲆粋€值時玩裙,?start?會遞增 (incremented);?end?指向緩沖區(qū)的最后一個值段直,當(dāng)我們插入一個新值時吃溅,?end?會遞增。
另外兩個索引鸯檬,?used?以及?new?决侈,是我們需要給這個應(yīng)用的基本環(huán)狀緩沖區(qū)所加入的東西。它們會介于?start?與?end?之間喧务。實(shí)際上赖歌,它總是符合:
start ≤ used ≤ new ≤ end
你可以把?used?與?new?想成是當(dāng)前匹配 (current match) 的?start?與?end?。當(dāng)我們開始一輪匹配時功茴,?used?會等于?start?而?new?會等于?end?庐冯。當(dāng)下一個字符 (successive character)匹配時,我們需要遞增?used?坎穿。當(dāng)?used?與?new?相等時展父,我們將開始匹配時返劲,所有存在緩沖區(qū)的字符讀入。我們不想要使用超過從匹配時所存在緩沖區(qū)的字符栖茉,或是重復(fù)使用同樣的字符姐叁。因此這個?new?索引忆某,開始等于?end?愉豺,但它不會在一輪匹配我們插入新字符至緩沖區(qū)一起遞增费彼。
函數(shù)?bref?接受一個緩沖區(qū)與一個索引,并返回索引所在位置的元素弃榨。借由使用?index?對向量的長度取?mod?鲸睛,我們可以假裝我們有一個任意長的緩沖區(qū)官辈。調(diào)用(new-buf?n)?會產(chǎn)生一個新的緩沖區(qū)拳亿,能夠容納?n?個對象肺魁。
要插入一個新值至緩沖區(qū)鹅经,我們將使用?buf-insert?瘾晃。它將?end?遞增幻妓,并把新的值放在那個位置 (譯注: 遞增完的位置)涌哲。相反的?buf-pop?返回一個緩沖區(qū)的第一個數(shù)值阀圾,接著將?start?遞增初烘。任何環(huán)狀緩沖區(qū)都會有這兩個函數(shù)肾筐。
接下來我們需要兩個特別為這個應(yīng)用所寫的函數(shù):?buf-next?從緩沖區(qū)讀取一個值而不取出吗铐,而?buf-reset?重置?used?與?new?到初始值典阵,分別是?start?與?end?壮啊。如果我們已經(jīng)把至?new?的值全部讀取完畢時撑蒜,?buf-next?返回?nil?座菠。區(qū)別這個值與實(shí)際的值不會產(chǎn)生問題拓萌,因?yàn)槲覀冎话阎荡嬖诰彌_區(qū)巡莹。
最后?buf-flush?透過將所有作用的元素降宅,寫至由第二個參數(shù)所給入的流腰根,而?buf-clear?通過重置所有的索引至?-1?將緩沖區(qū)清空额嘿。
在圖 7.1 定義的函數(shù)被圖 7.2 所使用册养,包含了字符串替換的代碼靠闭。函數(shù)?file-subst?接受四個參數(shù);一個查詢字符串拦键,一個替換字符串芬为,一個輸入文件以及一個輸出文件蟀悦。它創(chuàng)建了代表每個文件的流莲镣,然后調(diào)用?stream-subst?來完成實(shí)際的工作涎拉。
第二個函數(shù)?stream-subst?使用本節(jié)開始所勾勒的算法鼓拧。它一次從輸入流讀一個字符。直到輸入字符匹配要尋找的字符串時钮糖,直接寫至輸出流 (1)。當(dāng)一個匹配開始時酪我,有關(guān)字符在緩沖區(qū)?buf?排隊(duì)等候 (2)都哭。
變數(shù)?pos?指向我們想要匹配的字符在尋找字符串的所在位置纱新。如果?pos?等于這個字符串的長度穆趴,我們有一個完整的匹配阅羹,則我們將替換字符串寫至輸出流捏鱼,并清空緩沖區(qū) (3)酪耕。如果在這之前匹配失敗看尼,我們可以將緩沖區(qū)的第一個元素取出藏斩,并寫至輸出流狰域,之后我們重置緩沖區(qū)兆览,并從?pos?等于 0 重新開始 (4)抬探。
下列表格展示了當(dāng)我們將文件中的?"baro"?替換成?"baric"?所發(fā)生的事小压,其中文件只有一個單字?"barbarous"?:
第一欄是當(dāng)前字符 ──?c?的值;第二欄顯示是從緩沖區(qū)或是直接從輸入流讀雀攘 孩饼;第三欄顯示需要匹配的字符 ──?old?的第?posth?字符;第四欄顯示那一個條件式 (case)被求值作為結(jié)果梯码;第五欄顯示被寫至輸出流的字符;而最后一欄顯示緩沖區(qū)之后的內(nèi)容。在最后一欄里闯捎,?used?與?new?的位置一樣许溅,由一個冒號 (?:colon)表示茬祷。
在文件?"test1"?里有如下文字:
The struggle between Liberty and Authority is the most conspicuous feature
in the portions of history with which we are earliest familiar, particularly
in that of Greece, Rome, and England.
在我們對?(file-subst?"?th"?"?z"?"test1"?"test2")?求值之后,讀取文件?"test2"?為:
The struggle between Liberty and Authority is ze most conspicuous feature
in ze portions of history with which we are earliest familiar, particularly
in zat of Greece, Rome, and England.
為了使這個例子盡可能的簡單盹憎,圖 7.2 的代碼只將一個字符串換成另一個字符串铐刘。很容易擴(kuò)展為搜索一個模式而不是一個字面字符串陪每。你只需要做的是,將?char=調(diào)用換成一個你想要的更通用的匹配函數(shù)調(diào)用镰吵。
7.5 宏字符 (Macro Characters)
一個宏字符 (macro character)是獲得?read?特別待遇的字符檩禾。比如小寫的?a?,通常與小寫?b?一樣處理疤祭,但一個左括號就不同了: 它告訴 Lisp 開始讀入一個列表盼产。
一個宏字符或宏字符組合也稱作?read-macro?(讀取宏) 。許多 Common Lisp 預(yù)定義的讀取宏是縮寫勺馆。比如說引用 (Quote): 讀入一個像是?'a?的表達(dá)式時戏售,它被讀取器展開成?(quote?a)?。當(dāng)你輸入引用的表達(dá)式 (quoted expression)至頂層時灌灾,它們在讀入之時就會被求值嘿般,所以一般來說你看不到這樣的轉(zhuǎn)換盆佣。你可以透過顯式調(diào)用?read?使其現(xiàn)形:
[28]> (car (read-from-string "'a"))
QUOTE
引用對于讀取宏來說是不尋常的痹兜,因?yàn)樗脝我蛔址硎疽糯尽S辛艘粋€有限的字符集养叛,你可以在 Common Lisp 里有許多單一字符的讀取宏,來表示一個或更多字符。
這樣的讀取宏叫做派發(fā) (dispatching)讀取宏搜囱,而第一個字符叫做派發(fā)字符 (dispatching character)稽屏。所有預(yù)定義的派發(fā)讀取宏使用井號 (?#?)作為派發(fā)字符薄腻。我們已經(jīng)見過好幾個尽纽。舉例來說,?#'?是?(function?...)?的縮寫,同樣的?'?是?(quote?...)?的縮寫。
其它我們見過的派發(fā)讀取宏包括?#(...)?船逮,產(chǎn)生一個向量吗垮;?#nA(...)?產(chǎn)生數(shù)組;?#\?產(chǎn)生一個字符;?#S(n?...)?產(chǎn)生一個結(jié)構(gòu)讶泰。當(dāng)這些類型的每個對象被?prin1顯示時 (或是?format?搭配?~S),它們使用對應(yīng)的讀取宏?[2]?瓢姻。這表示著你可以寫出或讀回這樣的對象:
[29]> (let ((*print-array* t))
? ? (vectorp (read-from-string (format nil "~S"
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (vector 1 2)))))
T
當(dāng)然我們拿回來的不是同一個向量,而是具有同樣元素的新向量凯楔。
不是所有對象被顯示時都有著清楚 (distinct)、可讀的形式富弦。舉例來說盏缤,函數(shù)與哈希表潭流,傾向于這樣?#<...>?被顯示讼撒。實(shí)際上?#<...>?也是一個讀取宏辈双,但是特別用來產(chǎn)生當(dāng)遇到?read?的錯誤。函數(shù)與哈希表不能被寫出與讀回來担映,而這個讀取宏確保使用者不會有這樣的幻覺废士。?[3]
當(dāng)你定義你自己的事物表示法時 (舉例來說,結(jié)構(gòu)的印出函數(shù))蝇完,你要將此準(zhǔn)則記住官硝。要不使用一個可以被讀回來的表示法,或是使用?#<...>?短蜕。
Chapter 7 總結(jié) (Summary)
1)流是輸入的來源或終點(diǎn)氢架。在字符流里,輸入輸出是由字符組成朋魔。
2)缺省的流指向頂層达箍。新的流可以由開啟文件產(chǎn)生。
3)你可以解析對象铺厨、字符組成的字符串、或是單獨(dú)的字符硬纤。
4)format?函數(shù)提供了完整的輸出控制解滓。
5)為了要替換文本文件中的字符串,你需要將字符讀入緩沖區(qū)筝家。
6)當(dāng)?read?遇到一個宏字符像是?'?洼裤,它調(diào)用相關(guān)的函數(shù)。