为什么将iostream :: eof放在循环条件(即`while(!stream.eof())`)内?

我刚刚在答案中找到一条评论,说在循环条件下使用iostream::eof是“几乎肯定是错误的”。 我通常使用while(cin>>n)类的东西-我猜它隐式地检查EOF。

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

与在C语言中使用scanf("...",...)!=EOF有什么不同(我经常会毫无问题地使用它)?


#1楼

底线顶部:在正确处理空白的情况下,以下是可以使用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()不仅不“ 总是错误 ”-在该线程和类似的SO线程中似乎是压倒一切的意见-而且通过正确处理空白,它可以提供更清洁的环境。和更可靠的错误处理,并且始终是正确的解决方案(尽管不一定是最麻烦的)。

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

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
}    

在这里, 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不是failbit同时, std::ws跳过流中任何潜在的(零个或多个)尾随空间。 因此,只要至少要读取一个数据, 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)无疑是一个更常见,更简洁的习惯用法,并且在简单(每种读取类型的单个数据)场景中可能是首选。


#2楼

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

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

一般而言, 检查eof是错误的,因为流提取( >> )可能会失败而不会到达文件末尾。 如果您有int n; cin >> n; int n; cin >> n; 并且流包含hello ,则h不是有效数字,因此提取操作将在不到达输入结尾的情况下失败。

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

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

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

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

    (这与C ++ 11(见上文)相比应该有所不同:现在,您将获得零的“幻像记录”,而不是重复的最后一行。)

  • 如果流包含格式错误的数据,但仅检查.eof ,则会导致无限循环。 >>将无法从流中提取任何数据,因此循环旋转到位而不到达终点。


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


#3楼

因为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

#4楼

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

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

这里的问题是,如果不首先检查流读取是否成功,就无法some work on n进行some work on n ,因为如果不成功,则some work on nsome work on n会产生不希望的结果。

整个问题是, 尝试从流中读取后设置 eofbitbadbitfailbit 因此,如果stream >> n失败,则会立即设置eofbitbadbitfailbit ,因此如果您while (stream >> n)编写while (stream >> n)更加习惯,因为如果从中读取数据失败,则返回的对象stream将转换为false 。流,因此循环停止。 如果读取成功并且循环继续,它将转换为true

发布了0 篇原创文章 · 获赞 0 · 访问量 2233

猜你喜欢

转载自blog.csdn.net/p15097962069/article/details/103892523
EOF