本節,在學習如何清空輸入緩衝區之前,我們先來介紹一下“輸入緩衝區”的概念。
輸入緩衝區
所有從鍵盤輸入的資料,不管是字元還是數位,都是先儲存在記憶體的緩衝區中,叫作
“鍵盤輸入緩衝區”,簡稱“
輸入緩衝區”或“
輸入流”。我們先來看一個程式:
# include <stdio.h>
int main(void)
{
int a, b, c;
scanf("%d", &a);
printf("a = %dn", a);
scanf("%d", &b);
printf("b = %dn", b);
scanf("%d", &c);
printf("c = %dn", c);
return 0;
}
輸出結果是:
1
a = 1
2
b = 2
3
c = 3
或者:
1 2 3
a = 1
b = 2
c = 3
從輸出結果可以看出,不管是一個一個地輸入:1(回車)2(回車)3(回車);還是三個數位一次性輸入:1(空格)2(空格)3(回車),這兩種輸入方法的結果都是一樣的。原因是從鍵盤輸入的資料都會被依次存入緩衝區,不管是數位還是字元都會被當成資料存進去。但只有按回車,scanf 才會進去取資料,所取資料的個數取決於 scanf 中“輸入引數”的個數。因此不在於怎麼輸入,可以存一個取一個,也可以一次性全存入進去,然後一個個取。
那麼一次性全存進去,scanf 是如何取資料的呢?這就好比開閘放水,我們將水一次性全部放到閘裡以後,開一次閘就放掉一點,開一次閘就放掉一點,直到放光了為止。開閘的動作就相當於呼叫一次 scanf,開閘的開關就是回車;每開一次閘放掉多少水,取決於 scanf 中“輸入引數”的個數。
所以,輸入的資料放在輸入緩衝區中,先輸入的排在最前面,後輸入的依次往後排。如果 scanf 中“輸入引數”的個數只有一個,那麼我們呼叫一次 scanf 就把緩衝區中離出口最近的一個資料輸出給 scanf,也就是把排在最前面的一個資料輸出給 scanf。輸出後,緩衝區中就沒有這個資料了。
如果 scanf 中“輸入引數”的個數為 n,那麼就從排在最前面的開始,依次往後取 n 個資料輸出給 scanf。沒取完的仍舊放在緩衝區中,直到取用完畢為止。如果緩衝區中的資料全被取完了,但還有 scanf 要取資料,那就要再從鍵盤輸入資料。
%d和%c讀取緩衝區的差別
需要注意的是,對於 %d,在緩衝區中,空格、迴車、Tab 鍵都只是分隔符,不會被 scanf 當成資料取用。%d 遇到它們就跳過,取下一個資料。但是如果是 %c,那麼空格、迴車、Tab 鍵都會被當成資料輸出給 scanf 取用,例如下面這個程式:
# include <stdio.h>
int main(void)
{
int a, c;
char b;
scanf("%d%c%d", &a, &b, &c);
printf("a = %d, b = %c, c = %dn", a, b, c);
return 0;
}
輸出結果是:
1 5 6
a = 1, b = , c = 5
在此程式中,原本希望的是將數位 1 賦給變數 a,將字元 '5' 賦給變數 b,將數位 6 賦給變數 c。但從輸出結果可以看出,按一下回車,scanf 開始到緩衝區中取資料,因為“輸入引數”有三個,所以 scanf 從緩衝區中取三個資料。數位 1 賦給變數 a,而因為變數 b 是 %c,所以前三種情況分別將空格、回車和 Tab 鍵賦給變數 b,然後數位 5 賦給變數 c,而數位 6 仍然在緩衝區中,等待下一個 scanf 來取。這樣的話就會有一個問題,我們看下面這個程式:
# include <stdio.h>
int main(void)
{
int a;
char i;
while (1)
{
printf("請輸入一個數位:");
scanf("%d", &a);
printf("a = %dn", a);
printf("您想繼續嗎(Y/N):");
scanf("%c", &i);
if (('Y' == i) || ('y' == i))
{
;
}
else
{
break; // 跳出本層迴圈體
}
}
return 0;
}
輸出結果是:
請輸入一個數位:10
a = 10您想繼續嗎(Y/N):
當我們輸入“10”之後希望系統問:“您想繼續嗎(Y/N):”,若為“Y”就再重新輸入一個值,然後輸出,否則就跳出本迴圈體。但是執行的時候我們發現,剛按完“10”然後回車,直接就結束了,都不給我們輸入“Y”和“N”的機會,這是為什麼?
因為輸入“10”然後回車,“10”賦給了a,但是回車遺留在了緩衝區,所以等下面又遇到“scanf("%c",&i);”的時候就直接把字元 'n' 賦給變數 i 了(注意,按回車不是把回車符 'r' 存到緩衝區,而是把換行符 'n' 存進去了,因為按回車確實就是換行)。字元 'n' 明顯不等於字元 'Y',所以直接 break 跳出本層迴圈體。
那麼該怎麼辦呢?方法有兩個
-
既然不想將字元'n' 賦給變數 i,那麼就先定義一個字元變數 ch,然後用 scanf 將字元 'n' 取出來給變數 ch,這樣就有機會輸入“Y”或者“N”了;
-
直接清空輸入緩衝區。
用scanf吸收回車
# include <stdio.h>
int main(void)
{
int a;
char i;
char ch;
while (1)
{
printf("請輸入一個數位:");
scanf("%d", &a);
printf("a = %dn", a);
printf("您想繼續嗎(Y/N):");
scanf("%c", &ch); //用scanf吸收回車
scanf("%c", &i);
if (('Y' == i) || ('y' == i))
{
;
}
else
{
break; // 跳出本層迴圈體
}
}
return 0;
}
輸出結果是:
請輸入一個數位:10
a = 10您想繼續嗎, Y想, N不想:Y
請輸入一個數位:5
a = 5您想繼續嗎, Y想, N不想:Y
請輸入一個數位:333
a = 333您想繼續嗎, Y想, N不想:N
這時有人說,如果緩衝區前面排了三個字元'x20'(空格),我都不需要,想先把它們取出來,那是不是要先定義三個變數呢?當然不是!儲存不需要的垃圾字元只需要一個變數即可,因為它們都是垃圾,所以直接覆蓋就行了。取一個後,再取一個就把第一個覆蓋,再取一個就再覆蓋。
但是在實際程式設計中,一般不會用 scanf 吸收回車,也不會用 scanf 給一個字元變數賦值,因為有更簡單的方法,就是用 getchar()。getchar() 是專門從緩衝區讀取一個字元的函數。它是“吸收回車專業戶”,簡單、方便、好用。
getchar()
該函數的原型為:
# include <stdio.h>
int getchar(void);
功能是從緩衝區中讀取一個字元。這個函數非常簡單,連引數都沒有,非常好用。下面用 scanf 給字元變數賦值和吸收回車的程式用 getchar() 修改一下:
# include <stdio.h>
int main(void)
{
int a;
char ch;
while (1)
{
printf("請輸入一個數位:");
scanf("%d", &a);
printf("a = %dn", a);
printf("您想繼續嗎(Y/N):");
getchar(); /*用getchar吸收回車, 簡單、方便、好用, 都不需要定義變數用來儲存獲取的回車符*/
ch = getchar(); //用getchar從緩衝區中讀取一個字元賦給字元變數ch
if (('Y' == ch) || ('y' == ch))
{
;
}
else
{
break; // 跳出本層迴圈體
}
}
return 0;
}
輸出結果是:
請輸入一個數位:10
a = 10您想繼續嗎(Y/N):y
請輸入一個數位:5
a = 5您想繼續嗎(Y/N):y
請輸入一個數位:333
a = 333您想繼續嗎(Y/N):n
在程式中,“ch=getchar();”這句之前我們先用 getchar() 清空緩衝區,然後重新從鍵盤輸入一個字元。同樣,必須按回車 getchar() 才會進去取這個字元。這時候需要注意的是,同 scanf 一樣,按的這個迴車也會被遺留在緩衝區中,大家要注意。
這時有人會說,如果前面有多個 scanf 給 int 型變數賦值,那麼每個 scanf 都會遺留一個迴車,那這時是不是有幾個 scanf 就要用幾個 getchar() 呢?
回答是“不需要”,仍然只需要一個 getchar()!前面說過,當 scanf 用 %d 取緩衝區資料的時候,如果遇到空格、回車或 Tab 鍵就跳過去。不僅如此,這些被跳過去的空白符都被釋放了。所以假如前面有三個 scanf 給 int 型變數賦值,那麼第一個 scanf 輸入回車後把回車遺留在了緩衝區,而第二個 scanf 取值時會越過第一個 scanf 遺留在緩衝區中的迴車,那麼這個迴車就會從緩衝區中釋放。但第二個 scanf 取完值後也在緩衝區中留下了一個迴車,而當第三個 scanf 到緩衝區中取值時會跳過第二個 scanf 遺留的回車,這個迴車同樣也會從緩衝區中釋放,所以歸根結底最後緩衝區中只有一個迴車,也就是說,緩衝區中永遠不可能遺留多個迴車。
下面來寫一個程式驗證一下:
# include <stdio.h>
int main(void)
{
int a, b, c, d;
char ch;
printf("請輸入第一個數:");
scanf("%d", &a);
printf("請輸入第二個數:");
scanf("%d", &b);
printf("請輸入第三個數:");
scanf("%d", &c);
printf("您想繼續嗎(Y/N):");
getchar(); //只需要用一個getchar吸收回車
ch = getchar(); //用getchar獲取一個字元賦給ch
if (('Y' == ch) || ('y' == ch))
{
printf("請輸入密碼:");
scanf("%d", &d);
printf("恭喜成功獲得密碼%dn", d);
}
return 0;
}
輸出結果是:
請輸入第一個數:1
請輸入第二個數:2
請輸入第三個數:3
您想繼續嗎(Y/N):y
請輸入密碼:5678
恭喜成功獲得密碼5678
由此我們知道,當用 %d 獲取輸入流中的資料的時候,如果遇到字元(空格、迴車、Tab 除外),則直接從輸入流中退出來,什麼都不取。但如果是用 %c 獲取,那麼任何資料都會被當作一個字元。所以如果你要從輸入流中取一個字元,但在之前使用過 scanf,那麼此時就必須要先用 getchar() 吸收回車。否則取到的將不是你想要的字元,而是 scanf 遺留在輸入流中的迴車。如果你要從輸入流中取的不是字元,那就不需要用 getchar() 吸收回車了。
以上詳細分析了什麼時候需要吸收回車,什麼時候不需要。但實際程式設計中,程式往往很長,我們很難預測到下一次到緩衝區中取資料的是 %d 還是 %c 或者是 gets()、fgets()。所以為了避免忘記吸收回車或耗費精力考慮回車的問題,習慣上 scanf 後面都加上 getchar()。
fflush(stdin)
前面介紹了使用 getchar() 吸收回車的方法,除此之外還有一個更強大、更直接的方法,就是直接將輸入緩衝區全部清空。
清空緩衝區只需加一句 fflush(stdin) 即可。fflush 是包含在檔案 stdio.h 中的函數。stdin 是“標準輸入”的意思。std 即 standard(標準),in 即 input(輸入),合起來就是標准輸入。
fflush(stdin) 的功能是:清空輸入緩衝區。下面將前面的一個程式修改一下:
# include <stdio.h>
int main(void)
{
int a;
char i;
while (1)
{
printf("請輸入一個數位:");
scanf("%d", &a); //因為讀取的是數位, 所以不需要清空緩衝區
printf("a = %dn", a);
printf("您想繼續嗎, Y想, N不想:");
fflush(stdin);
scanf("%c", &i);
if ('Y'==i || 'y'==i)
{
;
}
else
{
break; // 跳出本層迴圈體
}
}
return 0;
}
輸出結果是:
請輸入一個數位:10safa
a = 10
您想繼續嗎, Y想, N不想:Y
請輸入一個數位:58jlkj*&**^
a = 58
您想繼續嗎, Y想, N不想:N
fflush 一般用於清除使用者前面遺留的垃圾資料,提高程式碼的健壯性。因為如果是自己程式設計的話,一般都會按要求輸入。但對於使用者而言,難免會有一些誤操作,多輸入了一些其他沒有用的字元,如果程式中不對此進行處理的話可能會導致程式癱瘓。所以程式設計時一定要考慮到各種情況,提高程式碼的健壯性和容錯性。使用 fflush 就可以將使用者輸入的垃圾資料全部清除。
但是 fflush 有一個問題,就是可移植性。並不是所有的編譯器都支援 fflush,比如 gcc 就不支援。那麼此時怎麼辦?還是用 getchar()。
getchar()的高階用法
while (getchar() != 'n');
這種用法其實在前面也使用過,它可以完全代替 fflush(stdion) 來清空緩衝區。不管使用者輸入多少個沒用的字元,他最後都得按回車,而且只能按一次。只要他按回車那麼回車之前的字元就都會被 getchar() 取出來。只要 getchar() 取出來的不是迴車 ('n') 那麼就會一直取,直到將使用者輸入的垃圾字元全部取完為止。