【问题标题】:Reuse std::cin after eating an EOF吃完 EOF 后重用 std::cin
【发布时间】:2018-09-14 02:22:10
【问题描述】:

unix 命令wc 有这个功能:

$ wc - - -
aaa bbb ccc<EOF>
0 3 11 -
aaa bbb ccc<EOF>
0 3 11 -
aaa bbb ccc<EOF>
0 3 11 -
0 9 33 total

每个&lt;EOF&gt; 表示一个&lt;C-d&gt; 键序列,它将EOF 输入到标准输入中。然后 wc 就可以接收到这个EOF

我正在尝试在 C++ 中实现它。一个常见的建议是clear()ignore() 的组合。

char c;
while (std::cin >> c) { ... }

std::cin.clear();
std::cin.ignore();

while (std::cin >> c) { /* never executed */ }

我也试过std::cin.peekg(std::cin.beg),也不管用。

【问题讨论】:

  • 一旦你EOF cin,我认为你不能把它带回来。如果可以的话,那就是通过一些神秘的黑魔法。最好为循环使用不同的终止条件,而不是杀死cin
  • 这很奇怪。您可以在什么操作系统和外壳上执行此操作。我尝试使用wc - - &lt;&lt;EOF 进行复制并输入a b cEOF。我得到了1 3 6 -0 0 0 -1 3 6 total。 (我在 Windows 10 中使用 cygwin64 并使用 bash。)
  • 顺便说一句。在尝试复制之前,我查看了wc 的一些来源,例如wc.c 找到类似“诀窍”的东西,但我找不到任何可疑或令人惊讶的东西。 (而且,因此我想知道我是否还有其他东西......)
  • @Scheff 我可以在 Windows 和 Arch Linux 的 Ubuntu 子系统上重现这个。
  • @Scheff:我想在回答之前测试一些东西,但我可以解释说,here-document (&lt;&lt;EOF) 的行为是完全不同的,因为它没有t 涉及终端。

标签: c++ io stdin cin istream


【解决方案1】:

在 Davis 的耐心帮助下,我了解了以 Ctrl+D 完成的键入输入与我不知道的 here 文档之间的区别。 (我向戴维斯·赫林致敬。)

有一次,我得到了它,C++ 中的其余部分非常简单,就像我的MCVE 中所示。

line-count.cc:

#include <fstream>
#include <iostream>
#include <string>

unsigned process(const std::string &fileName, std::istream &in)
{
  unsigned nLines = 0;
  if (in.bad()) {
    std::cerr << "ERROR: Cannot open '" << fileName << "'!\n";
    return 0;
  }
  for (std::string buffer; std::getline(in, buffer); ++nLines);
  std::cout << "File: '" << fileName << "', " << nLines << " counted.\n";
  return nLines;
}

int main(int argc, char **argv)
{
  unsigned nLines = 0;
  for (int i = 1; i < argc; ++i) {
    const std::string arg = argv[i];
    if (arg == "-") {
      nLines += process(arg, std::cin);
      std::cin.clear();
    } else {
      std::ifstream fIn(arg.c_str());
      nLines += process(arg, fIn);
    }
  }
  std::cout << "Total: " << nLines << " counted.\n";
  return 0;
}

cygwin64中编译测试:

$ g++ -std=c++11 -o line-count line-count.cc

$ ./line-count line-count.cc - line-count.cc -
File: 'line-count.cc', 32 counted.
1
2
3
File: '-', 3 counted.
File: 'line-count.cc', 32 counted.
1
2
3
File: '-', 3 counted.
Total: 70 counted.

$

所以,std::cin.clear() 的诀窍确实是重置输入流中的 EOF 标志,并可以再次从 /dev/stdin 读取。

在 OP 的情况下,std::cin.clear() 之后的 std::cin.ignore() 恕我直言是错误的。它会丢弃重新启用的标准输入的第一个字符,这会导致以下处理错误(不计算第一个字符)。

戴维斯(再次)提供了一个简短但显而易见的解释,我用我的话给出了:

使用 CtrlD,标准输入会在下一次尝试读取时收到 EOF;并将其存储在其内部标志中。不过,可以重置标志。如果没有任何进一步的输入,则下一次读取尝试将失败,否则,可以继续输入。

可能,值得强调std::ios 中的内部EOF 标志。如果没有std::cin.clear(),即使有更多输入可用,读取尝试也会失败。只要内部std::stream 标志没有导致良好的状态,即使可能成功,也不会在较低级别执行读取尝试。

【讨论】:

  • clear重新打开标准输入;它只是忘记了它看到的 EOF,以便下一次读取再次咨询内核。内核只为每个 ^D 提供一个 EOF(如果最后一个字符既不是 ^D 也不是换行符,则根本没有)。
  • @DavisHerring 我同意。我根据您的评论稍微修正了文字的底部。
猜你喜欢
  • 2017-08-29
  • 1970-01-01
  • 2020-04-02
  • 1970-01-01
  • 1970-01-01
  • 2011-06-12
  • 2016-01-19
  • 1970-01-01
相关资源
最近更新 更多