İostream :: eof bir döngü koşulu içinde (yani, `while (! Stream.eof ())`) neden yanlış kabul edilir?
Bu yanıtta iostream::eof
bir 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("...",...)!=EOF
C'de kullanmaktan ne kadar farklıdır (genellikle problemsiz kullanırım)?
Yanıtlar
Çü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
Alt satır üst: Beyaz boşluğun düzgün bir şekilde ele alınmasıyla, aşağıdakilerin nasıl eof
kullanı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::ws
ayarlanırken akışında yer arka herhangi bir potansiyel (sıfır ya da daha fazla) atlar eofbit
ve 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.
Çü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 n
okumanın başarılı olup olmadığını kontrol etmeden yapamazsınız , çünkü başarısız olursa, some work on n
istenmeyen sonuç üretirsiniz.
Bütün noktası, yani eofbit
, badbit
ya da failbit
ayarlanır girişimi akışından okumak amacıyla yapıldıktan sonra. Yani eğer stream >> n
başarısız, o zaman eofbit
, badbit
ya failbit
sen yazarsanız onun daha deyimsel yüzden hemen ayarlanır while (stream >> n)
döndürülen nesne, çünkü stream
hiç dönüştürür false
orada akışından okuma bazı başarısızlık ve eğer sonuç olarak döngü durur. Ve true
okuma başarılı olursa ve döngü devam ederse dönüşür .
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::eof
yanlış 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 h
geç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ındanstream >> 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şkenleri0
başlatılmamış olarak bırakmak yerine olarak ayarlar (char
s 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
.eof
ediyorsanı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) { ... }
.