最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

簡(jiǎn)記C語(yǔ)言清空輸入殘留內(nèi)容

2023-03-14 15:16 作者:程序員-王堅(jiān)  | 我要投稿

先從緩沖區(qū)說(shuō)起。

緩沖區(qū)是內(nèi)存中劃分出來(lái)的一部分。通常來(lái)說(shuō),緩沖區(qū)類型有三種:

  • 全緩沖

  • 行緩沖

  • 無(wú)緩沖

行緩沖#

在C語(yǔ)言中緩沖區(qū)這個(gè)概念的存在感還是挺強(qiáng)的,比較常用到的緩沖區(qū)類型則是行緩沖了,如標(biāo)準(zhǔn)輸入流?stdin?和標(biāo)準(zhǔn)輸出流?stdout一般(終端環(huán)境下)就是在行緩沖模式下的。

行緩沖,顧名思義,就是針對(duì)該緩沖區(qū)的I/O操作是基于行的。

  • 在遇到換行符前,程序的輸入和輸出都會(huì)先被暫存到流對(duì)應(yīng)的緩沖區(qū)中

  • 而在遇到換行符后(或者緩沖區(qū)滿了),程序才會(huì)進(jìn)行真正的I/O操作,將該緩沖區(qū)中的數(shù)據(jù)寫到對(duì)應(yīng)的流?(stream) 中以供后續(xù)讀取。

就標(biāo)準(zhǔn)輸入stdin而言,用戶的輸入首先會(huì)被存到相應(yīng)的輸入緩沖區(qū)中,每當(dāng)用戶按下回車鍵輸入一個(gè)換行符,程序才會(huì)進(jìn)行I/O操作,將緩沖區(qū)暫存的數(shù)據(jù)寫入到stdin中,以供輸入函數(shù)使用。

而對(duì)標(biāo)準(zhǔn)輸出stdout來(lái)說(shuō),輸出內(nèi)容也首先會(huì)被暫存到相應(yīng)的輸出緩沖區(qū)中,每當(dāng)輸出數(shù)據(jù)遇到換行符時(shí),程序才會(huì)將緩沖區(qū)中的數(shù)據(jù)寫入stdout,繼而打印到屏幕上。

這也是為什么在緩沖模式下,輸出的內(nèi)容不會(huì)立即打印到屏幕上:

#include <stdio.h>int main(){ // 設(shè)置緩沖模式為行緩沖,緩沖區(qū)大小為10字節(jié) setvbuf(stdout, NULL, _IOLBF, 10); fprintf(stdout, "1234567"); // 這里先向stdout對(duì)應(yīng)的緩沖區(qū)中寫入了7字節(jié) getchar(); // 這里等待用戶輸入 printf("89"); // 再向stdout對(duì)應(yīng)的緩沖區(qū)中寫入了2字節(jié) getchar(); // 接著等待用戶輸入 printf("Print!"); // 再向stdout對(duì)應(yīng)的緩沖區(qū)中寫入了6字節(jié) getchar(); // 最后再等待一次用戶輸入 return 0;}

運(yùn)行效果:

可以看到,直到執(zhí)行到第二個(gè)getchar()時(shí),屏幕上沒(méi)有新的輸出。

而在執(zhí)行了printf("Print!")之后,輸出緩沖區(qū)被填滿了,輸出緩沖區(qū)中現(xiàn)有的10字節(jié)的數(shù)據(jù)被寫入到stdout中,繼而才在屏幕上打印出123456789P

緩沖區(qū)內(nèi)容被讀走后,剩余的字符串rint!接著被寫入輸出緩沖區(qū)。程序運(yùn)行結(jié)束后,輸出緩沖區(qū)中的內(nèi)容會(huì)被全部打印到屏幕上,所以會(huì)在最后看到rint!。

C語(yǔ)言中常用的輸入函數(shù)#

輸入函數(shù)做的工作主要是從文件流中讀取數(shù)據(jù),亦可將讀取到的數(shù)據(jù)儲(chǔ)存到內(nèi)存中以供后續(xù)程序使用。

基于字符#

// 從給定的文件流中讀一個(gè)字符 (fgetc中的 f 的意思即"function")int fgetc( FILE *stream ); // 同fgetc,但是getc的實(shí)現(xiàn)*可能*是基于宏的int getc( FILE *stream ); // 相當(dāng)于是getc(stdin),從標(biāo)準(zhǔn)輸入流讀取一個(gè)字符int getchar(void);// 返回獲取的字符的ASCII碼值,如果到達(dá)文件末尾就返回EOF(即返回-1)

基于行#

// 從給定的文件流中讀取(count-1)個(gè)字符或者讀取直到遇到換行符或者EOF// fgets中的f代表“file”,而s代表“string”char *fgets( char *restrict str, int count, FILE *restrict stream );// 返回指向字符串的指針或者空指針NULL

格式化輸入#

// 按照f(shuō)ormat的格式從標(biāo)準(zhǔn)輸入流stdin中讀取所需的數(shù)據(jù)并儲(chǔ)存在相應(yīng)的變量中// scanf中的f代表“format”int scanf( const char *restrict format, ... );// 按照f(shuō)ormat的格式從文件流stream中讀取所需的數(shù)據(jù)并儲(chǔ)存在相應(yīng)的變量中// fscanf中前一個(gè)f代表“file(stream)”,后一個(gè)f代表“format”int fscanf( FILE *restrict stream, const char *restrict format, ... );// 按照f(shuō)ormat的格式從字符串buffer中截取所需的數(shù)據(jù)并儲(chǔ)存在相應(yīng)的變量中// sscanf中的第一個(gè)s代表“string”,字符串int sscanf( const char *restrict buffer, const char *restrict format, ... );// 返回一個(gè)整型數(shù)值,代表成功根據(jù)格式賦值的變量數(shù)(arguments)

最常到的輸入流問(wèn)題#

先來(lái)個(gè)不會(huì)出問(wèn)題的示例:

#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;}

運(yùn)行效果:

出問(wèn)題的示例:

#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;}

運(yùn)行效果:

因?yàn)槲覍⒏袷皆O(shè)置為了%3s,所以scanf最多接收包含三個(gè)字符的字符串。

在這個(gè)示例中,我按要求輸入了一條字符串Hello,并按下回車輸入一個(gè)換行符,緩沖區(qū)數(shù)據(jù)Hello\n被寫入到了stdin中。而scanf只從標(biāo)準(zhǔn)流stdin中讀走了Hel這一部分字符串。

此時(shí),標(biāo)準(zhǔn)流stdin中實(shí)際上還剩3個(gè)字符:

  1. l

  2. o

  3. \n?(回車輸入的換行符)

于是接下來(lái)三次針對(duì)字符的輸入函數(shù)只會(huì)分別從stdin中取走這三個(gè)字符,而不會(huì)等待用戶輸入,這就沒(méi)有達(dá)到我想要的效果。

在基本的命令行程序中很容易遇到這類問(wèn)題,這也是為什么需要及時(shí)清空輸入流stdin中的數(shù)據(jù)。

如何處理殘余內(nèi)容#

?? 以下內(nèi)容假設(shè)stdoutstdin兩個(gè)標(biāo)準(zhǔn)流都是在行緩沖模式下的。

標(biāo)準(zhǔn)輸出流stdout#

雖然本文主要是寫輸入流,但這里我還是掠過(guò)一下標(biāo)準(zhǔn)輸出流stdout。C語(yǔ)言標(biāo)準(zhǔn)庫(kù)中提供了一個(gè)用于刷新輸出流緩沖區(qū)的函數(shù):

int fflush( FILE *stream );// 如果成功了,返回0,否則返回EOF(-1)

要清空標(biāo)準(zhǔn)輸出流對(duì)應(yīng)的緩沖區(qū),只需要使用fflush(stdout)即可。上面的這個(gè)例子可以修改成這樣:

#include <stdio.h>int main(){ // 設(shè)置緩沖模式為行緩沖,緩沖區(qū)大小為10字節(jié) setvbuf(stdout, NULL, _IOLBF, 10); fprintf(stdout, "1234567"); // 這里先向stdout對(duì)應(yīng)的緩沖區(qū)中寫入了7字節(jié) fflush(stdout); // 刷新緩沖區(qū),將緩沖區(qū)中的數(shù)據(jù)寫入到標(biāo)準(zhǔn)輸出流中 getchar(); // 這里等待用戶輸入 printf("89"); // 再向stdout對(duì)應(yīng)的緩沖區(qū)中寫入了2字節(jié) fflush(stdout); getchar(); // 接著等待用戶輸入 printf("Print!"); // 再向stdout對(duì)應(yīng)的緩沖區(qū)中寫入了6字節(jié) getchar(); // 最后再等待一次用戶輸入 return 0;}

運(yùn)行效果:

可以看到,加入fflush(stdout)后,輸出緩沖區(qū)的內(nèi)容會(huì)被及時(shí)寫入stdout中,繼而打印到屏幕上。

值得注意的是,fflush(stdin)的行為是未定義(不確定)的:

For input streams (and for update streams on which the last operation was input), the behavior is undefined.

不同平臺(tái)的編譯器對(duì)此有不同的解釋。

  • 比如在Windows平臺(tái)上,無(wú)論是VC6.0這種目前一些學(xué)校教學(xué)還在使用的古董編譯器,還是gcc 8.x.x,大體還是支持通過(guò)這種操作清空輸入流的。

  • 但是在Linux平臺(tái)上的gcc編譯器就不買賬了,是不支持fflush(stdin)這種操作的。

因此,盡量避免fflush(stdin)這種寫法,這十分不利于代碼的可移植性。

標(biāo)準(zhǔn)輸入流stdin#

上面提到因?yàn)榭梢浦残砸苊?code>fflush(stdin)這種寫法,接下來(lái)記錄一下可移植性高的寫法。

接受格式化輸入時(shí)去除多余空白符#

這一種其實(shí)用的比較少,但我覺(jué)得還是得記一下。

whitespace characters: any single whitespace character in the format string consumes all available consecutive whitespace characters from the input. Note that there is no difference between "\n", " ", "\t\t", or other whitespace in the format string.

上面這段解釋來(lái)自于cppreference,也就是說(shuō),格式化字符串中的空白符(如"\n",?" ",?"\t\t")會(huì)吸收輸入字符串中的一段連續(xù)的空白符。

也就是說(shuō),下面這句格式化輸入函數(shù):

scanf(" %c %c",&recvChar1,&recvChar2);

可以從stdin中讀取形如\n a b,\t a b這樣的數(shù)據(jù)。其中a之前的空白符和ab之間的空白符都會(huì)被吸收,scanf得以能準(zhǔn)確獲取字符ab。

依靠這個(gè)特性,我們可以在接收輸入時(shí)自動(dòng)剔除stdin中殘留的空白符:

// 因?yàn)楦袷?s不會(huì)匹配多余的空白符,這里按回車后,stdin中會(huì)殘留一個(gè)換行符\nscanf("%s",recvStr);// 在格式%c前加一個(gè)空格,可以吸收掉上面殘留的換行符\n,程序便能如預(yù)期接受用戶輸入scanf(" %c",&recvChar);

然而,這一種方法僅只能剔除多余的空白符。

使用中括號(hào)字符集#

這個(gè)解決方法可以和上面剔除空白符的方法進(jìn)行結(jié)合。

格式化輸入有一個(gè)說(shuō)明符?%[set],它的功能和正則表達(dá)式中的中括號(hào)[ ]十分類似:

  • 其中set代表一個(gè)用于匹配的字符集,一般情況下匹配的是存在字符集中的字符

  • 字符集的第一個(gè)字符如果是^,則表示取反,匹配的是不存在于該字符集中的字符

  • 可以在中括號(hào)中使用短橫線?-?來(lái)表達(dá)一個(gè)范圍,比如%[0-9]代表匹配0-9之間的字符。值得注意的是,對(duì)于短橫線-,可能在不同編譯器之間有不同實(shí)現(xiàn),它是implementation-defined的。

另還有一個(gè)說(shuō)明符?*?,它被稱為賦值抑制或賦值屏蔽符。如字面意思,在%引導(dǎo)的格式轉(zhuǎn)換字串中如果包含*,這個(gè)格式匹配的內(nèi)容不會(huì)被賦給任何變量。

于是,可以給出如下的語(yǔ)句:

// 星號(hào) * 代表不會(huì)把匹配到的內(nèi)容賦給變量,相當(dāng)于“吸收”掉了// [^\n] 代表除了換行符外一律匹配scanf("%*[^\n]");

因?yàn)橛脩艚Y(jié)束一次輸入的標(biāo)志通常是按回車輸入一個(gè)換行符,殘留的內(nèi)容往往末尾是一個(gè)換行符。上面這句的原理就是吸收掉stdin中所有的殘余字符,直至達(dá)到最后一個(gè)字符,也就是換行符。

然而,換行符不會(huì)被上面這句所吸收,所以在接下來(lái)的輸入中只需要忽略stdin中的殘余空白符即可(換行符就是空白符之一):

scanf("%*[^\n]");scanf(" %c",&recvChar);

這種方法已經(jīng)可以解決一般情況下的輸入殘余問(wèn)題,不過(guò)在后續(xù)接受格式化輸入時(shí)還得忽略換行符\n,還是有點(diǎn)麻煩。

循環(huán)取走殘余字符#

這一種方法能在清除殘余時(shí)順便吸收掉末尾的換行符\n。

取字符需要用到取單個(gè)字符的輸入函數(shù),這里為了方便,選用的是getchar()。

一般情況下可以這樣寫:

// getchar() 會(huì)從 stdin 中取走一個(gè)字符while(getchar() != '\n') ;

(使用前提:stdin中有殘余)

while循環(huán)會(huì)一直進(jìn)行,直至getchar()取到的字符為換行符\n為止,這樣就可以順帶吸收掉末尾的換行符了,能相對(duì)完美地清除掉stdin中的殘余內(nèi)容。
(在行緩沖模式下,用戶的一次輸入通常以一個(gè)換行符結(jié)束)

不過(guò)咧,還可以考慮更周全點(diǎn)。在getchar()獲取字符失敗的時(shí)候會(huì)返回EOF,但此時(shí)并不滿足while循環(huán)的退出條件,對(duì)此可以再完善一下:

// 臨時(shí)儲(chǔ)存字符// 之所以是整型(int),是因?yàn)镋OF是一個(gè)代表 負(fù)值整型(通常為-1) 的宏int tempChar;// tempChar=getchar()這種賦值語(yǔ)句本身的返回值就是所賦的值while ((tempChar = getchar()) != '\n' && tempChar != EOF) ;

這樣一來(lái),當(dāng)getchar()失敗時(shí),程序執(zhí)行就會(huì)跳出循環(huán)。

綜上,針對(duì)stdin中的殘余內(nèi)容的清除,最建議采用的便是最后這種處理方法。

不過(guò)其他的方法也是可以在一些場(chǎng)景中使用的,這就見(jiàn)仁見(jiàn)智了...

什么時(shí)候會(huì)返回EOF#

這里提一個(gè)題外的點(diǎn):什么時(shí)候getchar()會(huì)返回EOF?再進(jìn)一步想,什么時(shí)候程序會(huì)認(rèn)為標(biāo)準(zhǔn)流stdin達(dá)到了文件流末尾?

實(shí)際上,這里的EOF往往是用戶輸入的一個(gè)特殊二進(jìn)制值[3],輸入方式:

  • 在Windows系統(tǒng)下是?Ctrl +?Z(F6應(yīng)該也行)

  • 在Linux下是?Ctrl +?D

當(dāng)用戶在輸入中發(fā)送EOF時(shí),標(biāo)準(zhǔn)流stdin就會(huì)被標(biāo)記為EOF,因此getchar()就會(huì)獲取字符失敗而返回EOF。

// 測(cè)試用代碼#include <stdio.h>int main(){ char testChar; fprintf(stdout, "Input Char: \n"); testChar = getchar(); if (testChar == EOF) { printf("Received EOF\n"); } else { printf("Received a char\n"); } return 0;}

EOF在C語(yǔ)言中是一個(gè)宏,定義在頭文件stdio.h中,其值為一個(gè)負(fù)值的整型(并不一定是?-1),因此上面用tempChar != EOF來(lái)判斷getchar()失敗。

處理殘余的語(yǔ)句放在哪里#

現(xiàn)在咱已經(jīng)搞清楚了清除殘余的代碼,那么這些代碼該放在哪呢?

對(duì)于標(biāo)準(zhǔn)輸出流stdout來(lái)說(shuō),fflush語(yǔ)句往往放在輸出函數(shù)執(zhí)行完成之后,以立刻將輸出內(nèi)容打印到屏幕上:

printf("Hello ");printf("World!\n");fflush(stdout);

當(dāng)然,如果嫌麻煩可以在輸出前直接通過(guò)setbuf關(guān)閉stdout的緩沖:

setbuf(stdout, NULL);

對(duì)于標(biāo)準(zhǔn)輸入流stdin來(lái)說(shuō),處理殘余的語(yǔ)句往往放在每次輸入函數(shù)執(zhí)行之后,以及時(shí)清理流中殘余內(nèi)容:

int c;char testChar1, testChar2;scanf("%*s"); // * 用于屏蔽賦值while ((c = getchar()) != '\n' && c != EOF) ;testChar1 = getchar();while ((c = getchar()) != '\n' && c != EOF) ;scanf("%c", &testChar2);

當(dāng)然,這樣就顯得有點(diǎn)冗余了。

實(shí)際上可以將清除的語(yǔ)句封裝進(jìn)函數(shù)或者定義為宏(不過(guò)確實(shí)不太建議定義為宏),這樣也更便于維護(hù)。

總結(jié)#

之前瀏覽了很多相關(guān)文章,標(biāo)題和內(nèi)容大多都寫著“清空輸入緩沖區(qū)”?,F(xiàn)在想一下,這樣寫可能是不對(duì)的,因?yàn)閷?shí)際我清空的是標(biāo)準(zhǔn)輸入流stdin中的殘留內(nèi)容。在用戶輸入完成(輸入換行符)的那一刻,輸入緩沖區(qū)實(shí)際上就已經(jīng)被清空了。

也就是說(shuō),標(biāo)準(zhǔn)流和對(duì)應(yīng)的緩沖區(qū)要辨別清楚,二者不是同一個(gè)概念(一個(gè)stream一個(gè)buffer),千萬(wàn)不能混淆了。


簡(jiǎn)記C語(yǔ)言清空輸入殘留內(nèi)容的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
白沙| 永春县| 通许县| 石狮市| 增城市| 衡东县| 乌拉特中旗| 永胜县| 嘉兴市| 沁水县| 罗城| 苏尼特左旗| 长寿区| 云林县| 江源县| 寿宁县| 衡水市| 基隆市| 佛学| 龙州县| 安达市| 绵竹市| 阿瓦提县| 长葛市| 英吉沙县| 襄垣县| 利川市| 台北县| 文登市| 南阳市| 尼勒克县| 芒康县| 年辖:市辖区| 定结县| 金华市| 鹿邑县| 长宁县| 黄石市| 湖口县| 桓台县| 佛山市|