對C語言輸入輸出流和緩衝區的深入理解
緩衝區 又稱為快取,它是記憶體空間的一部分。也就是說,在記憶體空間中預留了一定的儲存空間,這些儲存空間用來緩衝輸入或輸出的資料,這部分預留的空間就叫做緩衝區。
緩衝區根據其對應的是輸入裝置還是輸出裝置,分為輸入緩衝區和輸出緩衝區。
1、為什麼要引入緩衝區
例如,我們從磁盤裡取資訊,我們先把讀出的資料放在緩衝區,計算機再直接從緩衝區中取資料,等緩衝區的資料取完後再去磁碟中讀取,這樣就可以減少磁碟的讀寫次數,再加上計算機對緩衝區的操作大大快於對磁碟的操作,故應用緩衝區可大大提高計算機的執行速度。
又比如,我們使用印表機列印文件,由於印表機的列印速度相對較慢,我們先把文件輸出到印表機相應的緩衝區,印表機再自行逐步列印,這時我們的CPU可以處理別的事情。
現在您基本明白了吧,緩衝區就是一塊記憶體區, 它用在輸入輸出裝置和CPU之間,用來快取資料 。它 使得低速的輸入輸出裝置和高速的CPU能夠協調工作 ,避免低速的輸入輸出裝置佔用CPU,解放出CPU,使其能夠高效率工作。
2、緩衝區的型別
緩衝區分為三種類型:全緩衝、行緩衝和不帶緩衝。
1) 全緩衝
在這種情況下,當 填滿 標準I/O快取後才進行實際I/O操作。全緩衝的典型代表是 對磁碟檔案的讀寫 。
2) 行緩衝
在這種情況下,當在輸入和輸出中遇到 換行符 時,執行真正的I/O操作。這時,我們輸入的字元先存放在緩衝區,等 按下回車鍵換行 時才進行實際的I/O操作。典型代表是 標準輸入(stdin) 和 標準輸出(stdout) 。
3) 不帶緩衝
也就是不進行緩衝,標準出錯情況stderr是典型代表,這使得出錯資訊可以直接儘快地顯示出來。
3、緩衝區的大小
如果我們沒有自己設定緩衝區的話,系統會預設為標準輸入輸出設定一個緩衝區,這個緩衝區的大小通常是 512個位元組 的大小。
緩衝區大小由 stdio.h 標頭檔案中的巨集 BUFSIZ 定義,如果希望檢視它的大小,包含標頭檔案,直接輸出它的值即可:printf("%d", BUFSIZ);
緩衝區的大小是可以改變的,也可以將檔案關聯到自定義的緩衝區,詳情可以檢視 setvbuf()和 setbuf() 函式。
4、緩衝區的重新整理(清空)
下列情況會引發緩衝區的重新整理: 緩衝區滿時 ; 行緩衝區遇到回車時 ; 關閉檔案 ; 使用特定函式重新整理緩衝區 。
5、結合緩衝區談談C語言getchar()、getche()、getch()的區別
先來看一下 getchar() ,其原型為: int getchar(void);
當程式呼叫getchar()函式時,程式就等著使用者按鍵, 使用者輸入的字元被存放在鍵盤緩衝區中,直到使用者按回車為止(回車字元也放在緩衝區中) 。當用戶鍵入回車之後,getchar()函式 才開始從鍵盤緩衝區中每次讀入一個字元 。也就是說, 後續的getchar()函式呼叫不會等待使用者按鍵,而直接讀取緩衝區中的字元,直到緩衝區中的字元讀完後,才重新等待使用者按鍵 。打個比方,鍵盤緩衝區就像是一條水管連著你的程式,程式呼叫getchar()函式使用者輸入字元就相當於往水管裡注水,這個水注多少取決於你輸入多少,當你按回車停止注水時,getchar()函式才會開始從鍵盤緩衝區,也就是我們的水管裡取水,那每次只會讀一個字元也就是每次取一定量的水,當你在這之後繼續呼叫getchar()函式時,會接著在水管裡取上次沒用完的水,因為你的水管沒清空(緩衝區的重新整理),那這個階段就不用你再輸入了,因為一呼叫getchar()函式就有水可取嘛,直到水管裡沒水了,你還呼叫getchar()函式,那這個時候你就得注水了也就是程式會等你按鍵。
通俗一點說,當程式呼叫getchar()函式時,程式就等著使用者按鍵,並等使用者按下回車鍵返回。期間按下的字元存放在緩衝區,第一個字元作為函式返回值。繼續呼叫getchar()函式,將不再等使用者按鍵,而是返回您剛才輸入的第2個字元;繼續呼叫,返回第3個字元,直到緩衝區中的字元讀完後,才等待使用者按鍵。
getchar()函式的執行就是採用了行緩衝。第一次呼叫getchar()函式,會讓程式使用者(使用者)輸入一行字元並直至按下回車鍵 函式才返回。此時使用者輸入的字元和回車符都存放在行緩衝區。再次呼叫getchar()函式,會逐步輸出行緩衝區的內容。
請看下面一個例子:

執行結果如下:

再把程式做微小改變,你再看看,加深理解:

執行結果:

上面第二次列印時不是2而是空格,你應該想到為什麼了吧?
好,我們再來看一個例子:

執行結果:

getchar()函式是從 輸入流緩衝區 中讀取資料的,而不是從 鍵盤(終端)緩衝區 讀取。當讀取遇到回車(\n)結束時,這個'\n'會一起讀入到輸入流緩衝區的, 所以第一次接收輸入時取走字元後會留下字元\n,這樣第二次getchar()直接從緩衝區中把\n取走了 ,顯然讀取成功了,所以不會再從終端讀取!其實這裡的 10恰好是回車符 !這就是為什麼這個程式只執行了一次輸入操作就結束的原因!
getch()和getche()函式
在TC2.0時代,C程式設計師總是喜歡在程式末尾加上getch(),來實現程式執行完了暫停不退出的效果。如果不這樣做,在TC2.0的環境中Ctrl+F9編譯並執行後會立即退出程式,根本來不及看到結果。這時如果要看結果,就要按Alt+F5回到DOS環境中去,很麻煩。而如果在程式的結尾加上一行getch();語句,就可以省掉回DOS看結果這個步驟,因為程式執行完了並不退出,而是在程式最後把螢幕停住了,按任意鍵才退出程式。
實際上, getch() 的作用是從鍵盤 接收一個字元,且不帶回顯 。就是說, 你按了一個鍵後它並不在螢幕上顯示你按的什麼,而繼續執行後面的程式碼 ,所以在C語言中可以用它來實現“按任意鍵繼續”的效果,即程式中遇到getch();語句,就會停下來,等你按任意鍵,它接收了這個字元鍵後再繼續執行後面的程式碼。這跟上面在Windows下用的system(“PAUSE")功能一樣,但卻不會在螢幕上顯示(即不會有”按任意鍵繼續“的提示),這樣,利用getch()無回顯的特性,不管你按什麼鍵,都不會在螢幕上留下痕跡,使你的介面達到美觀效果。。
getche() 和getch()很相似,它也需要引入標頭檔案conio.h,它們之間的區別就在於:getch()無回顯,getche()有回顯。
下面看一個例子:

首先這是個連續5次的迴圈來實現5次停頓,等待你輸入。編譯並執行這個程式,假設輸入的是abcde,那麼螢幕上顯示的結果也是abcde,這個abcde並不是在ch=getch();中輸出的。把printf("%c",ch);這行語句去掉,就會發現按5次任意鍵程式就結束了,但螢幕上什麼都沒有顯示。
你可以把程式碼中的getch()換成getche()看看有什麼不同。如果還是輸入abcde,那麼螢幕上顯示的結果是aabbccddee,我們把printf("%c",ch);這行語句再去掉,顯示的結果就是abcde了,說明程式在執行ch=getche();這條語句的時候就把我們輸入的鍵返回顯示在螢幕上, 有無回顯就是它們的唯一區別 。