¿Por qué iostream::eof dentro de una condición de bucle (es decir, `` while (!stream.eof())`) se considera incorrecto?
Acabo de encontrar un comentario en esta respuesta que dice que usar iostream::eof
en una condición de bucle es "casi con certeza incorrecto". Generalmente uso algo como while(cin>>n)
- que supongo que comprueba implícitamente el EOF.
¿ Por qué la verificación de eof explícitamente es while (!cin.eof())
incorrecta?
¿ En qué se diferencia de usarlo scanf("...",...)!=EOF
en C (que uso a menudo sin problemas)?
Porque iostream::eof
solo regresará true
después de leer el final de la transmisión. No indica que la próxima lectura será el final de la transmisión.
Considere esto (y suponga que la próxima lectura será al final de la transmisió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
}
Contra esto:
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)
}
Y sobre tu segunda pregunta: porque
if(scanf("...",...)!=EOF)
es lo mismo que
if(!(inStream >> data).eof())
y no es lo mismo que
if(!inStream.eof())
inFile >> data
En pocas palabras: con un manejo adecuado de los espacios en blanco, lo siguiente es cómo eof
se puede utilizar (e incluso ser más confiable que fail()
para la verificación de errores):
while( !(in>>std::ws).eof() ) {
int data;
in >> data;
if ( in.fail() ) /* Handle with 'break' or 'throw' */;
// Now use data
}
( Gracias Tony D por la sugerencia de resaltar la respuesta. Consulte su comentario a continuación para ver un ejemplo de por qué esto es más sólido ) .
Al principal argumento en contra del uso eof()
parece faltarle una sutileza importante sobre el papel del espacio en blanco. Mi propuesta es que verificar eof()
explícitamente no solo no " siempre es incorrecto " (lo que parece ser una opinión predominante en esta y otras preguntas similares de Stack Overflow), sino que con el manejo adecuado de los espacios en blanco, proporciona un error más limpio y confiable. manejo, y es la solución siempre correcta (aunque no necesariamente la más concisa).
Para resumir lo que se sugiere como la terminación "adecuada" y el orden de lectura es lo siguiente:
int data;
while(in >> data) { /* ... */ }
// Which is equivalent to
while( !(in >> data).fail() ) { /* ... */ }
El fallo debido al intento de lectura más allá de eof se toma como condición de terminación. Esto significa que no existe una manera fácil de distinguir entre una transmisión exitosa y una que realmente falla por razones distintas a eof. Tome las siguientes transmisiones:
1 2 3 4 5<eof>
1 2 a 3 4 5<eof>
a<eof>
while(in>>data)
termina con un conjunto failbit
para las tres entradas. En el primero y en el tercero, eofbit
también se fija. Entonces, más allá del ciclo, se necesita una lógica adicional muy fea para distinguir una entrada adecuada (primera) de las incorrectas (segunda y tercera).
Considerando que, tome lo siguiente:
while( !in.eof() )
{
int data;
in >> data;
if ( in.fail() ) /* Handle with break or throw */;
// Now use data
}
Aquí, in.fail()
verifica que mientras haya algo para leer, sea el correcto. Su propósito no es un simple terminador de bucle while .
Hasta ahora todo bien, pero ¿qué sucede si hay un espacio final en la secuencia? ¿Cuál parece ser la principal preocupación eof()
como terminador?
No necesitamos renunciar a nuestro manejo de errores; simplemente come el espacio en blanco:
while( !in.eof() )
{
int data;
in >> data >> ws; // Eat white space with 'std::ws'
if ( in.fail() ) /* Handle with 'break' or 'throw' */;
// Now use data
}
std::ws
omite cualquier espacio final potencial (cero o más) en la secuencia al configurar eofbit
, y nofailbit
. Entonces, in.fail()
funciona como se esperaba, siempre que haya al menos un dato para leer. Si también se aceptan secuencias completamente en blanco, entonces la forma correcta es:
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
}
Resumen: Una construcción adecuada while(!eof)
no solo es posible y no es incorrecta, sino que permite localizar los datos dentro del alcance y proporciona una separación más clara entre la verificación de errores y el negocio habitual. Dicho esto, while(!fail)
es indiscutiblemente un modismo más común y conciso, y puede preferirse en escenarios simples (datos únicos por tipo de lectura).
Porque si los programadores no escriben while(stream >> n)
, posiblemente escriban esto:
while(!stream.eof())
{
stream >> n;
//some work on n;
}
Aquí el problema es que no puede prescindir some work on n
de comprobar primero si la lectura de la secuencia se realizó correctamente, porque si no se realizó correctamente, se some work on n
produciría un resultado no deseado.
El punto es que, eofbit
, badbit
o failbit
se configuran después de intentar leer la secuencia. Entonces, si stream >> n
falla, entonces eofbit
, badbit
o failbit
se establece inmediatamente, por lo que es más idiomático si escribe while (stream >> n)
, porque el objeto devuelto stream
se convierte false
si hubo algún error en la lectura de la secuencia y, en consecuencia, el bucle se detiene. Y se convierte true
si la lectura fue exitosa y el ciclo continúa.
Lo importante que debe recordar es que esto inFile.eof()
no sucederá True
hasta que falle un intento de lectura, porque haya llegado al final del archivo. Entonces, en este ejemplo, obtendrá un error.
while (!inFile.eof()){
inFile >> x;
process(x);
}
La forma de hacer que este bucle sea correcto es combinar la lectura y la verificación en una sola operación, así
while (inFile >> x)
process(x);
Por convención, operator>>
devuelve la secuencia de la que leemos y una prueba booleana en una secuencia regresa False
cuando la secuencia falla (como al llegar al final del archivo).
Entonces esto nos da la secuencia correcta:
- leer
- probar si la lectura tiene éxito
- si y sólo si la prueba tiene éxito, procesa lo que hemos leído
Si encuentra algún otro problema que le impide leer el archivo correctamente, no podrá acceder a eof()
él. Por ejemplo, veamos algo como esto
int x;
while (!inFile.eof()) {
inFile >> x;
process(x);
}
Repasemos el funcionamiento del código anterior, con un ejemplo.
- Suponga que el contenido del archivo es
'1', '2', '3', 'a', 'b'
. - El bucle leerá el 1, 2 y 3 correctamente.
- Entonces llegará a
a
. - Cuando intenta extraer
a
como int, fallará. - La transmisión ahora se encuentra en un estado fallido, hasta que
clear
la transmitamos, o a menos que la transmitamos, todos los intentos de leerla fallarán. - Pero, cuando probamos eof(), devolverá
False
, porque no estamos al final del archivo, porque todavía haya
esperando ser leído. - El bucle seguirá intentando leer el archivo y fallará cada vez, por lo que nunca llega al final del archivo.
- Entonces, el bucle anterior se ejecutará para siempre.
Pero, si usamos un bucle como este, obtendremos el resultado requerido.
while (inFile >> x)
process(x);
En este caso, la secuencia se convertirá False
no solo en caso de que finalice el archivo, sino también en caso de una conversión fallida, como la que a
no podemos leer como un número entero.