何时以及为什么我需要在C++中使用cin.ignore()?

97

我用C++写了一个非常基础的程序,让用户输入一个数字和一个字符串。令我惊讶的是,在运行程序时,它从未停下来询问要输入字符串。它直接跳过了字符串部分。通过在StackOverflow上查阅一些资料,我发现需要添加一行代码:

cin.ignore(256, '\n');

在获取字符串输入的那一行之前添加这句话,问题得到了修复,程序开始工作了。我的问题是,为什么C ++需要这个cin.ignore()语句,我如何预测何时需要使用cin.ignore()

这是我编写的程序:

#include <iostream>
#include <string>

using namespace std;

int main()
{
    double num;
    string mystr;

    cout << "Please enter a number: " << "\n";
    cin >> num;
    cout << "Your number is: " << num << "\n";
    cin.ignore(256, '\n'); // Why do I need this line?
    cout << "Please enter your name: \n";
    getline (cin, mystr);
    cout << "So your name is " << mystr << "?\n";
    cout << "Have a nice day. \n";

}

3
@LightnessRacesinOrbit 要投票将其关闭为重复问题吗? - M.M
1
@LightnessRacesinOrbit 对不起,重复了这个问题。 - Raddicus
@MattMcNabb:是啊,但我懒得找了。如果不用找就好了。 - Lightness Races in Orbit
1
好的,当我运行你的代码而没有忽略时,它会直接跳过第二个cin>>,这可能是因为endl仍然在流中。但是当我使用ignore运行你的代码时,如果我输入的不是数字字符,它仍然会跳过第二个cin>>。如果我输入两个ignores,它会在第二个cin>>之前等待第三个输入。有人能解释一下吗? - Kyle Delaney
7个回答

96

ignore的名称已经很清楚地表明了它的作用。

它不会“丢弃”你不需要的东西,而是忽略你在调用它时指定的字符数,直到指定的分隔符之前。

它适用于输入和输出缓冲区。

对于std::cin语句,你需要在调用getline之前使用ignore,因为当用户使用std::cin输入时,他们会按Enter键,使'\n'字符进入cin缓冲区。然后如果你使用getline,它会获取换行符而不是你想要的字符串。因此,你可以使用std::cin.ignore(1000,'\n'),这应该清除缓冲区直到你想要的字符串。(这里的1000是为了跳过指定分隔符之前的特定数量的字符,在这种情况下,是'\n'换行符。)


4
谢谢。问一下,为什么C++会出现你所描述的在cin缓冲区中获取'\n'字符的问题,这是否是语言本身的缺陷?还是故意设计成这样以便达到某种目的? - Raddicus
8
不完全是缺陷,更像是精度。技术上讲,如果换行符没有被读入,那么并没有读取所有内容。输入缓冲区会忽略空格,因此当您从std::cin中读取信息时,换行符并不重要。但是当谈论getline时,它会获取整行直到换行符,当换行符是getline函数看到的第一件事时,它就只获取这些内容。 - savageWays
26
需要使用numeric_limits<streamsize>::max()来忽略所有字符,而不是1000。 - Neil Kirk
2
根据标准,.ignore()函数的作用是“提取并丢弃字符”,因此我认为这与抛弃字符非常接近。http://www.cplusplus.com/reference/istream/istream/ignore/ - csguy
1
争论:在getline之前不要使用ignore。迟早你会发现或创建一个情况,在该情况下,ignore被调用时流中没有包含你想要忽略的数据,并最终丢弃了你想要的数据。相反,在你知道会在流中留下不需要的数据的操作之后再使用ignore - user4581301

45

你的思考方式是错误的。每次使用cingetline时,你都在逻辑步骤中思考。例如:首先询问一个数字,然后询问一个名称。这种思考cin的方式是错误的。所以你遇到了竞态条件,因为你假设每次请求输入时流都是清除的。

如果你只为输入编写程序,你会发现问题:

int main()
{
    double num;
    string mystr;

    cin >> num;
    getline(cin, mystr);

    cout << "num=" << num << ",mystr=\'" << mystr << "\'" << endl;
}

以上内容中,你在想,“首先要获取一个数字”。所以你输入 123 按下回车,输出结果将是 num=123,mystr=''。为什么呢?因为在输入流中你有 123\n,而123 被解析进变量 num 中,而 \n 仍留在输入流中。查阅 getline 函数的文档,它默认会读取输入流直到遇到 \n。在本例中,由于输入流中存在 \n,所以它看起来“跳过”了,但其实它已经正常工作了。

为了让上述方法生效,你需要输入 123Hello World 才能正确输出 num=123,mystr='Hello World'。或者你可以在 cingetline 之间插入 cin.ignore 命令,这样就能按照你预期的逻辑步骤进行分割。

这就是为什么你需要使用 ignore 命令。因为你是按照逻辑步骤而不是按照输入流形式来考虑,所以会遇到竞态条件。

再举一个在学校经常出现的代码示例:

int main()
{
    int age;
    string firstName;
    string lastName;

    cout << "First name: ";
    cin >> firstName;

    cout << "Last name: ";
    cin >> lastName;

    cout << "Age: ";
    cin >> age;

    cout << "Hello " << firstName << " " << lastName << "! You are " << age << " years old!" << endl;
}

上面似乎是按照逻辑步骤进行的。首先要求输入名字、姓氏,然后是年龄。因此,如果你分别输入 JohnDoe,然后是 19,应用程序将依次执行每个逻辑步骤。如果你把它看作“流”,你可以在“名字:”问题中直接输入 John Doe 19,也能正常工作并跳过其余问题。为了使上述按照逻辑步骤进行的方法起作用,你需要在每个问题的逻辑断点处忽略剩余的流。

只需记住将程序输入视为从“流”中读取而不是按照逻辑步骤进行。每次调用cin都是从流中读取数据。如果用户输入错误,则会导致这个应用程序出现错误。例如,如果用户在一个期望double的地方输入字符,那么应用程序将产生看似奇怪的输出。


27

简短回答

为什么?因为输入流中还有空格(换行、制表符、空格、新行)。

什么时候?当你使用一些函数时,它们本身不会忽略前导的空格。默认情况下,Cin会忽略并删除前导空格,但getline无法自行忽略前导空格。

现在是详细回答。

您在控制台输入的所有内容都从标准流stdin中读取。当您输入一些内容(例如在本例中为256)并按Enter键时,流的内容变为256\n。此时,cin选取256并将其从流中移除,但\n仍然保留在流中。 接下来,当您输入您的姓名(例如Raddicus)时,流的新内容为\nRaddicus

现在问题来了。 当您尝试使用getline读取一行时,如果没有提供第三个参数作为分隔符,getline默认会读取到换行字符并从流中删除换行字符。 所以调用newline时,getline从流中读取并丢弃\n,导致mystr中读取了一个空字符串,看起来像是跳过了getline(但实际上并没有),因为流中已经有一个换行符,getline不会提示输入,因为它已经读取了它应该读取的内容。

那么,cin.ignore在这里起什么作用?

根据cplusplus.com中的忽略文档,函数 istream& ignore (streamsize n = 1, int delim = EOF);会从输入序列中提取字符并将其丢弃,直到已提取 n 个字符或遇到一个与 delim 相等的字符。如果达到文件末尾,也会停止提取字符。如果在提取 n 个字符或找到 delim 之前就提前到达文件末尾,则该函数将设置 eofbit 标志。因此,cin.ignore(256, '\n');会忽略前 256 个字符或遇到分隔符(这里是换行符),以先出现的为准(这里是 \n,所以它会忽略直到遇到 \n)。如果不确定要跳过多少字符,你只需使用 cin.ignore(numeric_limits<streamsize>::max(),'\n')来清除流以准备使用 getline 或 cin 读取字符串。简单地说,它会忽略最大流大小相等的字符或者遇到 '\n' 字符,以先出现的为准。

cin.get() 也会像 cin.getline() 一样考虑空格字符吗? - Abhishek Mane

6
当你想要手动丢弃输入流中特定数量的字符时,这非常常见。一个很常见的用例是安全地忽略换行符,因为cin有时会留下换行符,你必须跳过它们才能到达下一行输入。
简而言之,它在处理流输入时给了你灵活性。

1
谢谢回复。在使用cin之后,有没有更有效的方法来使用getline()?我是否应该计划在每个带有cin后跟getline()的行之间插入一个ignore line?这对我来说似乎相当愚蠢。也许当你比我更有C++经验时,这会更有意义。 - Raddicus
1
@Raddicus,这真的很愚蠢,特别是因为我们知道 '\n' 在 IOStreams 中被解析为空格,并且 IOStreams 提供了 std::ws 操纵器,它会丢弃前导空格。你只需要使用 std::getline(std::cin >> ws, mystr) 来忽略所有空格和换行符。 - David G
1
@Raddicus:我建议你避免在同一流上混合使用operator>>getline。例如,对于cin,只使用getline。如果您需要非字符串数据,则可以使用istringstreamstoi/stod/etc...解析从getline获取的字符串。 - Benjamin Lindley
我认为随意地对我的答案进行负面评价有点过分,考虑到问题的开放性,我认为这个答案是合适的。这只是一个观点。 - shafeen
@Raddicus 我在回答中没有更具体地说明是因为使用 ignore() 和 getline() 或两者的组合的时间和地点将取决于情况和您特定的用例,我认为随着您在未来更多地使用 C++,您会发现这一点,祝你好运! - shafeen
显示剩余2条评论

2

Ignore函数用于跳过输入流中的字符。 Ignore文件与文件istream相关联。 考虑下面的函数 例如:cin.ignore(120,'/n'); 此特定函数将跳过下一个120个输入字符或跳过字符,直到读取换行符。


0
正如其他用户所指出的那样,这是因为可能存在空格或换行符。
考虑以下代码,它可以从给定的字符串中删除所有重复的字符。
#include <bits/stdc++.h>
using namespace std;

int main() {
    int t;
    cin>>t;
    cin.ignore(); //Notice that this cin.ignore() is really crucial for any extra whitespace or newline character
    while(t--){
        vector<int> v(256,0);
        string s;
        getline(cin,s);
        string s2;
        for(int i=0;i<s.size();i++){
            if (v[s[i]]) continue;
            else{
                s2.push_back(s[i]);
                v[s[i]]++;
            }
        }
        cout<<s2<<endl;
    }
    return 0;
}

那么,你明白它会忽略那些不需要的输入并完成工作。


-3

在C ++中,与其在cin>>语句后使用cin.ignore(),不如使用scanf(" %[^\n]",str)更好。要实现这一点,首先必须包含< cstdio >头文件。


2
不是这样的。scanf<cstdio>头文件的一部分,它不是C++中获取用户输入的惯用方式。 - Daniel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接