1. 程式人生 > >關於cin scanf 和 gets() getline() 的反思與總結

關於cin scanf 和 gets() getline() 的反思與總結

png putc color getchar 不為 with info 正數 yesterday

以下部分內容轉載自琴影老師博客:這是一個傳送門 感謝幫助!

今天做了一道算法題,題目本身不是特別難,內容如下:

What Are You Talking About

Problem Description

Ignatius is so lucky that he met a Martian yesterday. But he didn‘t know the language the Martians use. The Martian gives him a history book of Mars and a dictionary when it leaves. Now Ignatius want to translate the history book
into English. Can you help him? Input The problem has only one test case, the test case consists of two parts, the dictionary part and the book part. The dictionary part starts with a single line contains a string "START", this string should be ignored, then some lines
follow, each line contains two strings, the first one is a word in English, the second one is the corresponding word in Martian‘s language. A line with a single string "END" indicates the end of the directory part, and this string should be ignored. The book
part starts with a single line contains a string "START", this string should be ignored, then an article written in Martian‘s language. You should translate the article into English with the dictionary. If you find the word in the dictionary you should translate
it and write the new word into your translation, if you can‘t find the word in the dictionary you do not have to translate it, and just copy the old word to your translation. Space(‘ ‘), tab(‘\t‘), enter(‘\n‘) and all the punctuation should not be translated.
A line with a single string "END" indicates the end of the book part, and that‘s also the end of the input. All the words are in the lowercase, and each word will contain at most 10 characters, and each line will contain at most 3000 characters. Output In this problem, you have to output the translation of the history book.

Sample Input START from fiwo hello difh mars riwosf earth fnnvk like fiiwj END START difh, i‘m fiwo riwosf. i fiiwj fnnvk! END Sample Output hello, i‘m from mars. i like earth! Hint Huge input, scanf is recommended. 大意就是:Ignatius很幸運,遇到了一個火星人,火星人丟給他一本火星的歷史書和一本英文-火星文對照字典,現在來幫他翻譯這本歷史書。 這個問題只有一個測試用例,由字典和書兩部分組成。字典部分由單獨一行“START”開始,接下來的每行包含兩個字符串,一個是英文,一個是對應的火星文。字典部分以單獨一行“END”結束。書的部分由單獨一行“START”開始,這個字符串應該被忽略,然後是一篇火星文寫的文章。如果是字典裏出現了的火星單詞,就把翻譯後的新單詞寫進譯文中,如果不是,就不必翻譯它,只需把舊單詞抄到譯文中。空格(‘ ‘)、tab(′\t‘)、換行(‘\n‘)和所有標點都不翻譯。單獨一行“END”標誌著書部分的結尾,這也是輸入的結尾。所有單詞都是小寫字母,每個單詞最多包含10個字符,每行最多包含3000個字符。 輸出翻譯後的文本。 這道題很明顯是用STL的map做,但是我寫的代碼運行後卻發現輸出怎麽樣都不對,於是在網上看了一些別人寫的代碼,發現他們都用到了getchar(); 函數,而我沒有。這裏貼上我看到的兩位dalao的代碼,方法不太一樣但想法差不多: (傳送門1 )
 1
#include <iostream> 2 #include <cstdio> 3 #include <string> 4 #include <cstring> 5 #include <cctype> 6 #include <map> 7 using namespace std; 8 9 int main() 10 { 11 char buf[12], sign, s1[12], s2[12], ch; 12 map<string, string> mp; 13 int
id = 0; 14 gets(buf); //strip START 15 while(scanf("%s%s", s1, s2), strcmp(s1, "END")){ 16 mp[s2] = s1; 17 } 18 getchar(); //註意此處! 19 while(scanf("%c", &ch)){ 20 if(isalpha(ch)) buf[id++] = ch; 21 else{ 22 buf[id] = \0; id = 0; 23 if(strcmp(buf, "END") == 0) break; 24 if(mp.find(buf) != mp.end()){ 25 printf("%s", mp[buf].c_str()); 26 }else printf("%s", buf); 27 putchar(ch); 28 } 29 } 30 return 0; 31 }

(傳送門2)

#include<cstdio>
#include<string>
#include<iostream>
#include<map>
#include<sstream>
#include<cctype>
using namespace std;       
int main()
{
    string s,w1,w2,line,word;
    map<string,string> mp;
    cin>>s;
    while(cin>>w1&&w1!="END")
    {
        cin>>w2;
        mp[w2]=w1;
    }
    cin>>s;
    getchar();   //註意此處!!
    while(getline(cin,line)&&line!="END")
    {
        string str="";
        for(int i=0;i<line.length();i++)
        {
                if(!isalpha(line[i]) )
                {
                   map<string,string>::iterator it=mp.find(str);
                   if(it==mp.end()){cout<<str;}
                   else cout<<mp[str]; 
                   str="";
                   cout<<line[i];
                }
                else str+=line[i];
        }
        cout<<endl;
    }
    return 0;
}

註意我兩處標註的getchar(),因為自己太弱,接觸的題也少,當時我真是百思不得其解,只知道它是用來吃換行的,但並不知道這個換行到底是哪來的,只能大概猜測和緩沖區有關,於是上網搜了很多關於getchar、緩沖區、“吃”換行等等的資料。然後我發現一篇博客(鏈接在文章頂部),它是這麽說的:

1)

1.1 scanf 輸入字符時,會將‘\n‘吸收

1.2 scanf 輸入字符串時,遇到空格或者回車就代表結束

輸入一個字符串,如果在這之前有空格或回車,空格和回車不會給字符串。遇到下一個空格或回車才代表結束

1.3 讀一行字符,可以用gets();

(2)

cin用法很簡單,如果輸入的是一個字符,那麽,‘\n‘不會被吸收, 其他的情況和scanf差不多

(3)如果用gets()或者getline(),那麽它一遇到‘\n‘就結束,比如定義 char c; char s[10]; scanf("%c", &c);gets(s);printf("%c\n", c);printf("%s",s);

如果一輸入一個字符想給c,然後回車在下一行輸入一行字符串給s;那麽輸出的時候會發現,第一行是字符c,第二行是個空行, 光標在第三行;

分析:輸入的第一個字符給了c,然後回車‘\n‘,這個回車代表了s是個空串(很神奇),同時,如果在輸入一個字符c之後,按兩個空格再加一個字符a再回車,那麽s包含的就是兩個空格字符加字符a,在結束

還發現,如果定義 char c[10]; char s[10]; scanf("%s", c);gets(s);printf("%s\n", c);printf("%s",s);

輸入asd SS

結果輸出的是

asd

__SS(前面有有兩個空格)

表明輸入asd加個空格表示c字符串結束時,這個空格同時給了字符串s。

(4)如果定義的是字符數組 char c[10],那麽讀入一行只能用gets(),不能用getline();可以用cout輸出字符數組,也可以用printf()輸出;

如果定義string s;

輸入不能用gets(),只能用getline();

輸出不能用printf(),只能用cout;

結合以上內容和自己的一些代碼測試,我總結出以下幾點:

(1) 用cin以及scanf輸入時,結束輸入後的那個換行(‘\n‘)或是空格(‘ ‘)都會停留在緩沖區;

(2) 用scanf輸入字符(註意不是字符串)時,會自動“吃”停留在緩沖區的換行符或者空格,把它當作是你輸入的字符;

(3) 用cin輸入字符時,就不會“吃”緩沖區內的換行或是空格;

(4) 用cin或是scanf輸入字符串時,前面的所有空格或是換行都不會被“吃”;

(5) 用gets()以及getline()輸入時,會自動“吃”停留在緩沖區的字符,把它們當作是你輸入的字符;

(6) gets()以及getline()結束輸入之後的那個換行(‘\n‘),不會被放到緩沖區。

這樣一想,以上的問題就都迎刃而解。

技術分享圖片

比如上文給出的第一個代碼,它的while循環條件是同時輸入兩個字符串並且第一個字符串不為"END",所以在字典部分結束輸入“END”後並不會立即跳出循環,而是在輸入後面書部分的"START"後才會跳出while循環。用scanf輸入完"START"後有一個換行符,這個換行符停留在了緩沖區,而後面緊接著的while循環就是用scanf輸入字符(註意區分字符和字符串),於是這個緩沖區內的換行符就會被“吃”掉,它會以為這是你輸入的第一個字符,於是後面就全錯了。所以要在第一個while循環與第二個while循環之間加一個getchar(),把緩沖區內的這個換行符給“吃”了,這樣後面的程序才不會被影響。

另外,我在自己寫代碼的時候曾經把這裏的scanf("%c",&ch)換成了cin>>ch,自以為少寫了幾個字符,結果一看輸出,全錯了。註意看我上文總結的第三點,cin不會“吃”換行或是空格,於是原文中的換行和空格都不會被輸出,這樣的結果怎麽能對呢?所以這邊必須要用scanf來輸入字符。

技術分享圖片

再看第二個代碼,它的while循環條件是輸入一個字符串並且這個字符串不為"END",於是在輸入完"END"後,就立刻跳出了第一個while循環。接下來是用cin輸入書的開始標誌"START",可能有人會有疑問,為什麽在跳出循環後,輸入"START"前,不用加getchar()呢?可以看我上文總結的第四點,因為輸入的是字符串,所以緩沖區的換行不會對它產生影響。但是輸入完"START"後的換行被放在了緩沖區,而接下來就是用getline讀取一行字符串。註意上文總結第五點,這個換行會被getline吃掉,導致後面輸出出現錯誤。所以要在輸入完"START"後,getline()前,放一個getchar(),來“吃”掉這個換行。

另外,我發現第二個代碼有一點小缺陷,若是書的輸入部分,每行結尾處沒有標點符號,則輸出錯誤,而第一個代碼就沒有這種問題,大家可以測試一下。

下面給出我看完兩位dalao的代碼,並且理解了getchar()的用意後,自己敲的還原兩種方法的代碼,加上了厚厚的註釋。

【法一】

 1 #include <iostream>
 2 #include <string>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <map>
 6 using namespace std;
 7 
 8 int main()
 9 {
10     string start,eng,mar;
11     map<string,string> m;
12     cin>>start;
13     while(cin>>eng&&eng!="END"){
14         cin>>mar;
15         m[mar] = eng;
16     }
17     cin>>start;
18 //重要:輸入start之後有回車,回車停留在緩沖區,讀取字符時會先讀取這個回車,所以要getchar();
19     getchar();
20     char buf[1024];
21     char ch;
22     int n = 0;
23     while(scanf("%c",&ch)){
24         if(isalpha(ch))
25             buf[n++] = ch;  //如果輸入的一直是字母,就把輸入的字母依次放進buf
26         else{
27             buf[n] = \0;  //當輸入的不再是字母,就放進去一個結束符‘\0‘
28             //strcmp: 若str1==str2,則返回零;若str1<str2,則返回負數;若str1>str2,則返回正數。
29             if(strcmp(buf,"END")==0) break;  //如果此時的buf是END即結束循環 因為用的字符數組所以要用到strcmp
30             if(m.count(buf)!=0) //在map中查找buf是否為關鍵字 如果是 就輸出buf在map中對應的值(英文)
31                 cout<<m[buf];
32             else cout<<buf;   //如果不是關鍵字就直接輸出
33             cout<<ch;   //輸出這個非字母的字符(空格,換行,tab,標點...)
34             n = 0;  //讓n重新等於0 buf重新開始儲存字符
35             //因為會遇到‘\0‘,所以即使buf不重新初始化也沒關系,因為每次讀取到‘\0‘就會停止
36             //當然最好還是初始化一下
37         }
38     }
39 
40     return 0;
41 }

【法二】

 1 //與法一相比有缺陷 若每行結尾沒有標點符號則輸出錯誤
 2 #include <iostream>
 3 #include <string>
 4 #include <cstring>
 5 #include <algorithm>
 6 #include <map>
 7 using namespace std;
 8 
 9 int main()
10 {
11     string start,eng,mar;
12     map<string,string> m;
13     cin>>start;
14     while(cin>>eng>>mar&&eng!="END"){
15         m[mar] = eng;
16     }
17     //因為第一個while裏面是同時輸入eng和mar,所以在輸入END後不會立即跳出循環,而是在輸出緊接著的START後才會跳出
18     getchar(); //輸出start後有個回車,下面的getline會讀取這個緩沖區內的回車,把它當作我們的第一個輸出,所以要getchar()
19     string line;
20     while(getline(cin,line)&&line!="END"){  //重要: getline 敲完一行之後的回車不會放到緩沖區!!!
21         string str = ""; //相當於一個空串
22         for(int i=0;i<line.size();i++){
23             if(isalpha(line[i])) str += line[i]; //如果是字母 就依次連接在str後面
24             else{
25                 if(m.find(str)!=m.end())  //用map的find函數查找str是否為一個鍵值 返回值是一個叠代器 返回的是被查找元素的位置,沒有則返回map.end()
26                     cout<<m[str];
27                 else cout<<str;  //若不為一個鍵值,則原樣輸出
28                 cout<<line[i];  //輸出此處的不為字母的字符 (註意不包含換行)
29                 str = "";
30             }
31         }
32         cout<<endl;  //手動換行2333
33     }
34 
35     return 0;
36 }

那麽這道題就算是徹底過去了,以前在輸入字符、字符串時我從來沒有考慮過緩沖區的問題,以後就要多加註意啦!

關於cin scanf 和 gets() getline() 的反思與總結