Neden iostream :: eof bir döngü koşulu içinde (yani `` while (! Stream.eof ()) `) yanlış kabul ediliyor?


595

Bu cevapta iostream::eofbir döngü koşulunda kullanmanın "neredeyse kesinlikle yanlış" olduğunu söyleyen bir yorum buldum . Genellikle while(cin>>n)örtük olarak EOF kontrol eder gibi bir şey kullanıyorum .

Neden açıkça kullanıldığını kontrol etmek while (!cin.eof())yanlış?

scanf("...",...)!=EOFC (çoğunlukla problemsiz kullandığım) ile kullanmak arasındaki fark nedir?


21
scanf(...) != EOFC de çalışmaz, çünkü scanfbaşarıyla ayrıştırılan ve atanan alanların sayısını döndürür. Doğru bir durumdur scanf(...) < nnerede nbiçim dizesi alanların sayısıdır.
Ben Voigt

5
@Ben Voigt, EOF'ye ulaşılması durumunda negatif bir sayı döndürür (EOF genellikle böyle tanımlanır)
Sebastian

19
@SebastianGodelet: Aslında, EOFdosya alanı ilk alan dönüşümünden önce (başarılı veya başarılı değil) karşılaşırsa geri döner . Alanlar arasında dosya sonuna ulaşılırsa, başarıyla dönüştürülüp saklanan alanların sayısını döndürür. Hangi EOFyanlış ile karşılaştırma yapar .
Ben Voigt

1
@SebastianGodelet: Hayır, pek değil. "Döngüyü geçtikten sonra, uygun bir girdiyi yanlış bir girdiden ayırmanın (kolay) bir yolu olmadığını" söylediğinde hata yapar. Aslında .eof(), döngü çıktıktan sonra kontrol etmek kadar kolaydır .
Ben Voigt

2
@Ben Evet, bu durum için (basit bir int okuma). Ancak, while(fail)döngünün hem gerçek bir başarısızlık hem de bir eof ile sona erdiği bir senaryo kolayca ortaya çıkabilir . Yineleme başına 3 inte ihtiyacınız olup olmadığını düşünün (diyelim ki bir xyz noktası veya başka bir şey okuyorsunuz), ancak hatalı olarak akışta sadece iki ints var.
kurnaz

Yanıtlar:


544

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

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

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

İkinci sorunuzda: Çünkü

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

aynıdır

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

ve aynı değil

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

12
Bahsetmeye değer, eğer! (! Hata 1: Son veri parçasından sonra boşluk yoksa koşul girmez (son veri işlenmez). Hata 2: EOF'ye ulaşılmadığı sürece (sonsuz döngü, aynı eski verileri tekrar tekrar işleyerek) veri okuma başarısız olsa bile koşula girecektir.
Tronic

4
Bence bu cevabın biraz yanıltıcı olduğunu belirtmek gerekir. Çıkartırken intlar veya std::strings veya benzeri, EOF bit olduğunu sen bitmeden bir hakkı ayıklamak zaman ayarlayabilir ve çıkarma ucunu vurur. Tekrar okumanıza gerek yok. Dosyalardan okurken ayarlanmamasının nedeni \n, sonunda bir fazlalık olmasıdır . Bunu başka bir cevapta ele aldım . S okumak charfarklı bir konudur, çünkü her seferinde sadece bir tane çıkarır ve sonuna gelmeye devam etmez.
Joseph Mansfield

79
Asıl sorun, EOF'a ulaşamadığımız için bir sonraki okumanın başarılı olacağı anlamına gelmiyor .
Joseph Mansfield

1
@sftrabbit: hepsi doğru ama çok kullanışlı değil ... izleyen '\ n' olmasa bile, diğer izleyen boşlukların dosyadaki diğer boşluklarla tutarlı bir şekilde işlenmesini (yani atlanmış) makul olur. Ayrıca, "birini daha önce ayıkladığınız zaman" ın ince bir sonucu , girdi tamamen boş olduğunda s veya s while (!eof())üzerinde "işe yaramayacağı" anlamına gelir . intstd::string\n
Tony Delroy

2
@TonyD Tamamen katılıyorum. Ben söylüyorum sebebi bu okuma ve akarsu içeriyorsa benzer cevaplar düşünürler zaman çoğu insan düşünüyorum çünkü olduğu "Hello"(boşluk veya Sonuna eklenmiş \na ve) std::stringo mektuplar çıkartacaktır ayıklanır, Hhiç oayıklanması durdurmak, ve Daha sonra değil EOF bitini ayarlayın. Aslında, EOF bitini ayarlayacaktır çünkü ekstraksiyonu durduran EOF idi. Sadece bunu insanlar için temizlemeyi umuyorum.
Joseph Mansfield

103

Alt satır üstü: Beyaz boşluğun doğru kullanımı ile aşağıdakiler nasıl eofkullanılabilir (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. )


Kullanmaya karşı ana argümanın eof(), beyaz alanın rolü hakkında önemli bir incelik eksik olduğu görülmektedir. Benim önerim, eof()açıkça kontrol etmek sadece " her zaman yanlış " değil - bu ve benzer SO iş parçacıkları üzerinde baskın bir fikir gibi görünüyor - ama beyaz boşluğun düzgün bir şekilde işlenmesi ile daha temiz ve daha güvenilir bir yöntem sağlıyor. hata işleme ve her zaman doğru çözümdür (her ne kadar tersi olmasa da).

"Uygun" sonlandırma ve okuma sırası olarak önerilenleri özetlemek gerekirse:

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

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

Bunun ötesinde okuma girişiminden kaynaklanan başarısızlık, fesih koşulu olarak kabul edilir. Bunun anlamı, başarılı bir akış ile gerçekten başka nedenlerden dolayı başarısız olan bir akışı ayırt etmenin kolay bir yolu olmadığıdır. 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)her üç giriş failbitiçin de bir set ile sonlanır . Birinci ve üçüncü olarak da ayarlanır. Bu yüzden döngüden sonra, uygun bir girişi (1.) uygunsuz olanlardan (2. ve 3.) ayırmak için çok çirkin ekstra mantığa ihtiyaç vardır.eofbit

Halbuki aşağıdakileri yapı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 while döngüsü sonlandırıcı değildir.

Şimdiye kadar çok iyi, ama akışta izleyen alan varsa ne olur - eof()terminatör olarak büyük endişe gibi ne olur ?

Hata işlememizi teslim etmemize gerek yok; sadece beyaz alanı 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:

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ılanma while(!eof)sadece mümkün değildir ve yanlış değildir, aynı zamanda verilerin kapsam içinde yerelleştirilmesine izin verir ve hata kontrolünün her zamanki gibi işten daha net bir şekilde ayrılmasını sağlar. Bununla birlikte while(!fail), tartışmasız daha yaygın ve kısa bir deyimdir ve basit (okuma türü başına tek veri) senaryolarda tercih edilebilir.


6
" Bu yüzden döngüden sonra uygun bir girişi uygun olmayan bir girişten ayırmanın (kolay) bir yolu yoktur. " Ancak, bir durumda hem eofbitve hem failbitde ayarlanmış, diğerinde sadece failbitayarlanmış. Her döngüde değil, döngü sona erdikten sonra yalnızca bir kez test etmeniz gerekir ; döngüden yalnızca bir kez ayrılacağından, neden döngüden bir kez ayrıldığını kontrol etmeniz gerekir . while (in >> data)tüm boş akışlar için iyi çalışır.
Jonathan Wakely

3
Söylediğiniz (ve daha önce yapılan bir nokta) kötü biçimlendirilmiş bir akışın !eof & failgeçmiş döngü olarak tanımlanabileceğidir . Birinin buna güvenemeyeceği durumlar vardır. Yukarıdaki yoruma bakın ( goo.gl/9mXYX ). Her neyse, her zaman daha iyi bir alternatif eofolarak -check'i önermiyorum . Sizden sadece, söylüyorum olduğunu olası ve bunu yapmanın (bazı durumlarda daha uygun) yolu yerine "kesinlikle yanlış!" çünkü burada SO'da iddia ediliyor.
sinsi

2
"Örnek olarak, veri aynı anda birden çok alan okuma aşırı operatörü ile bir yapı >> olduğu hataları kontrol ediyorum nasıl dikkate" çok daha basit vaka açınızı destekliyor - stream >> my_int: - "" dere örneğin içerdiği nerede eofbitve failbitvardır Ayarlamak. Bu operator>>, kullanıcı tarafından sağlanan aşırı yüklenmenin, kullanımı eofbitdesteklemeye yardımcı olmak için geri dönmeden önce temizleme seçeneğine sahip olduğu senaryodan daha kötüdür while (s >> x). Daha genel olarak, bu cevap bir temizlik kullanabilir - sadece final while( !(in>>ws).eof() )genellikle sağlamdır ve sonunda gömülür.
Tony Delroy

74

Çünkü programcılar yazmazsa while(stream >> n), muhtemelen bunu yazarlar:

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

Sorun şu ki, some work on nönce akışın başarılı olup olmadığını kontrol etmeden yapamazsınız , çünkü başarısız olursa, some work on nistenmeyen sonuç üretersiniz.

Bütün noktası, yani eofbit, badbitya da failbitayarlanır girişimi akışından okumak amacıyla yapıldıktan sonra. Eğer stream >> nbaşarısız olursa, o zaman eofbit, badbitya failbitda hemen ayarlanırsa, yazdığınızda daha deyimsel olur while (stream >> n), çünkü döndürülen nesne akıştan okumada bir hata varsa streamdönüşür falseve sonuç olarak döngü durur. Ve trueokumanın başarılı olup olmadığına ve döngü devam edip etmediğine dönüşür .


1
Belirtilen "istenmeyen sonuç" dışında, tanımlanmamış değeri üzerinde çalışma yapmakla birlikte , başarısız akış işlemi herhangi bir girdi tüketmezse nprogram da sonsuz bir döngüye girebilir.
mastov

10

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:

eof neden açıkça iostream::eofyanlış kullanarak denetleniyor ?

Genel olarak, akış ayıklama ( ) dosyası sonuna gelmeden başarısız olabileceğinden eof yalnızca denetleme yanlıştır >>. Örneğiniz varsa int n; cin >> n;ve akış içeriyorsa hello, hgeçerli bir basamak değildir, bu nedenle girişin sonuna ulaşılmadan çıkarma işlemi başarısız olur.

Bu sorun, ondan okumaya başlamadan önce akış durumunu kontrol etme genel mantık hatası ile birleştiğinde, N giriş öğeleri için döngü N + 1 kez çalışacağı anlamına gelir:

  • Akış boşsa, döngü bir kez çalışır. >>başarısız olur (okunacak girdi yoktur) ve (tarafından stream >> x) ayarlanması gereken tüm değişkenler aslında başlatılmaz. Bu, saçma sonuçların (genellikle çok sayıda) ortaya çıkabilen çöp verilerinin işlenmesine yol açar.

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

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

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

  • Akış hatalı biçimlendirilmiş veriler içeriyorsa, ancak yalnızca kontrol .eofediyorsanız, sonsuz bir döngü elde edersiniz. >>akıştan herhangi bir veri çıkartamaz, böylece döngü sonuna kadar 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) { ... }.


1
Bu en doğru cevaptır, ancak c ++ 11'den itibaren, değişkenlerin artık başlatılmadığına inanmıyorum (ilk mermi pt)
csguy 20:19
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.