為了在命令行程式中實現和用戶的交互,我們編寫的程式的運行過程中往往涉及到對標準輸入/輸出流的多次讀寫。 在C語言中接受用戶輸入這一塊,有著一個老生常談的問題:“怎麼樣及時清空輸入流中的數據?” 這也是這篇小筆記的主題內容。 先從緩衝區說起。 緩衝區是記憶體中劃分出來的一部分。通常來說,緩衝區類型有三種 ...
為了在命令行程式中實現和用戶的交互,我們編寫的程式的運行過程中往往涉及到對標準輸入/輸出流的多次讀寫。
在C語言中接受用戶輸入這一塊,有著一個老生常談的問題:“怎麼樣及時清空輸入流中的數據?”
這也是這篇小筆記的主題內容。
先從緩衝區說起。
緩衝區是記憶體中劃分出來的一部分。通常來說,緩衝區類型有三種:
- 全緩衝
- 行緩衝
- 無緩衝
行緩衝
在C語言中緩衝區這個概念的存在感還是挺強的,比較常用到的緩衝區類型則是行緩衝了,如標準輸入流 stdin
和標準輸出流 stdout
一般(終端環境下)就是在行緩衝模式下的。
行緩衝,顧名思義,就是針對該緩衝區的I/O操作是基於行的。
-
在遇到換行符前,程式的輸入和輸出都會先被暫存到流對應的緩衝區中
-
而在遇到換行符後(或者緩衝區滿了),程式才會進行真正的I/O操作,將該緩衝區中的數據寫到對應的流 (stream) 中以供後續讀取。
就標準輸入stdin
而言,用戶的輸入首先會被存到相應的輸入緩衝區中,每當用戶按下回車鍵輸入一個換行符,程式才會進行I/O操作,將緩衝區暫存的數據寫入到stdin
中,以供輸入函數使用。
而對標準輸出stdout
來說,輸出內容也首先會被暫存到相應的輸出緩衝區中,每當輸出數據遇到換行符時,程式才會將緩衝區中的數據寫入stdout
,繼而列印到屏幕上。
這也是為什麼在緩衝模式下,輸出的內容不會立即列印到屏幕上:
#include <stdio.h>
int main()
{
// 設置緩衝模式為行緩衝,緩衝區大小為10位元組
setvbuf(stdout, NULL, _IOLBF, 10);
fprintf(stdout, "1234567"); // 這裡先向stdout對應的緩衝區中寫入了7位元組
getchar(); // 這裡等待用戶輸入
printf("89"); // 再向stdout對應的緩衝區中寫入了2位元組
getchar(); // 接著等待用戶輸入
printf("Print!"); // 再向stdout對應的緩衝區中寫入了6位元組
getchar(); // 最後再等待一次用戶輸入
return 0;
}
運行效果:
可以看到,直到執行到第二個getchar()
時,屏幕上沒有新的輸出。
而在執行了printf("Print!")
之後,輸出緩衝區被填滿了,輸出緩衝區中現有的10
位元組的數據被寫入到stdout
中,繼而才在屏幕上列印出123456789P
。
緩衝區內容被讀走後,剩餘的字元串rint!
接著被寫入輸出緩衝區。程式運行結束後,輸出緩衝區中的內容會被全部列印到屏幕上,所以會在最後看到rint!
。
C語言中常用的輸入函數
輸入函數做的工作主要是從文件流中讀取數據,亦可將讀取到的數據儲存到記憶體中以供後續程式使用。
基於字元
// 從給定的文件流中讀一個字元 (fgetc中的 f 的意思即"function")
int fgetc( FILE *stream );
// 同fgetc,但是getc的實現*可能*是基於巨集的
int getc( FILE *stream );
// 相當於是getc(stdin),從標準輸入流讀取一個字元
int getchar(void);
// 返回獲取的字元的ASCII碼值,如果到達文件末尾就返回EOF(即返回-1)
基於行
// 從給定的文件流中讀取(count-1)個字元或者讀取直到遇到換行符或者EOF
// fgets中的f代表“file”,而s代表“string”
char *fgets( char *restrict str, int count, FILE *restrict stream );
// 返回指向字元串的指針或者空指針NULL
格式化輸入
// 按照format的格式從標準輸入流stdin中讀取所需的數據並儲存在相應的變數中
// scanf中的f代表“format”
int scanf( const char *restrict format, ... );
// 按照format的格式從文件流stream中讀取所需的數據並儲存在相應的變數中
// fscanf中前一個f代表“file(stream)”,後一個f代表“format”
int fscanf( FILE *restrict stream, const char *restrict format, ... );
// 按照format的格式從字元串buffer中截取所需的數據並儲存在相應的變數中
// sscanf中的第一個s代表“string”,字元串
int sscanf( const char *restrict buffer, const char *restrict format, ... );
// 返回一個整型數值,代表成功根據格式賦值的變數數(arguments)
最常到的輸入流問題
先來個不會出問題的示例:
#include <stdio.h>
int main()
{
char test1[200];
char test2[200];
char testChar;
printf("Input a Character: \n");
testChar = getchar();
fprintf(stdout, "Input String1: \n");
scanf("%s", test1);
fprintf(stdout, "Input String2: \n");
scanf("%s", test2);
printf("Got String1: [ %s ]\n", test1);
printf("Got String2: [ %s ]\n", test2);
printf("Got Char: [ %c ]\n", testChar);
return 0;
}
運行效果:
出問題的示例:
#include <stdio.h>
int main()
{
char test[200];
char testChar1, testChar2, testChar3;
fprintf(stdout, "Input String: \n");
scanf("%3s", test);
printf("[1]Input a Character: \n");
testChar1 = getchar();
printf("[2]Input a Character: \n");
testChar2 = fgetc(stdin);
printf("[3]Input a Character: \n");
testChar3 = getchar();
printf("Got String: [ %s ]\n", test);
printf("Got Char1: [ %c ]\n", testChar1);
printf("Got Char2: [ %c ]\n", testChar2);
printf("Got Char3: [ %c ]\n", testChar3);
return 0;
}
運行效果:
因為我將格式設置為了%3s
,所以scanf
最多接收包含三個字元的字元串。
在這個示例中,我按要求輸入了一條字元串Hello
,並按下回車輸入一個換行符,緩衝區數據Hello\n
被寫入到了stdin
中。而scanf
只從標準流stdin
中讀走了Hel
這一部分字元串。
此時,標準流stdin
中實際上還剩3個字元:
l
o
\n
(回車輸入的換行符)
於是接下來三次針對字元的輸入函數只會分別從stdin
中取走這三個字元,而不會等待用戶輸入,這就沒有達到我想要的效果。
在基本的命令行程式中很容易遇到這類問題,這也是為什麼需要及時清空輸入流stdin
中的數據。
如何處理殘餘內容