C語言中文網 目錄

清空輸入緩沖器,C語言清空輸入緩沖區完全攻略

本節,在學習如何清空輸入緩沖區之前,我們先來介紹一下“輸入緩沖區”的概念。

輸入緩沖區

所有從鍵盤輸入的數據,不管是字符還是數字,都是先存儲在內存的緩沖區中,叫作“鍵盤輸入緩沖區”,簡稱“輸入緩沖區”或“輸入流”。我們先來看一個程序:
# include <stdio.h>
int main(void)
{
    int a, b, c;
    scanf("%d", &a);
    printf("a = %d\n", a);
    scanf("%d", &b);
    printf("b = %d\n", b);
    scanf("%d", &c);
    printf("c = %d\n", 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 = %d\n", 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 = %d\n", 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 跳出本層循環體。

那么該怎么辦呢?方法有兩個
  1. 既然不想將字符'\n' 賦給變量 i,那么就先定義一個字符變量 ch,然后用 scanf 將字符 '\n' 取出來給變量 ch,這樣就有機會輸入“Y”或者“N”了;
  2. 直接清空輸入緩沖區。

用scanf吸收回車

# include <stdio.h>
int main(void)
{
    int a;
    char i;
    char ch;
    while (1)
    {
        printf("請輸入一個數字:");
        scanf("%d", &a);
        printf("a = %d\n", 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 = %d\n", 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("恭喜成功獲得密碼%d\n", 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 = %d\n", 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') 那么就會一直取,直到將用戶輸入的垃圾字符全部取完為止。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo