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