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