Me gustaría proporcionar una perspectiva abstracta de alto nivel.
Concurrencia y simultaneidad
Las operaciones de E / S interactúan con el entorno. El entorno no es parte de su programa y no está bajo su control. El entorno realmente existe "simultáneamente" con su programa. Al igual que con todas las cosas concurrentes, las preguntas sobre el "estado actual" no tienen sentido: no existe un concepto de "simultaneidad" entre los eventos concurrentes. Muchas propiedades del estado simplemente no existen al mismo tiempo.
Permítame hacer esto más preciso: suponga que quiere preguntar, "¿tiene más datos"? Puede pedir esto a un contenedor concurrente o a su sistema de E / S. Pero la respuesta generalmente es inaccesible y, por lo tanto, no tiene sentido. Entonces, ¿qué pasa si el contenedor dice "sí"? En el momento en que intente leer, es posible que ya no tenga datos. Del mismo modo, si la respuesta es "no", para el momento en que intente leer, los datos pueden haber llegado. La conclusión es que simplemente hayninguna propiedad como "Tengo datos", ya que no puede actuar de manera significativa en respuesta a cualquier respuesta posible. (La situación es un poco mejor con la entrada almacenada en el búfer, donde posiblemente podría obtener un "sí, tengo datos" que constituye algún tipo de garantía, pero aún tendría que ser capaz de lidiar con el caso contrario. Y con la salida de la situación ciertamente es tan malo como lo describí: nunca se sabe si ese disco o ese búfer de red está lleno).
Así llegamos a la conclusión de que es imposible, y de hecho la ONU razonable , para pedir un sistema de E / S si será capaz de realizar una operación de E / S. La única forma posible de interactuar con él (al igual que con un contenedor concurrente) es intentar la operación y verificar si tuvo éxito o falló. En ese momento donde interactúa con el entorno, entonces y solo entonces puede saber si la interacción fue realmente posible, y en ese punto debe comprometerse a realizar la interacción. (Este es un "punto de sincronización", por así decirlo).
EOF
Ahora llegamos a EOF. EOF es la respuesta que obtiene de un intento de operación de E / S. Significa que estaba intentando leer o escribir algo, pero al hacerlo no pudo leer o escribir ningún dato, y en su lugar se encontró el final de la entrada o salida. Esto es cierto para esencialmente todas las API de E / S, ya sea la biblioteca estándar de C, iostreams de C ++ u otras bibliotecas. Mientras las operaciones de E / S tengan éxito, simplemente no puede saber si las futuras operaciones tendrán éxito. Siempre debe intentar primero la operación y luego responder al éxito o al fracaso.
Ejemplos
En cada uno de los ejemplos, tenga en cuenta cuidadosamente que primero intentamos la operación de E / S y luego consumimos el resultado si es válido. Tenga en cuenta además que siempre debemos usar el resultado de la operación de E / S, aunque el resultado toma diferentes formas y formas en cada ejemplo.
C stdio, leído de un archivo:
for (;;) {
size_t n = fread(buf, 1, bufsize, infile);
consume(buf, n);
if (n < bufsize) { break; }
}
El resultado que debemos usar es n
el número de elementos que se leyeron (que puede ser tan pequeño como cero).
C Stdio, scanf
:
for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
consume(a, b, c);
}
El resultado que debemos usar es el valor de retorno de scanf
, el número de elementos convertidos.
C ++, extracción con formato iostreams:
for (int n; std::cin >> n; ) {
consume(n);
}
El resultado que debemos usar es en std::cin
sí mismo, que se puede evaluar en un contexto booleano y nos dice si la secuencia todavía está en el good()
estado.
C ++, getline de iostreams:
for (std::string line; std::getline(std::cin, line); ) {
consume(line);
}
El resultado que debemos usar es nuevamente std::cin
, como antes.
POSIX, write(2)
para vaciar un búfer:
char const * p = buf;
ssize_t n = bufsize;
for (ssize_t k = bufsize; (k = write(fd, p, n)) > 0; p += k, n -= k) {}
if (n != 0) { /* error, failed to write complete buffer */ }
El resultado que usamos aquí es k
el número de bytes escritos. El punto aquí es que solo podemos saber cuántos bytes se escribieron después de la operación de escritura.
POSIX getline()
char *buffer = NULL;
size_t bufsiz = 0;
ssize_t nbytes;
while ((nbytes = getline(&buffer, &bufsiz, fp)) != -1)
{
/* Use nbytes of data in buffer */
}
free(buffer);
El resultado que debemos usar es nbytes
el número de bytes hasta la nueva línea (o EOF si el archivo no terminó con una nueva línea).
Tenga en cuenta que la función retorna explícitamente -1
(¡y no EOF!) Cuando ocurre un error o llega a EOF.
Puede notar que rara vez deletreamos la palabra real "EOF". Por lo general, detectamos la condición de error de alguna otra manera que nos interesa más inmediatamente (p. Ej., No realizar tantas E / S como hubiéramos deseado). En cada ejemplo, hay alguna característica de API que podría decirnos explícitamente que se ha encontrado el estado EOF, pero esta no es una información terriblemente útil. Es mucho más un detalle de lo que a menudo nos importa. Lo que importa es si la E / S tuvo éxito, más de lo que falló.
Un último ejemplo que realmente consulta el estado EOF: suponga que tiene una cadena y desea probar que representa un entero en su totalidad, sin bits adicionales al final, excepto los espacios en blanco. Usando C ++ iostreams, es así:
std::string input = " 123 "; // example
std::istringstream iss(input);
int value;
if (iss >> value >> std::ws && iss.get() == EOF) {
consume(value);
} else {
// error, "input" is not parsable as an integer
}
Usamos dos resultados aquí. El primero es iss
, el objeto de flujo en sí mismo, para comprobar que la extracción formateada se realizó correctamente value
. Pero luego, después de consumir también espacios en blanco, realizamos otra operación de E / S / iss.get()
y esperamos que falle como EOF, que es el caso si toda la cadena ya ha sido consumida por la extracción formateada.
En la biblioteca estándar de C, puede lograr algo similar con las strto*l
funciones al verificar que el puntero final haya llegado al final de la cadena de entrada.
La respuesta
while(!feof)
está mal porque prueba algo que es irrelevante y no prueba algo que necesita saber. El resultado es que está ejecutando código erróneamente que supone que está accediendo a datos que se leyeron con éxito, cuando en realidad esto nunca sucedió.
feof()
para controlar un bucle