İostream :: eof bir döngü koşulu içinde (yani, `while (! Stream.eof ())`) neden yanlış kabul edilir?

Apr 09 2011

Bu yanıtta iostream::eofbir döngü koşulunun kullanılmasının "neredeyse kesinlikle yanlış" olduğunu söyleyen bir yorum buldum . Genelde şöyle bir şey kullanıyorum while(cin>>n)- ki sanırım dolaylı olarak EOF'yi kontrol ediyor.

Neden eof kontrolünü açıkça while (!cin.eof())yanlış kullanıyor ?

scanf("...",...)!=EOFC'de kullanmaktan ne kadar farklıdır (genellikle problemsiz kullanırım)?

Yanıtlar

556 Xeo Apr 09 2011 at 19:58

Çünkü akışın sonunu okuduktan sonraiostream::eof geri dönecektir . O mu değil sonraki okuma akışının sonu olacağını belirtmek.true

Bunu düşünün (ve bir sonraki okumanın akışın sonunda olacağını varsayın):

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
}

Buna karşı:

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)
}

Ve ikinci sorunuzda: Çünkü

if(scanf("...",...)!=EOF)

aynıdır

if(!(inStream >> data).eof())

ve aynı değil

if(!inStream.eof())
    inFile >> data
104 sly Nov 24 2012 at 06:30

Alt satır üst: Beyaz boşluğun düzgün bir şekilde ele alınmasıyla, aşağıdakilerin nasıl eofkullanılabileceği (ve hatta, fail()hata kontrolünden daha güvenilir olabilir ):

while( !(in>>std::ws).eof() ) {  
   int data;
   in >> data;
   if ( in.fail() ) /* handle with break or throw */; 
   // now use data
}    

( Cevabı vurgulama önerisi için Tony D'ye teşekkürler. Bunun neden daha sağlam olduğuna dair bir örnek için aşağıdaki yorumuna bakın. )


Kullanıma karşı ana argüman eof(), beyaz boşluğun rolü hakkında önemli bir incelikten yoksun görünüyor. Benim önerim, eof()açıkça kontrol etmenin sadece " her zaman yanlış " değil - ki bu ve benzeri SO konu başlıklarında baskın bir görüş gibi görünüyor -, aynı zamanda beyaz boşluğun uygun şekilde ele alınmasıyla, daha temiz ve daha güvenilir bir hata işleme ve her zaman doğru çözümdür (her ne kadar tam anlamıyla olmasa da).

Neyin "uygun" sonlandırma ve okuma sırası olarak önerildiğini özetlemek gerekirse:

int data;
while(in >> data) {  /* ... */ }

// which is equivalent to 
while( !(in >> data).fail() )  {  /* ... */ }

Eof ötesindeki okuma girişiminden kaynaklanan başarısızlık, sonlandırma koşulu olarak alınır. Bu, başarılı bir akış ile eof dışındaki nedenlerle gerçekten başarısız olanı ayırt etmenin kolay bir yolu olmadığı anlamına gelir. Aşağıdaki akışları alın:

  • 1 2 3 4 5<eof>
  • 1 2 a 3 4 5<eof>
  • a<eof>

while(in>>data)üç girişin tümüfailbit için bir set ile sona erer . Birinci ve üçüncü olarak da ayarlanır. Öyleyse döngüden sonra, uygun bir girişi (1.) yanlış olanlardan (2. ve 3.) ayırt etmek için çok çirkin bir ekstra mantığa ihtiyaç vardır.eofbit

Oysa şunları alın:

while( !in.eof() ) 
{  
   int data;
   in >> data;
   if ( in.fail() ) /* handle with break or throw */; 
   // now use data
}    

Burada, in.fail()okunacak bir şey olduğu sürece doğru olduğunu doğrular. Amacı sadece bir süre döngüsü sonlandırıcı değildir.

Şimdiye kadar her şey yolunda, ama akışta izleyen boşluk varsa ne olur - eof()sonlandırıcı olarak en büyük endişe neye benziyor ?

Hata işlememizi teslim etmemize gerek yok; sadece boşlukları yiyin:

while( !in.eof() ) 
{  
   int data;
   in >> data >> ws; // eat whitespace with std::ws
   if ( in.fail() ) /* handle with break or throw */; 
   // now use data
}

std::wsayarlanırken akışında yer arka herhangi bir potansiyel (sıfır ya da daha fazla) atlar eofbitve değildirfailbit . Bu nedenle, in.fail()okunacak en az bir veri olduğu sürece beklendiği gibi çalışır. Tamamen boş akışlar da kabul edilebilirse, doğru form şudur:

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
}

Özet: Düzgün bir şekilde yapılandırılmış while(!eof)bir sadece mümkün değildir ve yanlış değildir, aynı zamanda verilerin kapsam dahilinde yerelleştirilmesine izin verir ve hata kontrolünün her zamanki gibi işten daha temiz bir şekilde ayrılmasını sağlar. while(!fail)Söylendiği gibi , tartışmasız daha yaygın ve kısa bir deyimdir ve basit (okuma türü başına tek veri) senaryolarda tercih edilebilir.

75 Nawaz Apr 09 2011 at 19:59

Çünkü programcılar yazmazlarsa while(stream >> n), muhtemelen şunu yazarlar:

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

Burada sorun şu ki, some work on nokumanın başarılı olup olmadığını kontrol etmeden yapamazsınız , çünkü başarısız olursa, some work on nistenmeyen sonuç üretirsiniz.

Bütün noktası, yani eofbit, badbitya da failbitayarlanır girişimi akışından okumak amacıyla yapıldıktan sonra. Yani eğer stream >> nbaşarısız, o zaman eofbit, badbitya failbitsen yazarsanız onun daha deyimsel yüzden hemen ayarlanır while (stream >> n)döndürülen nesne, çünkü streamhiç dönüştürür falseorada akışından okuma bazı başarısızlık ve eğer sonuç olarak döngü durur. Ve trueokuma başarılı olursa ve döngü devam ederse dönüşür .

10 melpomene May 04 2019 at 16:52

Diğer cevaplar mantığın neden yanlış olduğunu while (!stream.eof())ve nasıl düzeltileceğini açıkladı. Farklı bir şeye odaklanmak istiyorum:

neden eof kontrolünü açıkça iostream::eofyanlış kullanıyor ?

Genel anlamda, eof yalnızca kontrol etmek yanlıştır çünkü stream extraction ( >>) dosyanın sonuna ulaşmadan başarısız olabilir. Örneğiniz varsa int n; cin >> n;ve akım içeriyorsa hello, o zaman hgeçerli bir rakam değildir, bu nedenle girişin sonuna ulaşmadan çıkarma başarısız olacaktır.

Bu sorun, akış durumundan okumaya çalışmadan önce genel mantık hatasıyla birleştiğinde , bu, N giriş öğesi için döngünün N + 1 kez çalışacağı anlamına gelir, aşağıdaki belirtilere yol açar:

  • Akış boşsa, döngü bir kez çalışacaktır. >>başarısız olur (okunacak bir girdi yoktur) ve ayarlanması (tarafından stream >> x) varsayılan tüm değişkenler gerçekte başlatılmamıştır. Bu, gereksiz verilerin işlenmesine yol açar ve bu, anlamsız sonuçlar (genellikle çok büyük sayılar) olarak ortaya çıkabilir.

    (Standart kitaplığınız C ++ 11 ile uyumluysa, işler şimdi biraz farklıdır: Başarısız bir >>artık sayısal değişkenleri 0başlatılmamış olarak bırakmak yerine olarak ayarlar ( chars hariç ).)

  • Akış boş değilse, döngü son geçerli girişten sonra tekrar çalışacaktır. Son yinelemede tüm >>işlemler başarısız olduğundan, değişkenler muhtemelen değerlerini önceki yinelemeden korurlar. Bu, "son satır iki kez yazdırıldı" veya "son giriş kaydı iki kez işlendi" şeklinde ortaya çıkabilir.

    (Bu, C ++ 11'den beri biraz farklı görünmelidir (yukarıya bakın): Artık tekrarlanan bir son satır yerine sıfırların bir "hayali kaydı" elde edersiniz.)

  • Akış hatalı biçimlendirilmiş veriler içeriyorsa, ancak yalnızca kontrol .eofediyorsanız, sonsuz bir döngü elde edersiniz. >>akıştan herhangi bir veri ayıklayamaz, bu nedenle döngü sonuna ulaşmadan yerinde döner.


Özetle: çözüm başarısını test etmektir >>operasyon kendisi ayrı kullanmamaya .eof()yöntemi: while (stream >> n >> m) { ... }, sen başarısını test sadece C olarak scanfçağrı kendisi: while (scanf("%d%d", &n, &m) == 2) { ... }.