C語言:清空緩沖區


緩沖區的優點很明顯,它加快了程序的運行速度,減少了硬件的讀寫次數,讓整個計算機變得流暢起來;但是,緩沖區也帶來了一些負面影響,經過前面幾節的學習相信讀者也見識到了。

那么,該如何消除這些負面影響呢?思路其實也很簡單,在輸入輸出之前清空(刷新)緩沖區即可:

  • 對於輸出操作,清空緩沖區會使得緩沖區中的所有數據立即顯示到屏幕上;很明顯,這些數據沒有地方存放了,只能輸出了。
  • 對於輸入操作,清空緩沖區就是丟棄殘留字符,讓程序直接等待用戶輸入,避免引發奇怪的行為。
本節的代碼用到了一些暫時沒有學到的知識,估計很多初學者會不理解,沒關系,那就先記住吧,記不住就復制吧,總之,按照葫蘆畫瓢就完了。

清空輸出緩沖區

清空輸出緩沖區很簡單,使用下面的語句即可:

fflush(stdout);

fflush() 是一個專門用來清空緩沖區的函數,stdout 是 standard output 的縮寫,表示標准輸出設備,也即顯示器。整個語句的意思是,清空標准輸出緩沖區,或者說清空顯示器的緩沖區。

Windows 平台下的 printf()、puts()、putchar() 等輸出函數都是不帶緩沖區的,所以不用清空,下面的代碼演示了如何在 Linux 和 Mac OS 平台下清空緩沖區:

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. int main()
  4. {
  5. printf("C語言中文網");
  6. fflush(stdout); //本次輸出結束后立即清空緩沖區
  7. sleep(5);
  8. printf("http://c.biancheng.net\n");
  9. return 0;
  10. }

程序運行后,第一個 pirntf() 立即輸出,等待 5 秒以后,第二個 printf() 才輸出,這就符合我們的慣性思維了。如果不加fflush(stdout)語句,程序運行后,第一個 printf() 並不會立即輸出,而是等待 5 秒以后和第二個 scanf() 一起輸出(已在《C語言數據輸出大匯總以及輕量進階》中進行了演示),這有點不符合我們的思維習慣。

清空輸入緩沖區

首先,很遺憾地說,沒有一種既簡潔明了又適用於所有平台的清空輸入緩沖區的方案。只有一種很蹩腳的方案能適用於所有平台,那就是將輸入緩沖區中的數據都讀取出來,但是卻不使用。

大家不要以為我很輕松地就能說出這句話,我FQ查閱了很多英文資料,又測試了很多平台和編譯器才敢說的。

我們先說兩種通用的方案,雖然它很蹩腳,但是卻行之有效。

1) 使用 getchar() 清空緩沖區

getchar() 是帶有緩沖區的,每次從緩沖區中讀取一個字符,包括空格、制表符、換行符等空白符,只要我們讓 getchar() 不停地讀取,直到讀完緩沖區中的所有字符,就能達到清空緩沖區的效果。請看下面的代碼:

  1. int c;
  2. while((c = getchar()) != '\n' && c != EOF);

該代碼不停地使用 getchar() 獲取緩沖區中的字符,直到遇見換行符\n或者到達文件結尾才停止。由於大家所學知識不足,這段代碼暫時無法理解,我也就不細說了,在實際開發中,大家按照下面的形式使用即可:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a = 1, b = 2;
  5. char c;
  6. scanf("a=%d", &a);
  7. while((c = getchar()) != '\n' && c != EOF); //在下次讀取前清空緩沖區
  8. scanf("b=%d", &b);
  9. printf("a=%d, b=%d\n", a, b);
  10. return 0;
  11. }

輸入示例:

a=100↙
b=200↙
a=100, b=200

按下第一個回車鍵后,只有第一個 scanf() 讀取成功了,第二個 scanf() 並沒有開始讀取,等我們再次輸入並按下回車鍵后,第二個 scanf() 才開始讀取,這就符合我們的操作習慣了。如果沒有清空緩沖區的語句,按下第一個回車鍵后,兩個 scanf() 都讀取了,只是第二個 scanf() 讀取失敗了,讓人覺得很怪異,這點已在《使用scanf從鍵盤輸入數據》中進行了演示。

改變輸入方式,再次嘗試一下:

a=100b=200↙
b=300↙
a=100, b=300

你看,第一次輸入的多余內容並沒有起作用,就是因為它們在第二個 scanf() 之前被清空了。

這種方案的關鍵之處在於,getchar() 是帶有緩沖區的,並且一切字符通吃,或者說一切字符都會讀取,不會忽略。不過這種方案有個缺點,就是要額外定義一個變量 c,對於有強迫症的讀者來說可能有點難受。 

2) 使用 scanf() 清空緩沖區

scanf() 還有一種高級用法,就是使用類似於正則表達式的通配符,這樣它就可以讀取所有的字符了,包括空格、換行符、制表符等空白符,不會再忽略它們了。並且,scanf() 還允許把讀取到的數據直接丟棄,不用賦值給變量。

請看下面的語句:

scanf("%*[^\n]"); scanf("%*c");

第一個 scanf() 將逐個讀取緩沖區中\n之前的其它字符,% 后面的 * 表示將讀取的這些字符丟棄,遇到\n字符時便停止讀取。此時,緩沖區中尚有一個\n遺留,第二個 scanf() 再將這個\n讀取並丟棄,這里的星號和第一個 scanf() 的星號作用相同。由於所有從鍵盤的輸入都是以回車結束的,而回車會產生一個\n字符,所以將\n連同它之前的字符全部讀取並丟棄之后,也就相當於清除了輸入緩沖區。

相信很多讀者都不明白這種寫法,沒關系,下節我們在講解 scanf() 的高級用法時還會再解釋。

我們來演示這種方案的效果:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a = 1, b = 2;
  5. scanf("a=%d", &a);
  6. scanf("%*[^\n]"); scanf("%*c"); //在下次讀取前清空緩沖區
  7. scanf("b=%d", &b);
  8. printf("a=%d, b=%d\n", a, b);
  9. return 0;
  10. }

輸入示例 ①:

a=100↙
b=200↙
a=100, b=200

輸入示例 ②:

a=100b=200↙
b=300↙
a=100, b=300

相比使用 getchar(),這種方案不用額外定義一個變量,看起來更加整潔。

兩種不通用、不建議的方案

以上兩種清空輸入緩沖區的方案是通用的,在任何平台、任何編譯器、任何情景下都奏效。除此以外,有些教材和老師可能還講解過其它的方案,常見的有兩種,分別是fflush(stdin)rewind(stdin)

1) fflush(stdin)

fflush(stdin) 常用於 Windows 平台,在 VC 6.0、VS2010 等較老的編譯器下確實能夠清空緩沖區。

C語言標准規定,當 fflush() 用於 stdout 時,必須要有清空輸出緩沖區的作用;但是C語言標准並沒有規定 fflush() 用於 stdin 時的作用,編譯器的實現者可以自由決定,所以它的行為是未定義的。

較老的微軟編譯器進行了擴展,賦予了 fflush(stdin) 清空輸入緩沖區的功能,例如 VC 6.0、VS2010 等;但是,較新的微軟編譯器又取消了這種擴展,不再支持 fflush(stdin),例如 VS2015、VS2017 等,在這些版本的編譯器下,fflush() 是無效的。

較老的 GCC 是不支持 fflush(stdin) 的,但是最新的 GCC 又開始支持 fflush(stdin) 了。

LLVM/Clang 編譯器始終不支持 fflush(stdin)。

總之,fflush(stdin) 這種不標准的寫法只適用於一部分編譯器,通用性非常差,所以不建議使用。如果你由於個人習慣堅持使用,請測試你的編譯器是否支持。

2) rewind(stdin)

rewind() 函數並沒有清空緩沖區的功能,但是 rewind(stdin) 偏偏在某些編譯器下會導致清空緩沖區的假象,例如 VS2015、LLVM/Clang。在 GCC 下,rewind(stdin) 是沒有任何效果的。

總結

最靠譜、最通用、最有效的清空輸入緩沖區的方案就是使用 getchar() 或者 scanf() 將緩沖區中的數據逐個讀取出來,其它方案都有或多或少的問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM