提问人:MAK 提问时间:4/9/2011 最后编辑:melpomeneMAK 更新时间:1/30/2023 访问量:96353
为什么 iostream::eof 在循环条件(即 'while (!stream.eof())'))中被认为是错误的?
Why is iostream::eof inside a loop condition (i.e. `while (!stream.eof())`) considered wrong?
问:
我刚刚在这个答案中发现了一条评论,说在循环条件下使用“几乎肯定是错误的”。我通常使用类似的东西 - 我猜它隐式检查 EOF。iostream::eof
while(cin>>n)
为什么明确使用 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
\n
char
"Hello"
\n
std::string
H
o
// do stuff with (now uninitialized) data
自 C++11 起,情况不再如此,请参阅 stackoverflow.com/a/13379073/3002139
因为如果程序员不写,他们可能会写这个:while(stream >> n)
while(!stream.eof())
{
stream >> n;
//some work on n;
}
这里的问题是,你不能不首先检查流读取是否成功,因为如果它不成功,你将产生不希望的结果。some work on n
some work on n
关键是,在尝试从流中读取数据后设置、、或。因此,如果失败,则立即设置,或,因此,如果您编写,则更习惯使用,因为返回的对象将转换为从流中读取时出现一些故障,因此循环停止。如果读取成功并且循环继续,它将转换为。eofbit
badbit
failbit
stream >> n
eofbit
badbit
failbit
while (stream >> n)
stream
false
true
评论
n
底线顶部:通过正确处理空格,以下是如何使用(甚至比错误检查更可靠):eof
fail()
while( !(in>>std::ws).eof() ) {
int data;
in >> data;
if ( in.fail() ) /* Handle with 'break' or 'throw' */;
// Now use data
}
(感谢 Tony D 提出的建议,以突出答案。请参阅下面的评论,了解为什么这更健壮。)
反对使用的主要论点似乎忽略了关于空白作用的重要微妙之处。我的主张是,显式检查不仅不是“总是错误的”——这似乎是这个和类似的 Stack Overflow 问题中最重要的观点——而且通过正确处理空格,它提供了更干净、更可靠的错误处理,并且是始终正确的解决方案(尽管不一定是最简洁的)。eof()
eof()
总结一下建议的“正确”终止和读取顺序如下:
int data;
while(in >> data) { /* ... */ }
// Which is equivalent to
while( !(in >> data).fail() ) { /* ... */ }
由于读取尝试超出 eof 而导致的失败被视为终止条件。这意味着没有简单的方法来区分成功的流和由于 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
}
在这里,验证只要有东西要读,它就是正确的。它的目的不仅仅是一个while循环终结器。in.fail()
到目前为止一切顺利,但是如果流中有尾随空间会发生什么——这听起来像是终结者的主要问题?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
跳过流中任何潜在的(零个或多个)尾随空间,同时设置 ,而不是 failbit
。因此,只要至少有一个数据要读取,就可以按预期工作。如果全空白流也可以接受,那么正确的形式是: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)
评论
eofbit
failbit
failbit
while (in >> data)
!eof & fail
eof
stream >> my_int
eofbit
failbit
operator>>
eofbit
while (s >> x)
while( !(in>>ws).eof() )
a
其他答案已经解释了为什么逻辑是错误的以及如何解决它。我想专注于一些不同的东西:while (!stream.eof())
为什么明确使用错误的检查 EOF?
iostream::eof
一般而言,检查 only 是错误的,因为流提取 () 可能会在不到达文件末尾的情况下失败。如果您有例如 并且流包含 ,则不是有效数字,因此提取将失败,而不会到达输入的末尾。eof
>>
int n; cin >> n;
hello
h
此问题与在尝试读取流状态之前检查流状态的一般逻辑错误相结合,这意味着对于 N 个输入项,循环将运行 N+1 次,导致以下症状:
如果流为空,则循环将运行一次。 将失败(没有要读取的输入),并且所有应该设置(由 )的变量实际上都未初始化。这会导致垃圾数据被处理,这可能表现为无意义的结果(通常是巨大的数字)。
>>
stream >> x
(如果你的标准库符合 C++11,现在情况有点不同:失败现在将数值变量设置为,而不是使它们处于未初始化状态(s 除外)。
>>
0
char
如果流不为空,则循环将在最后一个有效输入后再次运行。由于在上一次迭代中,所有操作都失败了,因此变量可能会保留上一次迭代中的值。这可能表现为“最后一行打印两次”或“最后一个输入记录被处理两次”。
>>
(自 C++11 以来,这应该有点不同(见上文):现在你得到的是零的“幻像记录”,而不是重复的最后一行。
如果流包含格式不正确的数据,但您只检查 ,则最终会得到无限循环。 将无法从流中提取任何数据,因此循环在原地旋转而从未到达终点。
.eof
>>
回顾一下:解决方案是测试操作本身是否成功,而不是使用单独的方法:,就像在 C 中测试调用本身是否成功一样:。>>
.eof()
while (stream >> n >> m) { ... }
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);
}
让我们通过一个示例来追溯上述代码的工作
- 假设文件的内容是 。
'1', '2', '3', 'a', 'b'
- 循环将正确读取 1、2 和 3。
- 然后它会得到.
a
- 当它尝试提取为 int 时,它将失败。
a
- 流现在处于失败状态,直到或除非我们流,所有从中读取的尝试都将失败。
clear
- 但是,当我们测试 eof() 时,它会返回 ,因为我们不在文件的末尾,因为还有等待读取。
False
a
- 循环将不断尝试从文件中读取,并且每次都失败,因此它永远不会到达文件末尾。
- 因此,上面的循环将永远运行。
但是,如果我们使用这样的循环,我们将得到所需的输出。
while (inFile >> x)
process(x);
在这种情况不仅会在文件结束时转换为,而且在转换失败的情况下也会转换为,例如我们无法读取为整数。False
a
评论
True
False
上一个:C++ 将结构保存并加载到文件中
评论
scanf(...) != EOF
在 C 中也不起作用,因为返回成功解析和分配的字段数。正确的条件是 where 是格式字符串中的字段数。scanf
scanf(...) < n
n
EOF
EOF
.eof()
while(fail)