代码编译运行环境:Win10 x64 + CodeBlocks 17.12

参考文章

1.C++ cin 详细用法
2.C++cin详解及清空输入缓冲区
3.C语言 getchar()原理及易错点解析
4.C++中使用ios::sync_with_stdio(false)可能引发的问题
5.关于ios::sync_with_stdio(false)的一些问题

正文

首先,上结论:如果要关闭同步流ios::sync_with_stdio(false)来提高cin输入速度,就不能与getchar()混用,但是getchar()可以换成cin.get()!

ios::sync_with_stdio(false);
string s;cin>>s;
char c1=cin.get();
// char c2=getchar(); 不要用这个

举个例子说明一下(改编于牛客的一道题)。

要求输入n行带空格的字符串,每行包括至少2个单词,单词之间由单个空格分割。请你把每行字符串分成两部分,第一个部分就是第一个单词,第二个部分就是第一个单词后的字符串(不包括第一个单词后的首空格)。

输入的测试数据:

4
gg got to go
abr later
cde fgh py
opq rst

一、关闭同步流、使用getchar()【错误】

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    int n;cin>>n;
    string s1,s2;
    for(int i=1;i<=n;i++)
    {
        cin>>s1;
        // 注意getchar这句话,这个本意是读取每行第1个单词后的空格
        char c=getchar(); 
        getline(cin,s2);
        printf("i=%d getchar接收的字符c=%c 字符串s1=%s 字符串s2=%s\n\n",i,c,s1.c_str(),s2.c_str());
    }
    return 0;
}

运行结果:
在这里插入图片描述
观察一下以上结果。我们现在输入了4行字符串,却只输出了3行,而且编译器显示还可以再输入1次。那我们再随便输入一行字符串。
在这里插入图片描述
观察一下以上结果。我们现在输入了5行字符串,才得到4行输出。第2行的第1个单词“abr[空格]”压根就没被当作单词输出,而是被getchar()一个个的接收了,依次接收的字符是第2行的a, b, r, 空格

这种输出结果的原因将在最后一个部分解释。

二、关闭同步流、使用cin.get()【正确】

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false); 
    int n;cin>>n;
    string s1,s2;
    for(int i=1;i<=n;i++)
    {
        cin>>s1;
        char c=cin.get(); // getchar()更改为cin.get()
        getline(cin,s2);
        printf("i=%d getchar接收的字符c=%c 字符串s1=%s 字符串s2=%s\n\n",i,c,s1.c_str(),s2.c_str());
    }
    return 0;
}

运行结果:
在这里插入图片描述
这样结果是对的。对于每一行,我们先cin输入第1个单词,然后cin.get()接收第1个单词后的空格,然后再用getline输入后面的整个字符串。

三、不关闭同步流、使用getchar()【正确】

#include <bits/stdc++.h>
using namespace std;
int main()
{
    //ios::sync_with_stdio(false); 不关闭同步流
    int n;cin>>n;
    string s1,s2;
    for(int i=1;i<=n;i++)
    {
        cin>>s1;
        char c=getchar();
        getline(cin,s2);
        printf("i=%d getchar接收的字符c=%c 字符串s1=%s 字符串s2=%s\n\n",i,c,s1.c_str(),s2.c_str());
    }
    return 0;
}

这样结果也是对的,运行结果和第2个代码相同。

四、C++输入原理

1.关于输入缓冲区
  程序的输入都有一个缓冲区,即输入缓冲区。一次输入过程是这样的,当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据。正因为cin对象是直接从缓冲区取数据的,所以有时候当缓冲区中有残留数据时,cin对象会直接取得这些残留数据而不会请求键盘输入;当cin>>从缓冲区中读取数据时,若缓冲区中第一个字符是空格、tab或换行这些分隔符时,cin>>会将其忽略并清除,继续读取下一个字符,若缓冲区为空,则继续等待。但是如果cin读取成功,字符后面的分隔符是残留在缓冲区的,cin>>不做处理。
  当我们从键盘输入字符串的时候需要敲一下回车键才能够将这个字符串送入到缓冲区中,那么敲入的这个回车键(\r)会被转换为一个换行符(\n),这个换行符也会被存储在 cin 的缓冲区中并且被当成一个字符来计算。比如我们在键盘上敲下了 123456 这个字符串,然后敲一下回车键(\r)将这个字符串送入了缓冲区中,那么此时缓冲区中的字节个数是 7 ,而不是 6。

2.比较几个输入函数
(下面所说的分割符由Enter、Space、Tab键入;换行符指由Enter键入,在缓冲区中为'\n'。)
(1)$cin$
  对输入缓冲区中的第一个换行符忽略清除,继续阻塞等待缓冲区有效数据的到来,遇到分割符时结束读取。读取成功后,字符后的分隔符残留在缓冲区。
(2)$getline()$
  从输入缓冲区读取字符串时不忽略分隔符,直接读取,默认遇到换行符时终止,送入目标string后(换行符一并读入string),再将字符串的换行符替换为空字符’\0’。换行符不残留在输入缓冲区中,不会影响下面的输入处理。 因此,进行从键盘读取一行字符时,建议使用getline,较为安全。
(3)$cin.get()$
  从输入缓冲区读取单个字符时不忽略分隔符,直接读取,默认遇到换行符时终止。不处理换行符,换行符仍然残留在输入缓冲区。 效果同getchar()。
(4)$getchar()$
  键盘输入的字符都存到缓冲区内,一旦键入回车,getchar就进入缓冲区读取字符,一次只返回第一个字符作为getchar函数的值,如果有循环或足够多的getchar语句,就会依次读出缓冲区内的所有字符直到’\n’('\n'也将被读出)。

之所以你输入的一系列字符被依次读出来,是因为循环的作用使得反复利用getchar在缓冲区里读取字符,而不是getchar可以读取多个字符,事实上getchar每次只能读取一个字符。如果需要取消’\n’的影响,可以用getchar来清除,即从缓冲区读走一个字符。

3.ios::sync_with_stdio(false)的作用
  作用是取消缓冲区同步,例如printf()/scanf()是C函数,而cin/cout是C++函数,这些函数需要用到各自的缓冲区,为了防止各自的缓冲区错位,C++默认将C函数和C++函数的缓冲区同步。 它的原理是使本该同步的输入输出流分开,就是让C的输入输出流和C++的输入输出流分开。

五、解释代码输出的原因

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    int n;cin>>n;
    string s1,s2;
    for(int i=1;i<=n;i++)
    {
        cin>>s1;
        char c=getchar(); 
        getline(cin,s2);
        printf("i=%d getchar接收的字符c=%c 字符串s1=%s 字符串s2=%s\n\n",i,c,s1.c_str(),s2.c_str());
    }
    return 0;
}

在这里插入图片描述
注意:我们在代码前边写了ios::sync_with_stdio(false),那么C的缓冲区和C++的缓冲区就会分开。

首先键入n=4,这没什么问题,我们进入循环。

i=1,我们输入了两行。
  第一行,我们键入了gg got to go并回车,送入C++输入缓冲区,现在C++缓冲区有gg got to go以及\n。调用cin>>取出gg,C++缓冲区还剩下空格got to go(注意这里got to go前面还有一个空格,因为cin是不会取的);
  第二行,我们其实调用的是getchar(),它是C函数,只能从C的缓冲区中读入字符,但这时C的缓冲区什么也没有(因为缓冲区被分开了),所以getchar()只能要求等待再键入若干字符直到键入回车才能取出字符。键入后C的缓冲区为abr later\n,然后getchar()从C的缓冲区取出单个字符a
  接着调用getline(),它取出C++缓冲区剩下的空格gg got to go\n(送入指定string后其\n被替换为\0),C++缓冲区被清空。

i=2,我们输入了一行。
  因为这时C的缓冲区不为空了,键入回车后getchar()不必要求再键入若干字符而可以直接从C的缓冲区中取出单个字符b
  调用cin>>和getline的过程和i=1相同。由于C++缓冲区为空,所以键入回车后cde fgh py送入C++缓冲区,cin取出cde,缓冲区留下空格fgh py\n,然后getline()将其全部取走(送入指定string后其\n被替换为\0),C++缓冲区再次被清空。

之后的过程大致相同,我们可以发现循环4次导致getchar一个个取出的字符都是第二行输入的字符,即a, b, r, 空格。之后如果再循环输入6行同格式数据,则可以依次取出l, a, t, e, r, \n

总而言之,如果我们关闭了同步流,意味着C与C++的缓冲区错位分开,选择用C函数getchar()还是C++函数cin.get(),读取的缓冲区有所不同,导致了我们出现了错误的结果。正确代码就是只需用cin.get()即可。

最后,在C++中请用cin.get()代替getchar()吧!