轉載於:https://www.cnblogs.com/yhjoker/p/7530837.html
scanf函數讀取緩衝區數據的問題
標準I\O的緩衝類型
標準I\O根據不同的應用需求,提供了全緩衝、行緩衝、無緩衝三種緩衝方式。
全緩衝:只有當劃定的緩衝區被填滿或者數據讀取至末尾時,纔開始執行I\O操作(執行系統提供的read\write操作)。磁盤文件的讀寫一般採用這種方式。
行緩衝:當輸入輸出過程遇到換行符''\n"或者當分配緩衝區已滿時,纔開始執行I\O操作。一般涉及終端的讀寫操作如stdio與stdout使用這種緩衝方式。
無緩衝:當有數據產生時,馬上由相應的設備進行處理。一般來說stderr(standard error)使用這種緩衝方式,使得有錯誤信息時馬上能夠得到響應。需要注意的是,標準庫不緩存並不意味着操作系統或者設備驅動不緩存。
注意,以上關於stdio/stdout的緩衝方式並不是直接規定死的。一些語言的語言規範會對緩衝實現給出一定的限制,但並不具體,只是許多標準I/O是以上述方式實現的而已。可以參考關於流和緩衝區。
scanf函數
scanf函數: scanf C++ reference
函數聲明:int scanf( format string , arg1 , arg2 , ...);
從函數聲明可以看到,scanf的參數由指示讀取動作的format string和相應的地址參數arg1...argn組成。scanf函數將輸入從標準輸入緩衝區stdin中讀入,並將它們以format string中指定的格式存儲到額外的參數arg1...arg2等指定的內存空間中。其中額外的參數(additional argument)指向的內存空間的數據類型應該與format string中指定的數據類型相一致。
format string
format string字符串規定了如何從輸入緩衝stdin中讀取數據:
(1)空白字符(whitespace)。scanf會讀取並忽略在stdin中下一個非空白字符之前的所有空白字符(空格、換行和tab),然後讀取format string中規定格式的數據。format string中包含的空白字符會與輸入緩衝區中任意數量的連續空白字符相匹配,並將其從緩衝區中清除(包括0個);
(2)非空白字符(non whitespace)。對於format string中既非空白字符又不是格式說明符(format specifier,由%標識)的一部分的字符,scanf從stdin中讀取輸入,並將輸入與該字符比較,若匹配,繼續進行讀取,若不匹配,則函數返回錯誤信息;
(3)格式說明符說明了存儲輸入數據的格式,若當前的字符與格式說明符指定的數據類型不符,scanf會退出並返回;
由以上3條規則可知,format string規定了scanf函數的行爲。下面爲示例:
scanf("%s,%d",&a,&b); //scanf需先讀取一個字符串,再讀取一個 ‘,’(規則2),最後讀取一個整數
scanf("%d\t%d",&a,&b); //scanf需先讀取一個整數,再將format string中的 \t (空白字符)與緩衝區中0個或多個空白字符匹配並清除(規則1),最後讀取一個整數
scanf("%d%d",&a,&b); //scanf需要先讀取一個整數,之後再讀取一個整數,兩個整數之間的空白字符會被忽略(規則1)
行緩衝
標準輸入緩衝區stdin使用行緩衝的方式存儲輸入。亦即在用戶鍵入回車鍵或緩衝區滿後才進行I/O操作,此時輸入的數據均存放在緩衝區中。
scanf會在stdin中讀取數據,且scanf未讀取的數據仍會存放在緩衝區中,之後的scanf操作仍從這裏讀取數據,只有當緩衝區爲空後,scanf纔會等待用戶輸入(實際應該是等待stdin的緩衝)。
字符和字符串的讀取
對於stdin中的字符的讀取,scanf會讀取緩衝區中的第一個字符,包括空白字符和非空白字符。
對於stdin中的字符串的讀取,scanf會在開始處理後(即跳過第一個非空白字符之前的空白字符,規則1)在讀取到的第一個空白字符處退出,並在讀取的字符串尾部加入'\0'作爲結束標誌。
緩衝區讀取數據問題
例1:
程序先輸出變量未初始化之前的值,再使用scanf讀取輸入,再顯示讀取輸入之後的值
printf("%d,%d,%c\n",a,b,c); //輸出未初始化之前的值
scanf("%d%d",&a,&b);
scanf("%c",&c);
printf("%d,%d,%c\n",a,b,c); //輸出初始化之後的值
結果如下圖所示
解釋如下:
(1)用戶輸入只緩衝區中的數據實際爲 12 + 空格 + a + 換行符 ;
(2)第一次讀取輸入時,首先將讀取到的第一個數字12賦值給變量a,之後scanf會試圖讀取下一個十進制數,但是發現下一個非空白字符(忽略輸入的空格)爲字符a,與其所需要讀取的數據類型不符,scanf會退出並返回一個常數值來表示錯誤信息.此時字符a並未被讀取,仍然存在於緩衝區中;
(3)第二次讀取輸入時,scanf就會發現緩衝區中第一個非空白字符爲字符a,從而會將字符a賦值給變量c,並退出。
故而,再次輸出變量時,變量a和c均已改變,而變量b只能保持原值。
例2:
用於測試的函數先讀取一個字符串,再讀取一個字符,並將結果輸出
scanf("%s",a); scanf("%c",&b); printf("%s,%d",a,(int)b);
輸出結果如下
解釋如下:
(1)用戶在輸入時,實際進入緩衝區中的數據爲字符串'"abcd" + 換行符;
(2)第一次讀取時,scanf會讀取一個字符串,並在遇到第一個空白字符處停止,這裏爲換行符,即讀取的字符串爲"abcd",scanf函數還會在該字符串尾部加入'\0'進行存儲;
(3)第二次讀取時,scanf會讀取一個字符,前面已經說過包括空白字符,故而scanf會讀取換行符,而換行符的ASCII值即爲10;
但是將讀取輸入的要求換一下,要求讀取兩個字符串
結果scanf會再次等待用戶輸入
原因在於在讀取第一個字符串後,緩衝區中剩餘一個換行符,而根據規則1,在讀取字符串之前會跳過所有的空白字符,之後scanf會發現此時緩衝區已經爲空,從而需要再次等待用戶輸入。
事實上,對於上述情況,除非第二次讀取的參數是可以讀取空白字符的%c,其他的參數均會使得scanf認爲緩衝區已爲空,從而進入等待用戶輸入的狀態。
getchar
getchar()是用於字符輸入的C庫函數,其函數的聲明包含在頭文件stdio.h,函數聲明爲: int getchar(void).其功能是讀取標準輸入stdin中的一個字符。
getchar從標準輸入中讀取數據,而stdin是採用行緩衝的方式記錄用戶輸入,也就是隻有當用戶鍵入回車鍵或輸入至緩衝區末尾時,纔會開始I\O操作,亦即讀取一個字符。這樣用戶可以一次輸入不止一個字符,讀取過後緩衝區可能不爲空。當再次調用getchar()時,若緩衝區不爲空,getchar()就會直接讀取在緩衝區中字符,而不是等待用戶輸入。可以認爲是getchar()等待的是行緩衝的完成,而不是用戶輸入的完成,在行緩衝完成後,只要緩衝區不爲空,getchar()就可以讀取字符,而不需要等待用戶輸入。
/*codeblocks13.12*/ #include <stdio.h> int main(void) { char ch = '\0'; while(ch != '\n') { printf("輸入一個字符:"); ch = getchar(); printf("\n"); putchar(ch); printf("\n"); } return 0; }
程序的運行結果如下:
可以明顯看到,後續執行中並不要求用戶輸入,getchar()會直接讀取緩衝區中的數據。而且對於字符的讀取操作而言,換行符'\n'也被視爲一個字符,而不是單純的結束標誌。
等待用戶輸入的字符輸入
getchar()可以直接從緩衝區中讀取字符,而不等待用戶輸入,但這種方式也有可能帶來潛在的錯誤。這裏給出兩種等待用戶輸入的字符傳入方式。
1.使用getche()與getch()函數。都從鍵盤上讀入一個字節,其中後者不將字符回顯到屏幕上。以這兩個函數讀取字符時,都是通過調用函數讀取一個鍵盤輸入且只有一個。如調用getche(),鍵盤敲擊'abc'時,只有一個字符'a'會被讀取。其他字符爲無效輸入。
2.在每次getchar()之後,手動對緩衝區進行清除操作。可以使用fflush()函數清理緩衝區。C標準規定 fflush()函數可用來刷新輸出(stdout)緩衝區(一般是將緩衝區數據寫回存儲設備)。但對於標準輸入(stdin)則沒有明確定義。部分編譯器定義了 fflush( stdin )的實現,如微軟的VC。也就是不同的編譯器對於 fflush( stdin )的支持可能不同。GCC編譯器沒有定義它的實現,所以不能使用 fflush( stdin )來刷新輸入緩衝區。