Common Lisp:符號(hào)計(jì)算簡(jiǎn)單介紹(第九章)

輸入/輸出

9.1 導(dǎo)語(yǔ)

輸入/輸出,或者說(shuō)i/o,是計(jì)算機(jī)和世界交流的一種方式。lisp的read-eval-print循環(huán)就提供了一種簡(jiǎn)單的i/o。也就是從鍵盤(pán)讀入表達(dá)式,在顯示屏上輸出結(jié)果。有時(shí)候我們想要做更多的事情,使用本章介紹的i/o函數(shù),你可以創(chuàng)造你喜歡的程序打印信息。你甚至可以打印出問(wèn)題,等待用戶輸入回答。
i/0函數(shù)的另一種使用就是從磁盤(pán)文件中讀入數(shù)據(jù),或者寫(xiě)入一個(gè)文件以便你某一天在使用讀出。使用common lisp來(lái)做這些要比其它語(yǔ)言方便很多。
歷史上來(lái)說(shuō),輸入輸出是在lisp系統(tǒng)中最具爭(zhēng)議的一部分。甚至今天也沒(méi)有一個(gè)標(biāo)準(zhǔn)的窗口桌面系統(tǒng),例如。還沒(méi)有一個(gè)標(biāo)準(zhǔn)的鼠標(biāo)控制或者圖形設(shè)計(jì)。每一個(gè)lisp供應(yīng)商提供他們自己的工具,幸運(yùn)的是,最基礎(chǔ)的i/o工具最終被標(biāo)準(zhǔn)化了。本書(shū)會(huì)專注在基礎(chǔ)部分。

9.2 字符串

為了讓計(jì)算機(jī)在顯示屏上打印有用的信息,我們必須首先學(xué)習(xí)字符串(character strings),字符串(string)是一類序列的類型。在某些方面和列表時(shí)相似的,他是一個(gè)向量的子類型(將在第十三章討論),但是他們有一個(gè)不同的原始操作集合。
字符串求值為自身,就像數(shù)字一樣。請(qǐng)注意接下來(lái)的例子中,字符串不會(huì)轉(zhuǎn)換成全大寫(xiě)。字符串(string)不是字符(symbol),stringp斷言來(lái)判斷輸入是不是一個(gè)字符串。


1.JPG

如你所見(jiàn),字符串必須由雙引號(hào)括起來(lái),和單引號(hào)的引用字符和列表是不同的。兩個(gè)單引號(hào)并不等于一個(gè)雙引號(hào),這里必須使用雙引號(hào)。

9.3 format函數(shù)

正常情況下,format函數(shù)會(huì)返回nil,但是它引起的副作用就是會(huì)把一些信息顯示在顯示屏上或者寫(xiě)入文件中。如果我們想要輸出在顯示屏上,format的第一個(gè)參數(shù)應(yīng)該是字符T。(寫(xiě)入磁盤(pán)的時(shí)候會(huì)使用不同的字符)。第二個(gè)參數(shù)必須是字符串,叫做格式控制字符串。format函數(shù)輸出一個(gè)沒(méi)有引號(hào)的字符串,然后返回nil。

2.JPG

格式控制字符串也可以包括特殊格式指令,是由一個(gè)曲線開(kāi)始的,例如"%"指令就是讓format函數(shù)重新開(kāi)一行。兩個(gè)~%指令就可以在輸出中加一個(gè)空白行。
3.JPG

&指令的作用是新開(kāi)一行,除了已經(jīng)是新一行的情況下就不新開(kāi)了。所以兩個(gè)或者三個(gè)連續(xù)的&指令和一個(gè)~&指令的效果其實(shí)是相同的,這個(gè)指令的用處在于,在函數(shù)調(diào)用的時(shí)候,我們不是一直清楚光標(biāo)所處的位置在哪里。舉個(gè)例子,一些common lisp實(shí)現(xiàn)要求用戶在結(jié)束輸入的時(shí)候必須輸入一個(gè)換行,其他的實(shí)現(xiàn)回去并不要求。所以在format被調(diào)用的時(shí)候,光標(biāo)會(huì)處在一個(gè)不同的位置,這個(gè)位置取決于用戶是不是一定要換行。
在輸出多行的程序中,每一個(gè)格式控制字符串的前面都加上~&指令是一個(gè)好的習(xí)慣。這樣光標(biāo)就肯定是在一個(gè)新行開(kāi)始打印信息。
4.jpg

另一個(gè)很重要的格式化命令就是S,用處是在lisp對(duì)象的打印輸出中插入格式化信息。對(duì)于每一個(gè)在格式控制字符串中S出現(xiàn)的地方。format需要一個(gè)額外的參數(shù),在接下來(lái)的例子中,第一個(gè)S指令被符號(hào)boston所替代,第二個(gè)s指令被列表(new york)所替代,第三個(gè)~s由數(shù)字55替代。
5.JPG

下面還有另一個(gè)例子,函數(shù)square-talk接受一個(gè)數(shù)字作為輸入并且回答這個(gè)數(shù)字的平方。他其實(shí)并不返回平方,返回的是nil因?yàn)樗莊ormat的返回值。
6.JPG

mapcar返回的結(jié)果是一個(gè)nil的列表,因?yàn)槊恳粋€(gè)square-talk的返回值是nil。
A指令打印一個(gè)沒(méi)有使用轉(zhuǎn)義字符的對(duì)象,這是最簡(jiǎn)單的比較A和S指令的方式,S包括了引號(hào),~A沒(méi)有包括,一個(gè)引號(hào)就是一種轉(zhuǎn)義字符。
7.JPG

9.4 read函數(shù)

read函數(shù)是從鍵盤(pán)讀取一個(gè)lisp對(duì)象的函數(shù)(一個(gè)數(shù)字,符號(hào),列表或者其他),然后返回那個(gè)對(duì)象的值。對(duì)象不是一定要被引用因?yàn)樗粫?huì)被求值。通過(guò)在函數(shù)內(nèi)部調(diào)用一個(gè)read函數(shù),我們可以使得計(jì)算機(jī)在程序控制下從鍵盤(pán)讀取數(shù)據(jù)。下面的一些模板,回應(yīng)read函數(shù)的用戶輸入是加上下劃線的。


8.JPG

9.5 yes-or-no-p函數(shù)

yes-or-no-p函數(shù)接受一個(gè)格式控制字符串作為輸入,并要求用戶回答一個(gè)是還是否的問(wèn)題。用戶必須鍵入yes或者no來(lái)回應(yīng),相對(duì)的返回T或者nil。


9.JPG

這個(gè)函數(shù)有一個(gè)更簡(jiǎn)短的版本,叫做y-or-n-p,只需要用戶輸入y或者n作為回答。

9.6 使用with-open-file來(lái)讀取文件

with-open-file宏函數(shù)提供了一個(gè)方便的方式來(lái)訪問(wèn)文件。它的語(yǔ)法是這樣的:


10.JPG

with-open-file像let一樣,創(chuàng)建本地變量,然后設(shè)置成一個(gè)流對(duì)象來(lái)表示和那個(gè)文件的連接。流對(duì)象(stream object)是個(gè)特殊的lisp數(shù)據(jù)類型,專門(mén)用來(lái)描述和文件的連接。如果你想要看看效果,請(qǐng)看全局變量“terminal-io”的值。它的值是lisp從鍵盤(pán)讀入和輸出到顯示屏的流對(duì)象。


11.JPG

在with-open-file函數(shù)體內(nèi)部,流對(duì)象可以作為一個(gè)read函數(shù)的可選參數(shù),從文件輸入而不是鍵盤(pán)輸入。在離開(kāi)with-open-file函數(shù)后,文件的連接會(huì)自動(dòng)關(guān)閉。
我們來(lái)看一個(gè)從文件讀取數(shù)據(jù)的例子。假設(shè)文件“timer.bat”在目錄/usr/dst下面,內(nèi)容如下:
12.JPG

我們可以用下面的函數(shù)讀取數(shù)據(jù):


13.jpg

9.7 使用with-open-file來(lái)寫(xiě)入文件

我們也可以用特殊關(guān)鍵字來(lái)調(diào)用with-open-file來(lái)打開(kāi)文件輸出,關(guān)鍵字參數(shù)就是:direction和:output。with-open-file創(chuàng)造的流可以再format函數(shù)的參數(shù)位置使用。


14.JPG

如果我們只是使用~S指令來(lái)向文件寫(xiě)入數(shù)據(jù),我們確??梢栽僖淮巫x出來(lái)。當(dāng)然寫(xiě)入文件的信息可以使任意的,包括特殊標(biāo)點(diǎn),不成對(duì)的括號(hào)或者任何字符,但是我們將不可以用Lisp的read將他們讀回來(lái)了。這樣的文件仍然是有用的,因?yàn)樗梢员蝗怂x懂。

小結(jié)

format函數(shù)接受兩個(gè)或者更多的參數(shù),第一個(gè)參數(shù)是T的話就會(huì)打印到屏幕上,第二個(gè)必須是一個(gè)格式控制字符串。余下的蠶食是用來(lái)填補(bǔ)字符串中的指令S的。指令%的作用是開(kāi)一個(gè)新行。~&指令是沒(méi)新行開(kāi)新行,有新行不開(kāi)。
read函數(shù)從終端讀取一個(gè)lisp對(duì)象并返回那個(gè)對(duì)象。這個(gè)對(duì)象不是一定要背引用因?yàn)樗粫?huì)被求值。yes-or-no-p和y-o-n-p打印問(wèn)題(使用格式控制字符串)然后返回T或者nil來(lái)判斷輸入的答案。
with-open-file打開(kāi)一個(gè)文件來(lái)進(jìn)行輸入或者輸出,并且綁定一個(gè)變量到流對(duì)象來(lái)表現(xiàn)和文件的連接。這個(gè)流對(duì)象可以被傳送到read或者format來(lái)進(jìn)行文件i/o。

Lisp Toolkit: DRIBBLE

dribble函數(shù)將一個(gè)lisp過(guò)程的一部分記錄在文件中,如果你想要吧交互過(guò)程打印出來(lái)進(jìn)行展示,那這個(gè)工具會(huì)很有用。將一個(gè)文件名作為參數(shù),dribble打開(kāi)文件輸出然后開(kāi)始記錄。如果調(diào)用不含參數(shù),他會(huì)在記錄過(guò)后關(guān)閉文件。


15.jpg

第九章進(jìn)階話題

9.8 format指令的參數(shù)

一些format指令接受前綴參數(shù)來(lái)更加詳細(xì)定義他們的行為。前綴參數(shù)出現(xiàn)在波浪號(hào)和指令之間。例如,指令S接受一個(gè)寬度參數(shù),來(lái)詳細(xì)定義打印寬度,像這樣~10S,我們就可以進(jìn)行分欄輸出。

16.jpg

9.9 附加的format指令

指令D的作用是以十進(jìn)制方式打印整數(shù)。當(dāng)然打印成其他進(jìn)制也是可以的,甚至可以打印成羅馬數(shù)字,但是這里我們不討論這個(gè)。指令F是用來(lái)打印浮點(diǎn)數(shù),格式是固定包括一個(gè)小數(shù)點(diǎn)。所有這些指令都接受前綴參數(shù),我們第一個(gè)使用的參數(shù)是用來(lái)定義輸出的字符寬度:輸出應(yīng)該占到多少個(gè)字符寬度。(Lisp胡勇空格來(lái)填滿不夠的地方)。我們會(huì)接觸另一個(gè)前綴參數(shù),和F指令一起使用,第二個(gè)參數(shù)定義了在小數(shù)點(diǎn)后面具體打印多少位。例如,7,5F就是定義打印有7個(gè)字符寬,小數(shù)點(diǎn)后面是5位。

17.JPG

Lisp1.5中的輸出原始函數(shù)

原始的輸入輸出函數(shù)如TERPRI, PRIN1, PRINC, 和PRINT都定義在Lisp1.5中(所有現(xiàn)代Lisp系統(tǒng)的祖先)并且今天仍然可以在Common Lisp中使用。他們作為一個(gè)歷史記錄出現(xiàn)在進(jìn)階話題中;你可以用format函數(shù)實(shí)現(xiàn)相同的效果。terpri是終端輸出terminal print的縮寫(xiě)。他將光標(biāo)移動(dòng)到新行輸出,prin1使用任何需要的轉(zhuǎn)義字符來(lái)打印對(duì)象,以確保可以被read函數(shù)讀取。princ函數(shù)打印沒(méi)有轉(zhuǎn)義字符的對(duì)象?;旧?,S格式化指令的幸會(huì)類似于prin1,A的行為類似于princ。prin1和orinc都返回他們的第一個(gè)參數(shù)。

18.JPG

print函數(shù)是先前三個(gè)函數(shù)的組合,他會(huì)使用terpri開(kāi)始一個(gè)新行,用prin1打印它的參數(shù),然后用princ打印空格,一個(gè)簡(jiǎn)單版本的print定義如下。
19.JPG

terpri,prin1和princ都接受一個(gè)可選的流參數(shù),這就允許他們可以在文件i/o上使用。

9.11 處理end-of-file條件

有時(shí)候讀取一個(gè)不知道包含了多少對(duì)象的文件是很必要的,當(dāng)你的程序讀取到文件的結(jié)束的時(shí)候,下一個(gè)read函數(shù)就會(huì)生成end-of-file錯(cuò)誤,你可以在debugger中結(jié)束。在遇到end-of-file錯(cuò)誤的時(shí)候,告訴read函數(shù)不要報(bào)錯(cuò),而是返回一個(gè)特殊值也是可以的。我們要做的就是使read函數(shù)支持兩個(gè)額外的參數(shù):一個(gè)是NIL(意思是不要生成一個(gè)錯(cuò)誤),還有就是你想要用來(lái)表示eof的值。我們咋選擇這個(gè)值的時(shí)候必須小心。如果我們選了一個(gè)比較常用的,比如FOO,那程序中如果包含這個(gè)值,那程序就會(huì)以為已經(jīng)到了文件末尾了。因此,一個(gè)好的eof表示字符是一個(gè)新生成的內(nèi)存單元,我們會(huì)使用eq而不是equal來(lái)確保內(nèi)存單元被返回。
下面的程序?qū)嵗亲x取任意lisp對(duì)象的文件,告訴我們有多少單元被讀取,并返回一個(gè)列表。它使用的是內(nèi)存單元($eof$)作為特殊的end-of-file值,任何新生成的內(nèi)存單元都可以做這個(gè)標(biāo)記,重要的是哪個(gè)標(biāo)記的地址,而不是標(biāo)記的內(nèi)容。


20.JPG

輸入的數(shù)據(jù)如下:


21.JPG

最終結(jié)果如下:
22.JPG

9.12 用點(diǎn)式標(biāo)記打印

點(diǎn)式標(biāo)記是一個(gè)內(nèi)存單元表達(dá)式的變種,在點(diǎn)式標(biāo)記中每一個(gè)內(nèi)存單元被表示成一個(gè)左括號(hào),car部分,一個(gè)點(diǎn),cdr部分,和一個(gè)右括號(hào)。car和cdr部分,如果是列表的話那么他們本身回事點(diǎn)式標(biāo)記,使之成為一個(gè)地規(guī)定義。例如,列表(A)被表示成一個(gè)內(nèi)存單元,單獨(dú)的car字符A和cdr,nil。在點(diǎn)式標(biāo)記中這個(gè)列表被寫(xiě)成(A . NIL)。下面是更多例子:


23.JPG

9.13 混合標(biāo)記

lisp正常情況下用列表標(biāo)記打印,而不是點(diǎn)式標(biāo)記。但是我們也發(fā)現(xiàn),一個(gè)內(nèi)存單元格式,如(A . B)是不能夠用沒(méi)有點(diǎn)的標(biāo)記來(lái)表示的。lisp的原則就是只在需要的時(shí)候打印點(diǎn)。除非內(nèi)存單元鏈條的最后是一個(gè)非nil的接軌,不然點(diǎn)是不打印的。輸出是點(diǎn)式打印和純列表打印的混合的話,就成為混合標(biāo)記。下面的例子來(lái)區(qū)別。


24.JPG

進(jìn)階話題涉及函數(shù)

Lisp1.5原始輸出函數(shù):TERPRI, PRIN1, PRINC, PRINT

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容