如題,本文深入了解了下C程序的緩沖輸入方面問題。
通常,系統(tǒng)使用行緩沖輸入,這意味著輸入的內(nèi)容會在您按下回車鍵之時被傳輸給程序,按下回車鍵的同時還將傳輸一個編程時需要注意的換行字符。ANSIC把緩沖輸入作為標(biāo)準(zhǔn)。
為了說明何謂緩沖輸入,特舉了一個簡單的例子(別在意例子意義,意在說明何謂緩沖輸入):
1 2 3 4 5 6 7 8 9 10 11 | #include <stdio.h> int main (void) { char ch; while (getchar() != 'a') continue; ch = getchar(); putchar(ch); putchar('\n'); return 0; } |
輸入下行:
Not alone!
會發(fā)現(xiàn)輸出為字符l,這就是緩沖輸入的體現(xiàn)。由于簡單,就不詳講了。
緩沖輸入通常給用戶帶來方便,他提供了在將輸入發(fā)送至程序前對其進(jìn)行編輯的機會,但在使用字符輸入時這會給編程人員帶來麻煩。其主要問題在于緩沖輸入需要您按下回車鍵來提交您的輸入。這一動作還傳輸一個程序必須處理的換行符。
尤其是scanf()函數(shù)和getchar()函數(shù)混用的時候。這是因為getchar()讀取每個字符,包括空格、制表符和換行符;而scanf()在讀取數(shù)字時則會跳過空格、制表符和換行符。為了說明它產(chǎn)生的問題,舉例如下。該程序讀取一個字符和兩個數(shù)作為輸入,然后使用由所輸入的兩個數(shù)字指定的行數(shù)和列數(shù)來打印該字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | /* 程序單1 */ #include <stdio.h> void display (char cr, int lines, int width); int main(void) { int ch; /* 要打印的字符 */ int rows, cols; /* 行數(shù)和列數(shù) */ printf ("Enter a character and two integers: \n"); while ((ch = getchar()) != '\n') { scanf ("%d %d", &rows, &cols); display (ch, rows, cols); printf ("Enter another character and two integers: \n"); printf ("Enter a newline to quit.\n"); } printf ("Bye.\n"); return 0; } void display(char cr, int lines, int width) { int row, col; for (row = 1; row <= lines; row++) { for (col = 1; col <=width; col++) putchar(cr); putchar('\n'); /* 結(jié)束本行,開始新的一行 */ } } |
書上也說這個程序是有這大問題的,我們也來看下問題在哪。
運行時輸入c 2 3,程序如期打印2行c字符,每行3個。然后該程序提示輸入第二組數(shù)據(jù),并在您還沒能做出響應(yīng)之前就退出了!這就是緊跟在第一個輸入行的3后面的那個換行符所導(dǎo)致的問題。scanf()函數(shù)將該換行符留在了輸入隊列中。而getchar()由于并不跳過換行符,所以在下一個循環(huán)時您輸入其他內(nèi)容之前,這一換行符由getchar()讀出,然后將其賦值給ch,而這正是終止循環(huán)的條件。
書中也給出了解決方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /* 程序單2 */ #include <stdio.h> void display (char cr, int lines, int width); int main(void) { int ch; /* 要打印的字符 */ int rows, cols; /* 行數(shù)和列數(shù) */ printf ("Enter a character and two integers: \n"); while ((ch = getchar()) != '\n') { scanf ("%d %d", &rows, &cols); display (ch, rows, cols); while (getchar() != '\n') //添加的語句 continue; printf ("Enter another character and two integers: \n"); printf ("Enter a newline to quit.\n"); } printf ("Bye.\n"); return 0; } void display(char cr, int lines, int width) { int row, col; for (row = 1; row <= lines; row++) { for (col = 1; col <=width; col++) putchar(cr); putchar('\n'); /* 結(jié)束本行,開始新的一行 */ } } |
看到這里,可能就有人說了,你這樣照搬照抄有何意思。下面就來說下我自己當(dāng)時的一些疑惑吧。
在程序單1時,我想了下輸入為cr 2 3時結(jié)果會是怎樣,當(dāng)時真沒想出來。之后輸入運行了下,輸出:
Enter another character and two integers:
Enter a newline to quit.
rrr
rrr
Enter another character and two integers:
Enter a newline to quit.
Bye.
Press any key to continue
當(dāng)時看到這些時更加困惑了,為什么會這樣,c呢?百思不得其解。所以我又在程序單2中輸入cr 2 3,剛開始輸出如下:
Enter another character and two integers:
Enter a newline to quit.
并要求繼續(xù)輸入。當(dāng)時頗感無語,完全沒懂。然后又慢慢輸入:
1
2
3
結(jié)果:
111
111
Enter another character and two integers:
Enter a newline to quit.
這個對了!那么為什么前面輸入cr 2 3時是那樣輸出呢。按了下?lián)Q行程序結(jié)束后苦思冥想。
現(xiàn)在真是想通了,理解的透徹,我來說下吧:
在程序單1輸入cr 2 3時,getchar()讀取了輸入隊列中的c之后,scanf()無法讀取r及其之后的字符,只能使用默認(rèn)的值。而這時的rows和cols的值由于只是在前面聲明并沒有賦值,所以一般是負(fù)的大數(shù)。就假設(shè)這一次循環(huán)的輸入為c -16653 -16652,帶入display程序,當(dāng)然是什么都沒顯示啦。然后輸出了兩行提示信息。而這時由于緩沖的輸入隊列中有值,不等您反應(yīng)繼續(xù)帶入,即r 2 3,而這就是之后所輸出的內(nèi)容了。說到這里您可以把程序單1中的rows和cols聲明時分別初始化為1和2,看下結(jié)果就知道了。
至于程序單2,要先知道添加的while語句的作用。它把scanf()輸入后的所有字符,包括換行符都給剔除了。這樣能讓循環(huán)準(zhǔn)備好讀取即將輸入的下一行開始的第一個字符。也就是說,您輸入cr 2 3時,他先分別像程序單1中那樣輸入c -16653 -16652,把您輸入的c之后的r 2 3給剔除了包括3之后的換行符,然后輸出兩行提示等待您的再次輸入。
PS:好,寫到這里也差不多了(其實全部手碼的,信不信→_→實體書還是有麻煩的),今天感悟頗大,收獲頗豐。一直以為C學(xué)的還好,今天拿起以前買的C Primer Plus隨便翻了翻發(fā)現(xiàn)有好多不懂,才知道實在是想當(dāng)然了,我會的只是C的語法規(guī)則而已?戳8.5(創(chuàng)建更友好用戶界面)和8.6(輸入確認(rèn)),終于明白了我們大學(xué)生的編程只擁有算法的正確性,而算法的健壯性真的是不堪入目,也終于明白了一個真正的程序所需要擁有的東西以及編寫一個能用的軟件有多艱難。下面再給出書中描述的輸入流與字符的關(guān)系,個人感覺受教了。
輸入流與字符:
如下一行輸入:
is 28 12.4
在您眼中,該輸入是一串字符后面跟著一個整數(shù),然后是一個浮點值。對C程序而言,該輸入是一個字節(jié)流。第一個字節(jié)是字母i的字符編碼,第二個字節(jié)是字母s的字符編碼,第三個字節(jié)是空格字符的字符編碼,第四個字節(jié)是數(shù)字2的字符編碼,等等。
雖然輸入流由字符組成,但如果您指示了scanf()函數(shù)他就可以將這些字符轉(zhuǎn)換成數(shù)值。例如,考慮下面輸入:
42
如果您在scanf()中使用%c說明符,該函數(shù)將只讀取字符4并將其存儲在一個char類型的變量中。如果您使用%s說明符,該函數(shù)會讀取兩個字符,即字符4和2,并將它們存儲在一個字符串中。如果使用%d說明符,則scanf()讀取同樣的兩個字符,但是隨后它會繼續(xù)計算與它們相應(yīng)的整數(shù)值為4*10+2,即42;然后講該整數(shù)的二進(jìn)制表示保存到一個int變量中。如果使用%f說明符,則scanf()讀取這兩個字符,計算它們對應(yīng)的數(shù)值42,然后以內(nèi)部浮點表示該值,并將結(jié)果保存在一個float變量中。
C程序?qū)⑤斎胍暈橐粋外來字節(jié)的流。簡言之,輸入由字符組成,但scanf()可以將輸入轉(zhuǎn)換成整數(shù)或浮點值。使用像%d或%f這樣的說明符能限制可接受的輸入的字符類型,但getchar()和使用%c的scanf()接收任何字符。