为什么循环条件内的 iostream::eof (即 `while (!stream.eof())`)被认为是错误的?
我刚刚在 这个答案说在循环条件中使用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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
因为
iostream::eof
只会在读取流末尾后返回true
。它不表明下一次读取将是流的末尾。考虑这一点(并假设下一次阅读将在流的末尾):
反对这一点:
关于你的第二个问题:因为
与相同
并且不相同
Because
iostream::eof
will only returntrue
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):
Against this:
And on your second question: Because
is the same as
and not the same as
底线顶部:通过正确处理空白,以下是如何使用
eof
(甚至比fail()
更可靠) code> 进行错误检查):(感谢 Tony D 提出突出显示答案的建议。请参阅下面他的评论,了解为什么这更稳健的示例。)
反对使用
eof 的主要论点()
似乎遗漏了关于空白的作用的一个重要的微妙之处。我的建议是,显式检查eof()
不仅不会“总是错误”——这似乎是这个问题和类似的 StackOverflow 问题中的压倒一切的观点——,但通过正确处理空白,它提供了更清晰、更可靠的错误处理,并且是始终正确的解决方案(尽管不一定是最简洁的)。总结一下所建议的“正确”终止和读取顺序如下:
由于超出 eof 的读取尝试而导致的失败被视为终止条件。这意味着没有简单的方法来区分成功的流和因 eof 以外的原因而真正失败的流。采用以下流:
1 2 3 4 5
1 2 a 3 4 5
a
while(in>>data)
对于所有三个输入以设置failbit
终止。在第一个和第三个中,还设置了eofbit
。因此,在循环之后,需要非常丑陋的额外逻辑来区分正确的输入(第一个)和不正确的输入(第二个和第三个)。然而,采用以下内容:
这里,
in.fail()
验证只要有内容可供读取,它就是正确的。它的目的不仅仅是一个while循环终止符。到目前为止一切都很好,但是如果流中存在尾随空格会发生什么 - 听起来像是反对
eof()
作为终止符的主要问题?我们不需要放弃我们的错误处理;只是吃掉空白:
std::ws
在设置eofbit
时跳过流中任何潜在的(零个或多个)尾随空格,并且不是失败位
。因此,只要至少有一个数据需要读取,in.fail()
就会按预期工作。如果全空白流也可以接受,那么正确的形式是:总结: 正确构造的
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 thanfail()
for error checking):(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, checkingeof()
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:
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 setfailbit
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:
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:
std::ws
skips any potential (zero or more) trailing space in the stream while setting theeofbit
, and not thefailbit
. 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: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.因为如果程序员不写
while(stream >> n)
,他们可能会这样写:这里的问题是,你不能在没有先对 n 进行
some work
检查流读取是否成功,因为如果不成功,您对 n 的一些工作将产生不需要的结果。重点是,
eofbit
、badbit
或failbit
在尝试从流中读取之后被设置。< /strong> 所以如果stream >> n
失败,然后立即设置eofbit
、badbit
或failbit
,因此如果您编写while 则更惯用(stream >> n)
,因为如果从流中读取失败,则返回的对象stream
会转换为false
,从而导致循环停止。如果读取成功并且循环继续,它会转换为true
。Because if programmers don't write
while(stream >> n)
, they possibly write this: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, yoursome work on n
would produce undesired result.The whole point is that,
eofbit
,badbit
, orfailbit
are set after an attempt is made to read from the stream. So ifstream >> n
fails, theneofbit
,badbit
, orfailbit
is set immediately, so its more idiomatic if you writewhile (stream >> n)
, because the returned objectstream
converts tofalse
if there was some failure in reading from the stream and consequently the loop stops. And it converts totrue
if the read was successful and the loop continues.其他答案已经解释了为什么 while (!stream.eof()) 中的逻辑错误以及如何修复它。我想专注于一些不同的事情:
一般来说,仅检查
eof
是错误的,因为流提取 (>>
) 可能会在未到达文件末尾的情况下失败。如果你有例如int n;辛>> n;
并且流包含hello
,则h
不是有效数字,因此提取将在未到达输入末尾的情况下失败。此问题与尝试读取流之前检查流状态的一般逻辑错误相结合,这意味着对于 N 个输入项,循环将运行 N+1 次,导致以下症状:
>>
将失败(没有要读取的输入),并且所有应该设置的变量(通过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: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 containshello
, thenh
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 (bystream >> 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 to0
instead of leaving them uninitialized (except forchar
s).)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 thescanf
call itself:while (scanf("%d%d", &n, &m) == 2) { ... }
.需要记住的重要一点是,
inFile.eof()
在尝试读取之后之前不会变为true
失败,因为您已到达文件末尾。因此,在此示例中,您将收到错误。使这个循环正确的方法是将读取和检查合并到一个操作中,就像这样
按照惯例,operator>> 返回我们从中读取的流,并且对流进行布尔测试返回< code>false 当流失败时(例如到达文件末尾)。
所以这给了我们正确的顺序:
如果您碰巧遇到一些其他问题这会阻止您正确读取文件,因此您将无法访问
eof()
。例如,让我们看一下这样的内容让我们通过一个示例来跟踪上述代码的工作过程
'1', '2', '3', 'a', ' b'
。a
。a
提取为 int 时,将会失败。清除
该流,否则所有读取该流的尝试都将失败。false
,因为我们还没有到达文件末尾,因为仍然有a
等待读取。但是,如果我们使用这样的循环,我们将得到所需的输出。
在这种情况下,不仅在文件结束的情况下,而且在转换失败的情况下,流都会转换为
false
,例如我们可以的a
t 读为整数。The important thing to remember is that
inFile.eof()
doesn’t becometrue
until after an attempted read fails, because you’ve reached the end of the file. So, in this example, you’ll get an error.The way to make this loop correct is to combine reading and checking into a single operation, like so
By convention,
operator>>
returns the stream we read from, and a Boolean test on a stream returnsfalse
when the stream fails (such as reaching end of file).So this gives us the correct sequence:
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 thisLet us trace through the working of the above code, with an example
'1', '2', '3', 'a', 'b'
.a
.a
as an int, it’ll fail.clear
the stream, all attempts at reading from it will fail.false
, because we’re not at the end of the file, because there’s stilla
waiting to be read.But, if we use a loop like this, we will get the required output.
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 thea
that we can’t read as an integer.