吃完 EOF 后重用 std::cin

2022-01-04 00:00:00 stdin io c++ cin istream

unix 命令 wc 有这个功能:

The unix command wc has this functionality:

$ 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

每个 表示将 EOF 输入标准输入的 键序列.然后 wc 能够获取这个 EOF.

Each <EOF> indicates a <C-d> key sequence that enters an EOF into stdin. wc is then able to pick up this EOF.

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

I'm trying to implement this in C++. A common suggestion is combination of clear() and 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),它也不起作用.

I've also tried std::cin.peekg(std::cin.beg), which also does not work.

推荐答案

在 Davis 的耐心帮助下,我学会了用 Ctrl+D 完成的输入与输入的区别和一份我不知道的 here 文件.(我向戴维斯・赫林致敬.)

With the patient help of Davis, I learned the difference between typed input finished with Ctrl+D and a here document which I was not aware of. (My tribute to Davis Herring.)

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

Once, I got it the rest in C++ was quite simple like shown in my 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 << "'!
";
    return 0;
  }
  for (std::string buffer; std::getline(in, buffer); ++nLines);
  std::cout << "File: '" << fileName << "', " << nLines << " counted.
";
  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.
";
  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 再次读取.

So, it's indeed the trick with std::cin.clear() which resets the EOF flag in input stream and makes it possible to read again from the /dev/stdin.

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

In the case of OP, the std::cin.ignore() after std::cin.clear() is IMHO wrong. It discards the first character of the re-enabled standard input which makes the following processing wrong (by not counting the first character).

Davis(再次)提供了一个简短但显而易见的解释,我用我的话来说:

Davis (again) prodived a short but obvious explanation which I give in my words:

使用CtrlD,标准输入在下一次尝试读取时收到一个EOF;并在其内部标志中记住它.然而,该标志可以被重置.如果没有任何进一步的输入,下一次读取尝试将失败,否则,输入可以继续.

With CtrlD, the standard input receives an EOF in the next attempt to read; and memorizes it in its internal flags. Nevertheless, the flag can be reset. If there isn't any further input the next read attempt would fail but otherwise, the input can just be continued.

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

May be, it's worth to emphasize the internal EOF flag in std::ios. Without std::cin.clear(), the read attempt would fail even when more input is available. As long as the internal std::stream flags doesn't result in good state, no read attempt will be performed on lower level even though it might succeed.

相关文章