关于cin scanf 和 gets() getline() 的反思与总结

以下部分内容转载自琴影老师博客:这是一个传送门 感谢帮助!

今天做了一道算法题,题目本身不是特别难,内容如下:

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(' '), enter(' ') 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(′ ')、换行(' ')和所有标点都不翻译。单独一行“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] = ''; 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 输入字符时,会将' '吸收

     1.2  scanf 输入字符串时,遇到空格或者回车就代表结束

            输入一个字符串,如果在这之前有空格或回车,空格和回车不会给字符串。遇到下一个空格或回车才代表结束

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

 (2)

    cin用法很简单,如果输入的是一个字符,那么,' '不会被吸收, 其他的情况和scanf差不多

(3)如果用gets()或者getline(),那么它一遇到' '就结束,比如定义 char c; char s[10]; scanf("%c", &c);gets(s);printf("%c ", c);printf("%s",s);

     如果一输入一个字符想给c,然后回车在下一行输入一行字符串给s;那么输出的时候会发现,第一行是字符c,第二行是个空行, 光标在第三行;

分析:输入的第一个字符给了c,然后回车' ',这个回车代表了s是个空串(很神奇),同时,如果在输入一个字符c之后,按两个空格再加一个字符a再回车,那么s包含的就是两个空格字符加字符a,在结束

    还发现,如果定义 char c[10]; char s[10]; scanf("%s", c);gets(s);printf("%s ", 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输入时,结束输入后的那个换行(' ')或是空格(' ')都会停留在缓冲区;

(2) 用scanf输入字符(注意不是字符串)时,会自动“吃”停留在缓冲区的换行符或者空格,把它当作是你输入的字符;

(3) 用cin输入字符时,就不会“吃”缓冲区内的换行或是空格;

(4) 用cin或是scanf输入字符串时,前面的所有空格或是换行都不会被“吃”;

(5) 用gets()以及getline()输入时,会自动“吃”停留在缓冲区的字符,把它们当作是你输入的字符;

(6) gets()以及getline()结束输入之后的那个换行(' '),不会被放到缓冲区。

这样一想,以上的问题就都迎刃而解。

比如上文给出的第一个代码,它的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] = '';  //当输入的不再是字母,就放进去一个结束符''
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             //因为会遇到'',所以即使buf不重新初始化也没关系,因为每次读取到''就会停止
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 }

那么这道题就算是彻底过去了,以前在输入字符、字符串时我从来没有考虑过缓冲区的问题,以后就要多加注意啦!

原文地址:https://www.cnblogs.com/Aikoin/p/9312953.html