istream::ignore 是否丢弃超过 n 个字符?

Does istream::ignore discard more than n characters?

(这可能是 Why does std::basic_istream::ignore() extract more characters than specified? 的副本,但我的具体案例不处理 delim)

来自cppreference,istream::ignore的描述如下:

Extracts and discards characters from the input stream until and including delim.

ignore behaves as an UnformattedInputFunction. After constructing and checking the sentry object, it extracts characters from the stream and discards them until any one of the following conditions occurs:

  • count characters were extracted. This test is disabled in the special case when count equals std::numeric_limitsstd::streamsize::max()
  • end of file conditions occurs in the input sequence, in which case the function calls setstate(eofbit)
  • the next available character c in the input sequence is delim, as determined by Traits::eq_int_type(Traits::to_int_type(c), delim). The delimiter character is extracted and discarded. This test is disabled if delim is Traits::eof()

但是,假设我有以下程序:

#include <iostream>
int main(void) {
  int x;
  char p;
  if (std::cin >> x) {
    std::cout << x;
  } else {
    std::cin.clear();
    std::cin.ignore(2);
    std::cout <<   "________________";
    std::cin >> p;
    std::cout << p;
  
}

现在,假设我在程序启动时输入了类似 p 的内容。我希望 cin 到 'fail',然后 clear 被调用,ignore 从缓冲区中丢弃 2 个字符。所以留在缓冲区中的 'p' 和 '\n' 应该被丢弃。但是,程序在 ignore 被调用后仍然需要输入,所以实际上它只在我给它超过 2 个字符后才到达最后的 std::cin>>p 丢弃。

我的问题: 输入类似 'b' 的内容并在第一次输入后立即按 Enter(因此在字符被丢弃后 2,'p' 和 '\n')将 'b' 保留在缓冲区中并立即将其传递给cin,没有先打印消息。我怎样才能让它在丢弃两个字符然后调用 << 后立即打印消息?

您的问题与您的终端有关。当您按 ENTER 键时,您很可能会得到两个字符 -- '\r''\n'。因此,输入流中还剩下一个字符可供读取。将该行更改为:

std::cin.ignore(10, '\n'); // 10 is not magical. You may use any number > 2

查看您期望的行为。

在评论中来回反复(并自己重现问题)后,很明显问题在于:

  1. 您输入 p<Enter>,这是不可解析的
  2. 您尝试使用 ignore
  3. 恰好丢弃两个字符
  4. 你输出下划线
  5. 你提示下一个输入

但实际上事情似乎停在第 2 步,直到您给它更多的输入,下划线才出现。好吧,坏消息,你是对的,代码在 ignore 中的第 2 步阻塞。 ignore 正在阻塞等待输入 third 字符(真的,检查这两个字符之后是否是 EOF),根据规范,这显然是正确的我觉得呢?

这里的问题与the problem you linked是同一个基本问题,只是表现形式不同。当 ignore 因为读取了请求的字符数而终止时,它 总是 尝试再读取一个字符,因为它需要知道条件 2 是否也可能为真(它发生了读取最后一个字符以便它可以采取适当的操作,将 cin 置于 EOF 状态,否则将下一个字符留在缓冲区中以供下一次读取):

Effects: Behaves as an unformatted input function (as described above). After constructing a sentry object, extracts characters and discards them. Characters are extracted until any of the following occurs:

  • n != numeric_limits::max() (18.3.2) and n characters have been extracted so far
  • end-of-file occurs on the input sequence (in which case the function calls setstate(eofbit), which may throw ios_base::failure (27.5.5.4));
  • traits::eq_int_type(traits::to_int_type(c), delim) for the next available input character c (in which case c is extracted).

因为你没有给ignore提供一个结束字符,它正在寻找EOF,如果它在两个字符之后没有找到它,它必须再读一个看它是否出现在之后被忽略的字符(如果是,它将使 cin 处于 EOF 状态,否则,它看到的字符将是您阅读的下一个字符)。

这里最简单的解决方案是不要尝试专门丢弃两个字符。您想通过换行符摆脱所有内容,请使用:

std::cin.ignore(std::numeric_limits<std::stringsize>::max(), '\n');

而不是std::cin.ignore(2);;它将读取任何和所有字符,直到换行符(或 EOF),消耗换行符,并且它永远不会过度读取(从某种意义上说,它会一直持续到找到定界符或 EOF 为止,没有条件可以完成正在读取字符数,需要进一步查看)。

如果出于某种原因你想专门忽略两个字符(你怎么知道他们输入了 p<Enter> 而不是 pabc<Enter>?),只需调用 .get() 一对次或 .read(&two_byte_buffer, 2) 之类的,因此您阅读原始字符时不可能 peek 超出它们。

郑重声明,这似乎有点偏离 cppreference 规范(可能是错误的);规范中的条件 2 未指定它需要在读取计数字符后验证它是否处于 EOF,并且 cppreference 声称如果“分隔符”是默认值 [=25],则明确不检查条件 3(需要查看) =].但是在您的其他答案中找到的规范引述不包括关于条件 3 不申请 Traits::eof() 和条件 2 可能 允许检查您是否处于 EOF 的行,最终会出现观察到的行为。

在缓冲区中传递准确的字符数就可以了:

std::cin.ignore(std::cin.rdbuf()->in_avail());