天天看點

C語言清空輸入緩沖區的N種方法對比

原文位址為: C語言清空輸入緩沖區的N種方法對比

C語言中有幾個基本輸入函數:

//擷取字元系列
      
int fgetc(FILE *stream);      
int getc(FILE *stream);      
int getchar(void);      
//擷取行系列      
char *fgets(char * restrict s, int n, FILE * restrict stream);      
char *gets(char *s);//可能導緻溢出,用fgets代替之。      
//格式化輸入系列      
int fscanf(FILE * restrict stream, const char * restrict format, …);      
int scanf(const char * restrict format, …);      
int sscanf(const char * restrict str, const char * restrict format, …);      
這裡僅讨論輸入函數在标準輸入(stdin)情況下的使用。縱觀上述各輸入函數,      
  • 擷取字元系列的的前三個函數fgetc、getc、getchar。以getchar為例,将在stdin緩沖區為空時,等待輸入,直到回車換行時函數傳回。若stdin緩沖區不為空,getchar直接傳回。getchar傳回時從緩沖區中取出一個字元,并将其轉換為int,傳回此int值。

MINGW 4.4.3中FILE結構體源碼:

typedef struct _iobuf
      
{      
char*	_ptr;//指向目前緩沖區讀取位置      
int	_cnt;//緩沖區中剩餘資料長度      
char*	_base;      
int	_flag;      
int	_file;      
int	_charbuf;      
int	_bufsiz;      
char*	_tmpfname;      
} FILE;      
各編譯器實作可能不一樣,這裡擷取字元系列函數隻用到_ptr和_cnt。      

MINGW 4.4.3中getchar()實作:

__CRT_INLINE int __cdecl __MINGW_NOTHROW getchar (void)      
{      
return (--stdin->_cnt >= 0)      
?  (int) (unsigned char) *stdin->_ptr++      
: _filbuf (stdin);      
}      

其中stdin為FILE指針類型,在MINGW 4.4.3中,getc()和getchar()實作為内聯函數,fgetc()實作為函數。順便說一句,C99标準中已經加入對内聯函數的支援了。

  • 擷取行系列的fgets和gets,其中由于gets無法确定緩沖區大小,常導緻溢出情況,這裡不推薦也不讨論gets函數。對于fgets函數,每次敲入回車,fgets即傳回。fgets成功傳回時,将輸入緩沖區中的資料連換行符’\n’一起拷貝到第一個參數所指向的空間中。若輸入資料超過緩沖區長度,fgets會截取資料到前n-1(n為fgets第二個參數,為第一個參數指向空間的長度),然後在末尾加入’\n’。是以fgets是安全的。通常用fgets(buf, BUF_LEN, stdin);代替gets(buf);。
  • 格式化輸入系列中,fscanf從檔案流進行格式化輸入很不好用。常用的還是scanf,格式化輸入系列函數舍去輸入資料(根據函數不同可能是标準輸入也可能是字元串輸入,如:sscanf)前的空白字元(空格、制表符、換行符)直至遇到非空白字元,然後根據格式參數嘗試對非空白字元及後續字元進行解析。該系列函數傳回成功解析指派的變量數,若遇檔案尾或錯誤,傳回EOF。

=================分 割 線=================

提到緩沖區,就不得不提setbuf和setvbuf兩個緩沖區設定函數,其聲明如下:

void setbuf(FILE * restrict stream, char * restrict buf);
      
int setvbuf(FILE * restrict stream, char * restrict buf, int mode, size_t size);      

setvbuf的mode參數有:

  • _IOFBF(滿緩沖):緩沖區空時讀入資料;緩沖區滿時向流寫入資料。
  • _IOLBF(行緩沖):每次從流讀入一行資料或向流寫入資料。如:stdio,stdout
  • _IONBF(無緩沖):直接從流讀入資料,或者直接向流寫入資料,而沒有緩沖區。如: stderr

setbuf(stream, buf);在:

  • buf == NULL:等價于(void)setvbuf(stream, NULL, _IONBF, 0);
  • buf指向長度為BUFSIZ的緩沖區:等價于(void)setvbuf(stream, buf, _IOFBF, BUFSIZ);

注:BUFSIZ宏在stdio.h中定義。

這裡還要提一下傳說中的setbuf的經典錯誤,在《C陷阱和缺陷》上有提到:

int main()
      
{      
int c;      
char buf[BUFSIZ];      
setbuf(stdout,buf);      
while((c = getchar()) != EOF)      
putchar(c);      
return 0;      
}      

問題是這樣的:程式交回控制給作業系統之前C運作庫必須進行清理工作,其中一部分是重新整理輸出緩沖,但是此時main函數已經運作完畢,buf緩沖區作用域在main函數中,此時buf字元數組已經釋放,導緻輸出詭異亂碼。

解決方案:可以将buf設定為static,或者全局變量,或者調用malloc來動态申請記憶體。

=================分 割 線=================

下面來看看幾種流行的緩沖區清空方法:

  • fflush(stdin);式

由C99标準文檔中:

If stream points to an output stream or an update stream in which the most recent
      
operation was not input, the fflush function causes any unwritten data for that stream      
to be delivered to the host environment to be written to the file; otherwise, the behavior is      
undefined.      

可以看出fflush對輸入流為參數的行為并未定義。但由MSDN上的fflush定義:

If the file associated with stream is open for output, fflush writes to that file the 
      
contents of the buffer associated with the stream. If the stream is open for input,       
fflush clears the contents of the buffer.       

可以看出fflush(stdin)在VC上還是有效地!鑒于各編譯器對fflush的未定義行為實作不一樣,不推薦使用fflush(stdin)重新整理輸入緩沖區。

  • setbuf(stdin, NULL);式

由前面對setbuf函數的介紹,可以得知,setbuf(stdin, NULL);是使stdin輸入流由預設緩沖區轉為無緩沖區。都沒有緩沖區了,當然緩沖區資料殘留問題會解決。但這并不是我們想要的。

  • scanf("%*[^\n]");式(《C語言程式設計 現代方法 第二版》中提到)

這裡用到了scanf格式化符中的“*”,即指派屏蔽;“%[^集合]”,比對不在集合中的任意字元序列。這也帶來個問題,緩沖區中的換行符’\n’會留下來,需要額外操作來單獨丢棄換行符。

  • 經典式
int c;
      
while((c = getchar()) != '\n' && c != EOF);      

由代碼知,不停地使用getchar()擷取緩沖區中字元,直到 擷取的字元c是換行符’\n’或者是檔案結尾符EOF為止。這個方法可以完美清除輸入緩沖區,并且具備可移植性。

轉載請注明本文位址: C語言清空輸入緩沖區的N種方法對比

繼續閱讀