tft每日頭條

 > 生活

 > c語言裡的scanf

c語言裡的scanf

生活 更新时间:2024-07-23 07:08:11

接下來的部分請認真閱讀:

“用戶輸入”這個文件,其實可以間斷進行讀取和寫入,先寫一點,再讀一點,再寫一點,再讀一點。

就好像你在寫一本小說,你朋友很好奇在旁邊盯着你看。你一邊寫着,他就一邊讀。類比過來,一般情況下(單線程的模式),命令行用戶輸入的時候程序是不能繼續運行的,也不能讀取輸入——就好像因為地方太窄,你朋友想要看你寫好的部分的時候,你就不得不停筆,把稿子給他看完以後再繼續寫。

而程序進行計算,産生輸出的過程,就像你的朋友對你寫好的部分提意見。命令行輸入的模式當中,每當你調用一次 scanf ,程序會先讀取你寫的還沒有讀取的部分,全部讀取完這個 scanf 還沒結束的話,它再讓你輸入。就相像你的朋友想要讀小說,你如果之前有寫好他還沒看的部分,他就先接着看,不急着要你寫;如果他看完了還不足以發表一次意見,或者他想讀的時候你根本都還沒寫,他就會喊你過來接着寫,直到你寫完之前都等着你。

而你卻不知道你的朋友需要讀多少才夠發表一次意見,就隻有寫一段請他過來讀一下。在命令行輸入當中對應的,就是你輸入的時候換了一次行。之前因為輸入不夠在等待你的 scanf ,聽到你輸入了 '\n' 以後就暫時過來接手稿子,繼續閱讀看看能不能讀完這一次 scanf 的目的。然後 scanf 一直重複上述的過程——不夠就讓你接着寫,寫到 '\n' 就看看有沒有寫夠,寫夠了再結束這個 scanf 繼續執行下面的代碼,好好就着你寫的小說計算、思考,把計算的結果 printf 出來,直到又遇到程序裡的下一個 scanf。

scanf 字面意思 scan f(ormat) 按格式掃描

用戶的輸入在某種奇怪的設計哲學裡是當作文件處理的,或者對應 C 裡的流——就是一個可以單方向讀入的(字符)串,讀入以後再也沒法回頭讀(有例外)。

在入門教材、學校/培訓機構課程中的練習、示例當中涉及到交互的時候,經常會說 scanf 是用來“輸入一個整數,輸入一個字符串”的東西,仿佛能直接輸入對應類型的對象,仿佛兩個對象的輸入之間是完全分開的。

其實實際的機制不那麼直觀,很多入門教材、課程以入門介紹為目的不會好好講解 scanf 的原理和用法(也沒有那個必要)。但是很多入門學習的人就是會在這些地方踩到陷阱,或者就是喜歡在這種地方鑽牛角尖。

順帶也普及一個觀念,很多時候教材要介紹的是一個非線性的,前後相關,非常複雜的系統,隻是為了給你介紹入門概念就要涉及到後面才講的内容,這時候就請按照教材作者的思路,他告訴你不用弄懂,你就别着急弄懂,後面會告訴你的。

scanf的第一個參數是格式參數,入門教材裡會說 "%d" 就會讀入一個整數,然後賦值給對應位置參數指針指向的對象。scanf("%d",ptr); 效果就行是 *ptr=getInt(); (如果真是這樣該多好,省得解釋這麼多了)

事實上,scanf 把你的輸入當成一個字符串處理。你輸入的時候大緻會這樣操作——輸入幾位數字,然後按下回車确認,這時候命令行換了一行,程序從 scanf 的地方繼續向下執行。

假設你想輸入的數是42,你的輸入實際上是"42\n"。scanf從第一個參數(格式字符串參數)裡讀到了%d,就知道應該從你的輸入字符串裡最開頭讀取一個整數(然後以int形式保存)。具體過程是這樣的——讀入'4',是一個數字,繼續讀下一個字符'2',還是數字,繼續讀'\n',不是數字,于是數的輸入就結束了,'\n'被留在輸入的字符串裡,'4'和'2'已經被讀過,後面再也不可能回來讀了。然後讀出來的數字'4'和'2'經過計算表示十進制數42,寫入對應位置參數指針指向的對象。

所有 scanf 調用都操作的是同一個輸入文件(字符串),前一個 scanf 舍棄掉的字符,後一個就讀不到了,每一個 scanf 都從上一次 scanf 讀到的進度繼續閱讀輸入的字符串。

這個例子結束為止一切和入門教程上說的沒有什麼區别。

但事實上有一個矛盾——輸入應該是一個字符串,你有接連的幾個scanf,中間還夾着計算、輸出:

例1.

int a,b;

scanf("%d",&a);\\類似*(&a)=getInt();

printf("a=%d\n",a);

scanf("%d",&b);\\類似*(&b)=getInt();

printf("b=%d\n",b);

命令行當中看起來像這樣

12

a=12

450

b=12450

用戶的輸入即使在按了回車以後還是沒有結束的(實際上隻有輸入特殊字符才能結束用戶輸入)

這個例子用教科書裡的說法(模型)可以解釋的通,但是用這裡說的“scanf 閱讀字符串”就說不通——輸入都還沒有完成(沒有整個字符串都輸入完),輸出就先蹦出來了

如果按照先前的解釋,輸入輸出應該像這個樣子:

12

450

^z

a=12

b=450

其中^z就是我說的結束輸入的特殊字符,輸入方式是ctrl z;

回到文件的說法,用戶的輸入是一個單向讀取的文件,與之相對,輸入過程中用戶就相當于在寫入這個文件。如果用戶一次性寫完再讓程序讀取,就不可能進行“交互”,在輸入以後立馬看到結果,再根據看到的結果決定下一步輸入什麼。

而你就可以根據程序對你寫的小說的反饋,決定接下來寫什麼内容。

例2.

//學生管理系統的片段,程序隻用來說明,請不要模仿這種寫法

printf("please input the operation:");

scanf("%s",opBuff);//讀取要進行的操作

if(!strcmp(opBuff,"add")){//添加學生

printf("please input id, name, age:");

scanf("%d %s %d",&id,name,&age);//讀取要進行的操作

student_record sr;

make_student_record(&sr,id,name,age);

if(insert(&sr)) printf("insert succeeded");

else printf("insert failed");

}else if(...){//其他操作

}

...

程序告訴你他想知道你進行什麼操作,在命令行輸出

please input the operation:

你輸入了

add

然後回車('\n')告知程序,你寫完了,請他閱讀,于是他知道你要加入一個學生,他就告訴你,接下來的段落請告訴他學生的學号、姓名、年齡——

please input id, name, age:

你看到了這個說明就知道該寫什麼了,于是輸入:

12

然後立馬就回車,等不及先想知道程序對你寫的小說有什麼看法;但是程序讀下了“12\n”,對照自己的想要看到的東西("%d %s %d"),現在進行到了" %s %d",覺得還沒讀完(一個scanf還沒結束),于是還是等你寫,你寫道——

bakasunchy 19

然後忘了回車,雖然你輸入完了,但是程序不知道,還在傻等。知道你反應過來按下了回車,程序才讀完這些,知道你小說裡有一個19歲叫bakasunchy的人物并且記住了他(insert(&sr)),并且告訴你他能接受得了這個人物——

insert succeeded

拓展閱讀:如果你輸入 add 12 bakasunchy 19, 然後回車,程序就會一股腦兒輸出“please input id, name, age:insert succeeded”(連換行都沒有,原因是正常情況下的換行其實是你輸入的),其實就相當于你在叫你朋友來看之前一次性寫了兩個段落,他反應慢了半拍

接下來講解格式字符串的格式

scanf 讀你寫的小說的時候,自己會先寫下期待看到的東西,然後從你的小說裡看到一件就勾掉一件,而且按順序勾。

格式字符串的構成主要有 字符 空白 格式說明符 三種(寫的時候沒有參照标準,說法未必正确)

在格式字符串裡如果出現了非空白字符,就說明必須原模原樣地輸入一樣的字符,比如 scanf("long_long_ago"); 你就必須輸入 "long_long_ago" ,就好像你的讀者很挑剔,必須用“很久很久以前”來開頭。scanf 會先發現第一個字符是l,對應格式字符串裡的字符,對,于是接着讀下一個,直到剛好 scanf 想看到的東西都看到了。

而出現空白的時候對應的是任意數目的空白,比如

scanf(" ")就可以讀入" " 或者 ”\t\n\n“(\t是制表符,也是空白的),甚至什麼都沒有,就好像讀慣了盜版網絡小說的讀者無論怎麼排版都能讀得下去。

現在有必要插入說明,scanf按照格式字符串讀取的時候,是以最長匹配為原則的——

scanf("super soda sea"); 你輸入"super \n soda sea"的時候,scanf 對比到 super 後面的那個空格,不會因為" "可以對應格式裡的空格,就跳過了想要看's',然後在看到'\n'的時候抱怨——他會一直讀到不是空白字符為止,也就是's',然後發現對得上。

格式說明符以'%'開始,緊接着的說明要輸入的、可以對應的字符,和解釋方式

隻講幾個簡單的用于舉例:

d 會一直讀取十進制數字(0-9),并且把讀到的數字當成一個數來解釋,保存到對應位置的參數(指針指向的對象)。

s 會讀取除了空格符以外的任意字符(除非遇到文件結尾),把讀到的結果保存到對應參數指針對應的字符數組裡(之所以說是對應,原因是并不是指向,這裡不細說)

c 會讀取除了空格符以外的任意字符,但隻讀一個

還有些有趣的部分就不講了,隻告訴讀者有這樣的東西存在(請查閱 cppref):在%後加入數字可以讀入固定長度,在%後數字前加上*可以不保存這個格式說明符得到的輸入到對應參數(參數順序順延),%後加中括号括起字符串可以匹配特定字符組成的字符串

在大部分格式說明符之前,都會相當于加了一個空白字符

例3.

scanf("%d",&a);

deal_with(a);

scanf("%d",&b);

輸入:

12

24

在第一個 %d 處 12 被匹配掉了,直到遇到了'\n',scanf 知道第一個 %d 結束了,于是第一個 scanf 調用返回,程序繼續執行。處理完 a 的事以後繼續 scanf("%d",&b); 。此時輸入中還剩下"\n24\n",如果直接按 %d 本身的規則(不包括空白字符處理),第一個遇到的字符就不是十進制數字,輸入就會出錯。但是 %d 相當于之前有一個空白符。%d 會先處理掉所有空白符号,然後繼續。這也是這種代碼可以正常運行的原因。

例4.

scanf("%d",&a);

deal_with(a);

scanf("%s",str);

輸入:

9

baka

你會發現str其實是"\nbaka",baka 前多了個換行,%s 認為空格很有可能也是要輸入的一部分,因此不會先省略掉\n。

%c也按相同的邏輯設計,這也是

錯例1.

scanf("%s",&command);

//do something and output something

scanf("%c",&operation);

輸入“cal\n ”不能正确按照使用者想法進行的原因——要能經過上一次輸入還殘留了一個\n,然後就賦值給operation,後面真正輸入的 就這樣錯過了(甚至造成後面一系列的輸入都出錯)。

要時刻記着,scanf 隻能從你輸入的字符串裡推測你輸入的哪一段對應他的哪一個格式說明符,如果有兩種以上的對應方法,就會按前面所說的“最長匹配”的原則來匹配。abcde如果可以切分成abc/de ab/cde兩種切分方法都符合,scanf 一定會選擇abc/de。有了以上的知識以後再講講怎麼處理輸入錯誤——

scanf 是有返回值的。在輸入沒有正确進行的時候,scanf 會返回比格式說明符數量少的整數,也即成功輸入的格式說明符的數量,比如scanf("%d%d%d"),如果在讀到第三個%d時出錯了,scanf 會中止,并返回2。并且,錯誤輸入的部分就留着不會繼續讀下去。

下一次 scanf ,還會從上一個 scanf 放棄的地方開始。

這次你扮演讀者,如果是讀取真正一次性寫好一次性讀取的文件,出了錯誤是不可能糾正的,但是交互程序就可以——你可以要求用戶重新輸入之前輸入錯誤的部分。

問題是,你不知道從哪裡開始是用戶重新輸入的部分。怎麼跳過錯誤的部分從重新輸入的部分開始讀起呢?

有些書會教你 fflush(STDIN); 然而這是 UB(請自行搜索)。你要做的是,一直讀取,直到發現上一次失敗 scanf 應該結束的位置。

其實可以簡單做個推理,scanf 能繼續執行,一定是輸入了回車。在用戶看到“輸入錯誤”的提示之前他一定剛剛輸入了回車,在繼續輸入之前一定來得及看到“輸入錯誤”的提示。

對于 scanf 來說,他是沒有時間概念的

錯例2.

while(scanf("%d",&a)<1){

scanf(magic);//假設magic可以匹配任意字符而且丢棄(實際上并不存在)

printf("input error, please input an integer:");

}

對于 scanf 來說,他是沒有時間概念的,丢棄錯誤輸入的scanf(magic);不知道到底該什麼時候停,他不知道自己被調用之前用戶實際上輸入了多少——他丢完了已有的輸入以後,還會繼續要求輸入,然後把你的輸入再丢掉,讀完又問你要,周而複始。

正确的實現是

例5.

while(scanf("%d",&a)<1){

scanf("%*[^\n]");

printf("input error, please input an integer:");

}

你可以當成一個模闆,寫的時候類似這樣:

while(scanf(...)<n){

scanf("%*[^\n]");

printf("input error, please input (what you specified up there):");

}

scanf("%*[^\n]"); 的意思是讀取任意不是換行的字符,并且丢棄不保存,有興趣的話可以看 cppref 的解釋。

補充兩個錯誤案例:

scanf("%d\n",&a);

scanf("%d,%d");//當輸入是 5 1 的時候

第一個 scanf 輸入完數字以後無論按下多少次回車鍵都不行——隻要讀者看懂了前面的說明就知道,他們都和 '\n' 匹配。隻有再輸入空白符以外的東西,并且回車才能喚醒 scanf。

第二個 scanf 也許本身并不算錯,隻是你必須按 [ \n\t][0-9] ,[ \n\t][0-9] (任意數目空白符,一個以上的數字,逗号,任意數目空白符,一個以上的數字)的形式輸入。強行用空格分段就會導緻 ',' 匹配失敗,讀取不到第二個數。

喜歡的話關注收藏評論轉發一波 比心麼麼哒!加入我們C語言C 學習交流 壹 496926338群内有大量的項目開發和新手教學視頻千人大群等着你來加入。

c語言裡的scanf(關于C語言scanf的一點講解)1

C語言C 學習交流群496926338

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关生活资讯推荐

热门生活资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved