C++ 为什么循环条件(即,`while(!stream.eof())`)中的iostream::eof被认为是错误的?
我刚刚在回答中发现一条评论说,在循环条件中使用iostream::eof几乎肯定是错误的。我通常使用whilecin>>n之类的东西——我猜这会隐式地检查EOF 为什么检查eof显式使用while!怎么了 它与使用scanf有什么不同=我经常使用的C语言中的EOF没有问题?因为iostream::EOF只有在读取流的结尾后才会返回true。它并不表示下一次读取将是流的结束 考虑这一点,并假设下一次读取将在流的末尾:C++ 为什么循环条件(即,`while(!stream.eof())`)中的iostream::eof被认为是错误的?,c++,iostream,c++-faq,C++,Iostream,C++ Faq,我刚刚在回答中发现一条评论说,在循环条件中使用iostream::eof几乎肯定是错误的。我通常使用whilecin>>n之类的东西——我猜这会隐式地检查EOF 为什么检查eof显式使用while!怎么了 它与使用scanf有什么不同=我经常使用的C语言中的EOF没有问题?因为iostream::EOF只有在读取流的结尾后才会返回true。它并不表示下一次读取将是流的结束 考虑这一点,并假设下一次读取将在流的末尾: while(!inStream.eof()){ int data; //
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
而且和
if(!(inStream >> data).eof())
if(!inStream.eof())
inFile >> data
因为如果程序员不在Stream>>n期间编写,他们可能会编写以下内容:
while(!stream.eof())
{
stream >> n;
//some work on n;
}
这里的问题是,在没有首先检查流读取是否成功的情况下,您无法对n执行某些工作,因为如果读取不成功,您对n执行的某些工作将产生不希望的结果
关键在于,eofbit、badbit或failbit是在尝试从流中读取后设置的。因此,如果stream>>n失败,则会立即设置eofbit、badbit或failbit,因此,如果在stream>>n期间写入,则更为惯用,因为如果从流读取时出现一些故障,则返回的对象流将转换为false,从而循环停止。如果读取成功并且循环继续,它将转换为true。底线顶部:通过正确处理空白,以下是如何使用eof,甚至在错误检查中比失败更可靠:
while( !(in>>std::ws).eof() ) {
int data;
in >> data;
if ( in.fail() ) /* handle with break or throw */;
// now use data
}
感谢Tony D建议强调答案。请参见下面他的评论,以获取一个示例,说明为什么这一功能更强大
反对使用eof的主要论点似乎缺少了一个关于空白作用的重要微妙之处。我的主张是,显式检查eof不仅不总是错误的——这在本线程和类似SO线程中似乎是一个压倒一切的观点——而且通过正确处理空白,它提供了更干净、更可靠的错误处理,并且始终是正确的解决方案,尽管不一定是最简洁的
以下是建议的正确终止和读取顺序的总结:
int data;
while(in >> data) { /* ... */ }
// which is equivalent to
while( !(in >> data).fail() ) { /* ... */ }
由于超出eof的读取尝试而导致的故障被视为终止条件。这意味着没有简单的方法来区分成功的流和由于eof以外的原因而真正失败的流。采取以下措施:
1 2 3 4 5
12345
A.
而在>>数据终止时,所有三个输入都有一个设置的故障位。在第一个和第三个中,还设置了eofbit。因此,通过循环,我们需要非常丑陋的额外逻辑来区分正确的输入1和不正确的输入2和3
鉴于,采取以下措施:
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 whitespace with std::ws
if ( in.fail() ) /* handle with break or throw */;
// now use data
}
设置eofbit时,ws跳过流中任何可能的零或更多尾随空间,而不是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不仅可行而且没有错误,而且允许在范围内对数据进行本地化,并提供了错误检查与日常业务的更清晰分离。尽管如此,但是!fail无疑是一个更常见、更简洁的习惯用法,在每种读取类型的简单单数据场景中可能更可取 其他答案解释了为什么while中的逻辑是错误的!stream.eof以及如何修复它。我想关注一些不同的东西: 为什么显式使用iostream::eof检查eof是错误的 一般来说,只检查eof是错误的,因为流提取>>可能会失败,而不会到达文件的末尾。如果您有,例如int n;cin>>n;并且流包含hello,那么h不是一个有效数字,因此提取将失败,而不会到达输入的末尾 此问题,再加上在尝试读取流状态之前检查流状态的一般逻辑错误,这意味着对于N个输入项,循环将运行N+1次,导致以下症状: 如果流为空,循环将运行一次。>>将失败—没有要读取的输入,所有本应由stream>>x设置的变量实际上都未初始化。这导致垃圾数据被处理,这可能表现为无意义的结果,通常是巨大的数字 如果您的标准库符合C+ +11,现在情况有点不同了:失败的>>现在将数值变量设置为0,而不是将它们除字符外未初始化 如果流不是空的,循环将在最后一个有效输入之后再次运行。因为在上一次迭代中,所有>>操作都失败了,所以变量可能会保留上一次迭代中的值。这可能表现为最后一行打印两次或最后一条输入记录处理两次 这应该与C++11(见上文)有所不同:现在您得到的是一个0的幻影记录,而不是重复的最后一行 如果流包含格式不正确的数据,但您只检查.eof,那么您将得到一个无限循环。>>将无法从流中提取任何数据,因此循环将原地旋转而不会到达终点
总而言之:解决方案是测试>>操作本身的成功,而不是使用单独的.eof方法:while stream>>n>>m{…},就像在C中测试scanf调用本身的成功一样:while scanf%d%d,&n,&m==2{…}。循环中的iostream::eof被认为是错误的,因为我们还没有达到eof。因此,这并不意味着下一次读取将成功 我将用两个示例代码解释我的陈述,这肯定会帮助您更好地理解这个概念。比如说,当我们想用C++中的文件流读取文件时。当我们使用循环写入文件时,如果我们使用stream.eof检查文件的结尾,我们实际上是在检查文件是否已到达结尾 示例代码 当我们在循环中直接使用流时,我们不会再次检查条件 示例代码
扫描…!=EOF在C中也不起作用,因为scanf返回成功解析和分配的字段数。正确的条件是扫描…
a适用于所有空白流。您刚才说的是,格式错误的流可以被识别为!eof&失败通过循环。在有些情况下,我们不能依靠这一点。见上述评论。尽管如此,我并不建议eof支票作为更好的选择。我只是说,这是一种可能的,在某些情况下更合适的方法,而不是最肯定的错误!作为一个例子,请考虑如何检查错误,其中数据是一个重载操作符>一次读取多个字段的结构——一个更简单的案例支持您的观点是流> MyItIt,其中流包含例如:EFOBIT和FROBIT。这比operator>>场景更糟糕,在operator>>场景中,用户提供的重载至少可以选择在返回帮助支持之前清除eofbit,而s>>x使用。更一般地说,这个答案可能需要一个清理-只有在最后一段时间!在>>中,ws.eof通常是健壮的,它被隐藏在末尾。除了在未定义的n值上进行工作时所提到的不希望出现的结果外,如果失败的流操作不消耗任何输入,程序也可能陷入无限循环。这是最准确的答案,尽管从c++11开始,我不相信这些变量是未初始化的