iostream::eof
几乎肯定是错误的。我通常使用类似于while(cin>>n)
的方式来读取输入,并且这种方式隐式地检查了EOF。
为什么明确检查EOF时使用while (!cin.eof())
是错误的呢?它与在C语言中使用scanf("...",...)!=EOF
有何不同(我通常都这样做而没有问题)?
因为 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
int
、std::string
或类似的内容时,在提取到结束符之前的最后一个内容并且提取操作到达了结尾时,EOF标志位会被设置,你无需再次读取。之所以在从文件中读取时不会设置该标志是因为在结尾处有一个额外的\n
字符。我已经在另一个答案中详细讨论了这个问题。读取char
是另一回事,因为它每次只提取一个字符,并且不会一直提取到结尾。 - Joseph Mansfield"Hello"
(没有尾随空格或 \n
),并且提取了一个 std::string
,它将从 H
到 o
提取字母,停止提取,然后 不 设置 EOF 位。实际上,它会设置 EOF 位,因为正是 EOF 停止了提取。只是希望为人们澄清这一点。 - Joseph Mansfield底线优先:通过正确处理空格,以下是如何使用eof
(甚至可以比fail()
更可靠地进行错误检查)的方法:
while( !(in>>std::ws).eof() ) {
int data;
in >> data;
if ( in.fail() ) /* Handle with 'break' or 'throw' */;
// Now use data
}
(感谢Tony D的建议突出回答。请参见他下面的评论,了解为什么这更加健壮。)
反对使用eof()
的主要论点似乎缺少有关空格作用的重要细微差别。我的观点是,明确检查eof()
不仅不总是错误的 - 这似乎是在此类Stack Overflow问题中的一个压倒性意见 - 而且通过正确处理空格,它提供了更清洁和可靠的错误处理,并且是始终正确的解决方案(尽管不一定是最简洁的解决方案)。
总结所建议的“适当”终止和读取顺序如下:
int data;
while(in >> data) { /* ... */ }
// Which is equivalent to
while( !(in >> data).fail() ) { /* ... */ }
由于超出文件末尾的读取尝试导致失败,这被视为终止条件。这意味着没有简单的方法来区分成功的流和真正因为eof以外的原因而失败的流。考虑以下流:
1 2 3 4 5<eof>
1 2 a 3 4 5<eof>
a<eof>
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
,而不是failbit
。 因此,只要有至少一个数据可读取,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)
无疑是更常见和简洁的习惯用法,在简单情况下(每次读取一个数据类型)可能更受欢迎。
eofbit
和 failbit
都被设置之外,在另一种情况下只有 failbit
被设置。在循环终止后,只需要检查一次这个情况,而不是在每次迭代之后都检查它。 它只会离开循环一次,因此您只需要检查它为什么仅离开循环一次。while (in >> data)
对所有空流都有效。 - Jonathan Wakely!eof & fail
来识别。但有些情况下不能依赖这种方法,详见上面的评论(http://goo.gl/9mXYX)。无论如何,我并不是建议将`eof`检查作为*始终更好*的选择。我只是在说,它是一种可能的且(在某些情况下更为合适的)方法,而不是像在 SO 中经常声称的“几乎肯定是错的!” - slystream >> my_int
,其中流包含例如“-”:设置了eofbit和failbit。这比operator>>的场景更糟糕,因为用户提供的重载至少可以在返回之前清除eofbit来支持while(s >> x)
用法。更普遍的情况是,此答案需要清理 - 仅最后的while( !(in>>ws).eof() )
通常是可靠的,并且它被埋藏在最后。 - Tony Delroya
无法转换为整数并且未被读取--它将留在输入中。 - Chris Dodd因为如果程序员不写 while(stream >> n)
,他们可能会写这样的代码:
while(!stream.eof())
{
stream >> n;
//some work on n;
}
问题在于,如果在检查流读取是否成功之前不先对 n
进行一些处理,那么如果读取失败,你的处理结果可能会出现意外。
关键在于,eofbit
、badbit
或者 failbit
是在尝试从流中读取数据后设置的。 因此,如果 stream >> n
失败,那么 eofbit
、badbit
或者 failbit
会立即被设置。因此,如果您写成 while (stream >> n)
,更符合习惯。因为返回的对象 stream
会在从流中读取失败时转换为 false
,从而停止循环。如果读取成功,则转换为 true
并继续循环。
n
上进行工作会产生“不良结果”之外,如果失败的流操作没有消耗任何输入,程序也可能陷入无限循环。 - mastovwhile (!stream.eof())
中的逻辑是错误的以及如何修复它。我想专注于不同的问题:为什么仅使用显式检查文件结束是错误的?
一般来说,仅检查eof
是错误的,因为流提取(>>
)在未到达文件结尾的情况下可能失败。如果您有例如int n; cin >> n;
并且流包含hello
,那么h
不是有效数字,因此提取将在未达到输入结尾时失败。如果流为空,则循环将运行一次。>>
将失败(没有输入可读取),所有应该被设置的变量(通过stream >> x
)实际上都没有被初始化。这会导致处理垃圾数据,可能表现为无意义的结果(通常是巨大的数字)。
(如果您的标准库符合C++11,现在情况有点不同:失败的>>
现在将数字变量设置为0
,而不是保留它们未初始化(除了char
)。)
如果流不为空,则在最后一个有效输入后,循环将再次运行。由于在最后一次迭代中所有的>>
操作都失败了,变量很可能会保持其来自上一次迭代的值。这可能表现为“最后一行被打印两次”或“最后一个输入记录被处理两次”。
(自C++11以来,这应该表现得有些不同(见上文):现在您会得到一个由零组成的“幻像记录”,而不是重复的最后一行。)
如果流包含格式不正确的数据,但只检查.eof
,则会陷入无限循环。>>
将无法从流中提取任何数据,因此循环会无限地旋转而永远不会到达结尾。
总之:解决方案是测试>>
操作本身的成功,而不是使用单独的.eof()
方法:while (stream >> n >> m) { ... }
,就像在C语言中测试scanf
调用本身的成功一样:while (scanf("%d%d", &n, &m) == 2) { ... }
。
重要的是要记住,inFile.eof()
直到尝试读取失败后才变为True
,因为你已经到达文件末尾。所以,在这个例子中,你会出现错误。
while (!inFile.eof()){
inFile >> x;
process(x);
}
while (inFile >> x)
process(x);
按照惯例,operator>>
返回我们读取的流以及流上的布尔测试。当流失败时(例如到达文件末尾)布尔测试返回 False
。
因此,这给我们提供了正确的顺序:
如果你遇到了其他一些阻止你正确读取文件的问题,你将无法像遇到文件结束符那样到达eof()
。例如,让我们看看下面的示例。
int x;
while (!inFile.eof()) {
inFile >> x;
process(x);
}
while (inFile >> x)
process(x);
while(!feof(file))
总是错误的?。因为标志位只有在到达EOF之后才会被设置。 - legends2k