摘要:在使用安全、方便的c++的I/O流操作中,某些細(xì)節(jié)的處理往往被忽視,這會給看似簡單的程序帶來料想不到的影響。文章結(jié)合作者多年c語言及c++語言的教學(xué)經(jīng)驗,針對I/O流在教學(xué)過程中遇到的問題,對緩沖式輸入、輸出操作進(jìn)行了較為深入的研究,提出了若干使用技巧,希望能給c++教學(xué)和c++語言的運用提供一些有益的借鑒。
關(guān)鍵詞:C++;I/O流;緩存;eof函數(shù)
0引言
與c語言不同,c++可使用類型安全的I/O操作。插入操作符“<<”和提取操作符“>>”被重載以便能接受特定類型的數(shù)據(jù)。如果實際的數(shù)據(jù)類型和函數(shù)不匹配,則會終止程序執(zhí)行。如果需要處理非法的數(shù)據(jù)類型,則用戶可以通過流中設(shè)置的不同錯誤標(biāo)志來測試輸入輸出的成功與否。因此,c++的輸入輸出較c語言更加安全。另一方面,插入和提取操作都能自動識別其插入或提取數(shù)據(jù)的類型,所以比c語言要按指定格式輸入輸出數(shù)據(jù)類型要方便許多。但正因為其安全、方便,在使用中對一些細(xì)節(jié)往往關(guān)注不夠。在教學(xué)過程中發(fā)現(xiàn),學(xué)生中許多簡單的、看似毫無問題的程序經(jīng)常會產(chǎn)生意想不到的結(jié)果,這種情況往往會使學(xué)生和使用者動搖對c++穩(wěn)定性的信任。下面從一些具體問題出發(fā)分析其產(chǎn)生的根源并探討解決辦法。
1 COUt的輸出緩存問題
1.1交互式輸入輸出順序問題
在交互式的應(yīng)用程序中,常常希望在屏幕上出現(xiàn)提示信息后,用戶再鍵入相應(yīng)的數(shù)據(jù)作出響應(yīng)。即,程序在處理輸入操作前先要顯示提示信息。但在使用不當(dāng)?shù)那闆r下,這種交互的效果不但達(dá)不到,反而會出現(xiàn)令人費解的現(xiàn)象,先來看下面程序l所示的簡單例子。
運行程序1后發(fā)現(xiàn),程序并未如設(shè)想的那樣,先輸出提示信息,后再接受輸入;而是在輸入數(shù)據(jù)之后才輸出此前的那條提示信息。為什么輸出順序顛倒呢?其根源在于c++的I/0流內(nèi)部帶有緩沖區(qū),而cout就是一個I/0流ostream流類的一個標(biāo)準(zhǔn)的輸出對象。在有輸出緩存的情況下,輸出并不都是即時的。只有當(dāng)輸出緩存已滿、程序明確要求、或程序結(jié)束時,輸出緩存的信息才會顯示出來。
下面,換一種輸入方式,將程序l中的輸入語句改為用cin實現(xiàn),如程序2所示。運行該程序的結(jié)果是輸出順序不再是程序1的顛倒順序,而是正常的順序了。原來,c++提供了成員函數(shù)tie來同步istream和ostream的操作,以保證輸出在輸入之前顯示出來。即調(diào)用語句cin.tie(cout);
可以把cout連接到cin。事實上,無須顯式地在代碼中使用此調(diào)用語句,c++會自動執(zhí)行該調(diào)用來創(chuàng)建用戶的標(biāo)準(zhǔn)輸入/輸出環(huán)境。這就是為什么程序2能得以正常執(zhí)行的原因。
那么如何使程序l中的cout在沒有和getchar()輸入“捆綁”在一起的前提下,也能按正確的順序輸入輸出,也就是說無論輸出緩存是否已滿,都能即時輸出,這就需要通過手動刷新緩沖區(qū)的方式來強制要求輸出緩存的信息。具體可以用流操作符endl或者flush來清空輸出緩存,以達(dá)到即時輸出的目的。見程序3。
1.2 cout中多表達(dá)式的輸出順序問題
cout允許其后插入多個表達(dá)式,但在許多c++系統(tǒng)中,多個表達(dá)式之間的求值順序卻出乎意料。觀察程序4的運行結(jié)果,我們看到,程序運行結(jié)果不是預(yù)想的:
f1
1
f2
2而是
f2
f1
1
2
如果將主函數(shù)改為如程序5所示的形式,則程序運行結(jié)果為:
1
2
f2
f1
可以看出,程序5先輸出了prinff的結(jié)果,然后再輸出cout的結(jié)果。而按程序本身的順序,應(yīng)該是cout的執(zhí)行在前,返回給prinff的結(jié)果在后。這也印證了cout在使用上要注意輸出緩存的問題。另一方面f2,f1的輸出順序與程序4一樣,都和預(yù)想的相反。其原因是,在許多c++系統(tǒng)中,無論是在cout的“<<”運算中,還是prinff的表達(dá)式求值中,多表達(dá)式的求值順序都是自右向左進(jìn)行的。所以在程序4里cout的多表達(dá)式輸出中,表達(dá)式送入輸出緩沖區(qū)的順序是自左向右的,而表達(dá)式的求值順序則是自右向左進(jìn)行的。因此,在多表達(dá)式輸出中,不要將相互有值依賴的表達(dá)式放到一個cout語句中,也盡量不要將帶緩沖的cout和不帶緩沖的prinff這兩種輸出混用,否則會帶來不可預(yù)料的結(jié)果。
2 cin的輸入緩存問題
通過標(biāo)準(zhǔn)輸入流cin輸入數(shù)據(jù)時,提取符“<<”能自動識別其后的數(shù)據(jù)類型,所以一旦輸入的數(shù)據(jù)類型與規(guī)定的類型不相匹配,流提取操作符就會設(shè)置流的failbit狀態(tài)位,輸入的數(shù)據(jù)就不會被提取,從而保證了輸入的安全性。但有時程序需要對非法類型的數(shù)據(jù)進(jìn)行處理,如在用cin對自定義類型進(jìn)行操作時,當(dāng)發(fā)現(xiàn)輸入有錯時需要予以糾正,以便重新輸入,這時就需要使用clear()函數(shù)將流的標(biāo)記更改為正確,如程序6所示。
運行程序6卻發(fā)現(xiàn),在輸入非法數(shù)據(jù)的時候,程序并不能重新接受數(shù)據(jù),而是陷入了死循環(huán)??梢娡ㄟ^clear()函數(shù)將流的標(biāo)記更改為正確還不夠,原因是cin是類型敏感的輸入,對非法類型的數(shù)據(jù)是不提取的;同時cin又是緩沖式輸入,不被提取的非法類型數(shù)據(jù)便一直留在緩沖區(qū)內(nèi)。這時只有通過get()成員函數(shù)清除掉緩沖區(qū)的非法類型數(shù)據(jù)后,cin才能重新提取正確的數(shù)據(jù)并送入變量,否則將會陷入死循環(huán)。所以,上述程序必須將被注釋掉的那條cin.getO語句變?yōu)橛行Тa才能達(dá)到目的。
3 eof函數(shù)的判定時間問題
許多地方對eofO函數(shù)的解釋都是,“判定是否已經(jīng)讀到文件的結(jié)尾,如果到文件結(jié)尾,該函數(shù)返回值為1,否則返回為0”。但在程序中使用該函數(shù)時常常會感到困惑,如程序7要實現(xiàn)的功能是將文本文件\"a.txt\"的內(nèi)容輸出到屏幕上。(假定文本文件的內(nèi)容是連續(xù)存放的26個小寫英文字母)
程序運行情況卻是,在屏幕上輸出26個小寫英文字母之后,又多輸出了一個“z”,即最后一個字符輸出了兩次。這說明當(dāng)文件指針到達(dá)文件末尾時,執(zhí)行eof并不會返回1,而是要到下一次讀取后才會返回1。
事實上,文件本身是沒有文件結(jié)束符EOF的。當(dāng)讀取文件中最后一個有效字符后,雖然文件指針已指向空白了,但這時還不知道是否到了文件末尾,只有再讀取一次文件,待讀不到任何內(nèi)容了,這時輸入流設(shè)置eofbit位,eof的返回值才為l,而空的內(nèi)容是不會被提取到變量的,故最后一次讀到變量中的內(nèi)容又被重復(fù)輸出了。避免多輸出一次的錯誤可采用如程序8的先讀取后判斷的方法。
4結(jié)束語
綜上所述,由于c++的標(biāo)準(zhǔn)輸入輸出流是帶緩沖的輸入輸出,使用中需注意以下問題。
(1)cout與cin之間的同步操作由系統(tǒng)自動執(zhí)行,但與其它輸入方式之間交互的正確性則需手動刷新緩沖區(qū)的方法來保證。
(2)在多表達(dá)式輸出中,不要將相互有值依賴關(guān)系的表達(dá)式放到一個cout語句中。
(3)同一程序中盡量不要將帶緩沖的cout和不帶緩沖的prinff這兩種輸出混用。
(4)cin不提取非法類型的數(shù)據(jù),若要處理非法數(shù)據(jù),必須借助其它輸入方式清掉輸入緩存中的非法數(shù)據(jù)。
(5)用eof函數(shù)判所讀取文件結(jié)束與否時,宜采用先讀取后判斷的步驟。
以上這些細(xì)節(jié)的處理常常是被忽視的,但這種忽視往往又會給看似簡單的程序帶來意想不到的影響,所以希望在教學(xué)及應(yīng)用中能夠?qū)@些問題的處理引起足夠的重視。
(注:本文中所涉及到的圖表、注解、公式等內(nèi)容請以PDF格式閱讀原文。)