为什么循环条件内的 iostream::eof (即 `while (!stream.eof())`)被认为是错误的?

发布于 2024-10-31 17:42:54 字数 420 浏览 6 评论 0原文

我刚刚在 这个答案说在循环条件中使用iostream::eof“几乎肯定是错误的”。我通常使用类似 while(cin>>n) 的东西 - 我猜它会隐式检查 EOF。

为什么使用 while (!cin.eof()) 显式检查 eof 是错误的?

它与在 C 中使用 scanf("...",...)!=EOF (我经常使用它没有任何问题)有什么不同?

I just found a comment in this answer saying that using iostream::eof in a loop condition is "almost certainly wrong". I generally use something like while(cin>>n) - which I guess implicitly checks for EOF.

Why is checking for eof explicitly using while (!cin.eof()) wrong?

How is it different from using scanf("...",...)!=EOF in C (which I often use with no problems)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

无人接听 2024-11-07 17:42:54

因为 iostream::eof 只会在读取流末尾后返回 true 。它表明下一次读取将是流的末尾。

考虑这一点(并假设下一次阅读将在流的末尾):

while(!inStream.eof()){
  int data;
  // yay, not end of stream yet, now read ...
  inStream >> data;
  // oh crap, now we read the end and *only* now the eof bit will be set (as well as the fail bit)
  // do stuff with (now uninitialized) data
}

反对这一点:

int data;
while(inStream >> data){
  // when we land here, we can be sure that the read was successful.
  // if it wasn't, the returned stream from operator>> would be converted to false
  // and the loop wouldn't even be entered
  // do stuff with correctly initialized data (hopefully)
}

关于你的第二个问题:因为

if(scanf("...",...)!=EOF)

与相同

if(!(inStream >> data).eof())

并且相同

if(!inStream.eof())
    inFile >> data

Because iostream::eof will only return true after reading the end of the stream. It does not indicate, that the next read will be the end of the stream.

Consider this (and assume then next read will be at the end of the stream):

while(!inStream.eof()){
  int data;
  // yay, not end of stream yet, now read ...
  inStream >> data;
  // oh crap, now we read the end and *only* now the eof bit will be set (as well as the fail bit)
  // do stuff with (now uninitialized) data
}

Against this:

int data;
while(inStream >> data){
  // when we land here, we can be sure that the read was successful.
  // if it wasn't, the returned stream from operator>> would be converted to false
  // and the loop wouldn't even be entered
  // do stuff with correctly initialized data (hopefully)
}

And on your second question: Because

if(scanf("...",...)!=EOF)

is the same as

if(!(inStream >> data).eof())

and not the same as

if(!inStream.eof())
    inFile >> data
揽清风入怀 2024-11-07 17:42:54

底线顶部:通过正确处理空白,以下是如何使用eof(甚至比fail()更可靠) code> 进行错误检查):(

while( !(in>>std::ws).eof() ) {
   int data;
   in >> data;
   if ( in.fail() ) /* Handle with 'break' or 'throw' */;
   // Now use data
}

感谢 Tony D 提出突出显示答案的建议。请参阅下面他的评论,了解为什么这更稳健的示例。


反对使用 eof 的主要论点() 似乎遗漏了关于空白的作用的一个重要的微妙之处。我的建议是,显式检查 eof() 不仅不会“总是错误”——这似乎是这个问题和类似的 StackOverflow 问题中的压倒一切的观点——,但通过正确处理空白,它提供了更清晰、更可靠的错误处理,并且是始终正确的解决方案(尽管不一定是最简洁的)。

总结一下所建议的“正确”终止和读取顺序如下:

int data;
while(in >> data) {  /* ... */ }

// Which is equivalent to
while( !(in >> data).fail() )  {  /* ... */ }

由于超出 eof 的读取尝试而导致的失败被视为终止条件。这意味着没有简单的方法来区分成功的流和因 eof 以外的原因而真正失败的流。采用以下流:

  • 1 2 3 4 5
  • 1 2 a 3 4 5
  • a

while(in>>data) 对于所有三个输入以设置failbit 终止。在第一个和第三个中,还设置了eofbit。因此,在循环之后,需要非常丑陋的额外逻辑来区分正确的输入(第一个)和不正确的输入(第二个和第三个)。

然而,采用以下内容:

while( !in.eof() )
{
   int data;
   in >> data;
   if ( in.fail() ) /* Handle with break or throw */;
   // Now use data
}

这里,in.fail() 验证只要有内容可供读取,它就是正确的。它的目的不仅仅是一个while循环终止符。

到目前为止一切都很好,但是如果流中存在尾随空格会发生什么 - 听起来像是反对 eof() 作为终止符的主要问题?

我们不需要放弃我们的错误处理;只是吃掉空白:

while( !in.eof() )
{
   int data;
   in >> data >> ws; // Eat white space with 'std::ws'
   if ( in.fail() ) /* Handle with 'break' or 'throw' */;
   // Now use data
}

std::ws 在设置 eofbit 时跳过流中任何潜在的(零个或多个)尾随空格,并且不是失败位。因此,只要至少有一个数据需要读取,in.fail() 就会按预期工作。如果全空白流也可以接受,那么正确的形式是:

while( !(in>>ws).eof() )
{
   int data;
   in >> data;
   if ( in.fail() ) /* Handle with 'break' or 'throw' */;
   /* This will never fire if the eof is reached cleanly */
   // Now use data
}

总结: 正确构造的 while(!eof) 不仅是可能的,也不是错误的,而且它允许数据在范围内本地化,并提供错误检查与正常业务的更清晰的分离。话虽如此,while(!fail) 无疑是一种更常见、更简洁的习惯用法,并且在简单(每种读取类型单个数据)场景中可能是首选。

Bottom-line top: With proper handling of white space, the following is how eof can be used (and even, be more reliable than fail() for error checking):

while( !(in>>std::ws).eof() ) {
   int data;
   in >> data;
   if ( in.fail() ) /* Handle with 'break' or 'throw' */;
   // Now use data
}

(Thanks Tony D for the suggestion to highlight the answer. See his comment below for an example to why this is more robust.)


The main argument against using eof() seems to be missing an important subtlety about the role of white space. My proposition is that, checking eof() explicitly is not only not "always wrong"—which seems to be an overriding opinion in this and similar Stack Overflow questions—, but with proper handling of white-space, it provides for a cleaner and more reliable error handling, and is the always correct solution (although, not necessarily the tersest).

To summarize what is being suggested as the "proper" termination and read order is the following:

int data;
while(in >> data) {  /* ... */ }

// Which is equivalent to
while( !(in >> data).fail() )  {  /* ... */ }

The failure due to read attempt beyond eof is taken as the termination condition. This means is that there is no easy way to distinguish between a successful stream and one that really fails for reasons other than eof. Take the following streams:

  • 1 2 3 4 5<eof>
  • 1 2 a 3 4 5<eof>
  • a<eof>

while(in>>data) terminates with a set failbit for all three input. In the first and third, eofbit is also set. So past the loop one needs very ugly extra logic to distinguish a proper input (first) from improper ones (second and third).

Whereas, take the following:

while( !in.eof() )
{
   int data;
   in >> data;
   if ( in.fail() ) /* Handle with break or throw */;
   // Now use data
}

Here, in.fail() verifies that as long as there is something to read, it is the correct one. It's purpose is not a mere while loop terminator.

So far so good, but what happens if there is trailing space in the stream—what sounds like the major concern against eof() as terminator?

We don't need to surrender our error handling; just eat up the white-space:

while( !in.eof() )
{
   int data;
   in >> data >> ws; // Eat white space with 'std::ws'
   if ( in.fail() ) /* Handle with 'break' or 'throw' */;
   // Now use data
}

std::ws skips any potential (zero or more) trailing space in the stream while setting the eofbit, and not the failbit. So, in.fail() works as expected, as long as there is at least one data to read. If all-blank streams are also acceptable, then the correct form is:

while( !(in>>ws).eof() )
{
   int data;
   in >> data;
   if ( in.fail() ) /* Handle with 'break' or 'throw' */;
   /* This will never fire if the eof is reached cleanly */
   // Now use data
}

Summary: A properly constructed while(!eof) is not only possible and not wrong, but it allows data to be localized within scope and provides a cleaner separation of error checking from business as usual. That being said, while(!fail) is inarguably a more common and terse idiom, and may be preferred in simple (single data per read type of) scenarios.

农村范ル 2024-11-07 17:42:54

因为如果程序员不写 while(stream >> n),他们可能会这样写:

while(!stream.eof())
{
    stream >> n;
    //some work on n;
}

这里的问题是,你不能在没有先对 n 进行some work检查流读取是否成功,因为如果不成功,您对 n 的一些工作将产生不需要的结果。

重点是,eofbitbadbitfailbit尝试从流中读取之后被设置。< /strong> 所以如果 stream >> n 失败,然后立即设置 eofbitbadbitfailbit,因此如果您编写 while 则更惯用(stream >> n),因为如果从流中读取失败,则返回的对象 stream 会转换为 false,从而导致循环停止。如果读取成功并且循环继续,它会转换为 true

Because if programmers don't write while(stream >> n), they possibly write this:

while(!stream.eof())
{
    stream >> n;
    //some work on n;
}

Here the problem is, you cannot do some work on n without first checking if the stream read was successful, because if it was unsuccessful, your some work on n would produce undesired result.

The whole point is that, eofbit, badbit, or failbit are set after an attempt is made to read from the stream. So if stream >> n fails, then eofbit, badbit, or failbit is set immediately, so its more idiomatic if you write while (stream >> n), because the returned object stream converts to false if there was some failure in reading from the stream and consequently the loop stops. And it converts to true if the read was successful and the loop continues.

惯饮孤独 2024-11-07 17:42:54

其他答案已经解释了为什么 while (!stream.eof()) 中的逻辑错误以及如何修复它。我想专注于一些不同的事情:

为什么使用 iostream::eof 显式检查 eof 是错误的?

一般来说,仅检查 eof 是错误的,因为流提取 (>>) 可能会在未到达文件末尾的情况下失败。如果你有例如 int n;辛>> n; 并且流包含 hello,则 h 不是有效数字,因此提取将在未到达输入末尾的情况下失败。

此问题与尝试读取流之前检查流状态的一般逻辑错误相结合,这意味着对于 N 个输入项,循环将运行 N+1 次,导致以下症状:

  • < p>如果流为空,则循环将运行一次。 >> 将失败(没有要读取的输入),并且所有应该设置的变量(通过 stream >> x)实际上都未初始化。这会导致处理垃圾数据,这可能表现为无意义的结果(通常是巨大的数字)。

    (如果您的标准库符合 C++11,现在情况有点不同:失败的 >> 现在将数值变量设置为 0 而不是让它们处于未初始化状态(char 除外)。)

  • 如果流不为空,则循环将在最后一次有效输入后再次运行。由于在最后一次迭代中,所有 >> 操作都失败,因此变量可能会保留上一次迭代的值。这可以表现为“最后一行被打印两次”或“最后一个输入记录被处理两次”。

    (自 C++11 以来,这应该表现得有点不同(见上文):现在您会得到一个由零组成的“幻象记录”,而不是重复的最后一行。)

  • 如果流包含格式错误的数据,但您只检查 < code>.eof,你最终会陷入无限循环。 >> 将无法从流中提取任何数据,因此循环会原地旋转而不会到达末尾。


回顾一下:解决方案是测试 >> 操作本身是否成功,而不是使用单独的 .eof() 方法: while (stream >> n >> m) { ... },就像在 C 中测试 scanf 调用本身是否成功一样:while (scanf(" %d%d", &n, &m) == 2) { ... }

The other answers have explained why the logic is wrong in while (!stream.eof()) and how to fix it. I want to focus on something different:

why is checking for eof explicitly using iostream::eof wrong?

In general terms, checking for eof only is wrong because stream extraction (>>) can fail without hitting the end of the file. If you have e.g. int n; cin >> n; and the stream contains hello, then h is not a valid digit, so extraction will fail without reaching the end of the input.

This issue, combined with the general logic error of checking the stream state before attempting to read from it, which means for N input items the loop will run N+1 times, leads to the following symptoms:

  • If the stream is empty, the loop will run once. >> will fail (there is no input to be read) and all variables that were supposed to be set (by stream >> x) are actually uninitialized. This leads to garbage data being processed, which can manifest as nonsensical results (often huge numbers).

    (If your standard library conforms to C++11, things are a bit different now: A failed >> now sets numeric variables to 0 instead of leaving them uninitialized (except for chars).)

  • If the stream is not empty, the loop will run again after the last valid input. Since in the last iteration all >> operations fail, variables are likely to keep their value from the previous iteration. This can manifest as "the last line is printed twice" or "the last input record is processed twice".

    (This should manifest a bit differently since C++11 (see above): Now you get a "phantom record" of zeroes instead of a repeated last line.)

  • If the stream contains malformed data but you only check for .eof, you end up with an infinite loop. >> will fail to extract any data from the stream, so the loop spins in place without ever reaching the end.


To recap: The solution is to test the success of the >> operation itself, not to use a separate .eof() method: while (stream >> n >> m) { ... }, just as in C you test the success of the scanf call itself: while (scanf("%d%d", &n, &m) == 2) { ... }.

玻璃人 2024-11-07 17:42:54

需要记住的重要一点是,inFile.eof() 在尝试读取之后之前不会变为true失败,因为您已到达文件末尾。因此,在此示例中,您将收到错误。

while (!inFile.eof()){
    inFile >> x;
        process(x);
}

使这个循环正确的方法是将读取和检查合并到一个操作中,就像这样

while (inFile >> x) 
    process(x); 

按照惯例,operator>> 返回我们从中读取的流,并且对流进行布尔测试返回< code>false 当流失败时(例如到达文件末尾)。

所以这给了我们正确的顺序:

  • 读取
  • 测试读取是否成功
  • 当且仅当测试成功时,处理我们读取的内容

如果您碰巧遇到一些其他问题这会阻止您正确读取文件,因此您将无法访问 eof() 。例如,让我们看一下这样的内容

int x; 
while (!inFile.eof()) { 
    inFile >> x; 
    process(x);
} 

让我们通过一个示例来跟踪上述代码的工作过程

  • 假设文件的内容是 '1', '2', '3', 'a', ' b'
  • 循环将正确读取 1、2 和 3。
  • 然后它会到达a
  • 当它尝试将 a 提取为 int 时,将会失败。
  • 该流现在处于失败状态,除非我们清除该流,否则所有读取该流的尝试都将失败。
  • 但是,当我们测试 eof() 时,它将返回 false,因为我们还没有到达文件末尾,因为仍然有 a 等待读取。
  • 该循环将继续尝试从文件中读取数据,但每次都会失败,因此它永远不会到达文件末尾。
  • 因此,上面的循环将永远运行。

但是,如果我们使用这样的循环,我们将得到所需的输出。

while (inFile >> x)
    process(x);

在这种情况下,不仅在文件结束的情况下,而且在转换失败的情况下,流都会转换为 false ,例如我们可以的 a t 读为整数。

The important thing to remember is that inFile.eof() doesn’t become true until after an attempted read fails, because you’ve reached the end of the file. So, in this example, you’ll get an error.

while (!inFile.eof()){
    inFile >> x;
        process(x);
}

The way to make this loop correct is to combine reading and checking into a single operation, like so

while (inFile >> x) 
    process(x); 

By convention, operator>> returns the stream we read from, and a Boolean test on a stream returns false when the stream fails (such as reaching end of file).

So this gives us the correct sequence:

  • read
  • test whether the read succeeds
  • if and only if the test succeeds, process what we’ve read

If you happen to encounter some other problem that prevents you from reading from the file correctly, you will not be able to reach eof() as such. For example, let’s look at something like this

int x; 
while (!inFile.eof()) { 
    inFile >> x; 
    process(x);
} 

Let us trace through the working of the above code, with an example

  • Assume the contents of the file are '1', '2', '3', 'a', 'b'.
  • The loop will read the 1, 2, and 3 correctly.
  • Then it’ll get to a.
  • When it tries to extract a as an int, it’ll fail.
  • The stream is now in a failed state, until or unless we clear the stream, all attempts at reading from it will fail.
  • But, when we test for eof(), it’ll return false, because we’re not at the end of the file, because there’s still a waiting to be read.
  • The loop will keep trying to read from the file, and fail every time, so it never reaches the end of the file.
  • So, the loop above will run forever.

But, if we use a loop like this, we will get the required output.

while (inFile >> x)
    process(x);

In this case, the stream will convert to false not only in case of end of file, but also in case of a failed conversion, such as the a that we can’t read as an integer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文