tipos tabla lenguaje leer espacios definicion datos con caracteres cadenas cadena c stdio

lenguaje - tabla de tipos de datos en c



¿Cómo leer/analizar la entrada en C? Las preguntas frecuentes (1)

Tengo problemas con mi programa C cuando intento leer / analizar la entrada.

¿Ayuda?

Esta es una entrada de preguntas frecuentes.

StackOverflow tiene muchas preguntas relacionadas con la entrada de lectura en C, con respuestas generalmente enfocadas en el problema específico de ese usuario en particular sin realmente pintar la imagen completa.

Este es un intento de cubrir una serie de errores comunes de manera integral, por lo que esta familia específica de preguntas puede responderse simplemente marcándolas como duplicados de esta:

  • ¿Por qué la última línea se imprime dos veces?
  • ¿Por qué scanf("%d", ...) mi scanf("%d", ...) / scanf("%c", ...) ?
  • ¿Por qué se cuelga gets() ?
  • ...

La respuesta está marcada como wiki de la comunidad. Siéntase libre de mejorar y (con cautela) extender.


La cartilla de entrada C para principiantes

  • Modo de texto versus modo binario
  • Verifique fopen() por falla
  • Trampas
    • Verifique cualquier función que llame para tener éxito
    • EOF, o "por qué la última línea se imprime dos veces"
    • No use gets() , nunca
    • No use fflush() en stdin o cualquier otra secuencia abierta para leer, nunca
    • No use *scanf() para entradas potencialmente malformadas
    • Cuando *scanf() no funciona como se esperaba
  • Leer, luego analizar
    • Leer (parte de) una línea de entrada a través de fgets()
    • Analiza la línea en memoria
  • Limpiar

Modo de texto versus modo binario

Una secuencia de "modo binario" se lee exactamente como se ha escrito. Sin embargo, podría (o no) haber un número definido por la implementación de caracteres nulos ('' /0 '') agregados al final de la secuencia.

Una secuencia de "modo de texto" puede realizar una serie de transformaciones, que incluyen (entre otras):

  • eliminación de espacios inmediatamente antes de un final de línea;
  • cambiar las líneas nuevas ( ''/n'' ) a algo más en la salida (por ejemplo, "/r/n" en Windows) y volver a ''/n'' en la entrada;
  • agregar, modificar o eliminar caracteres que no son caracteres de impresión ( isprint(c) es verdadero), pestañas horizontales o líneas nuevas.

Debería ser obvio que el texto y el modo binario no se mezclan. Abra archivos de texto en modo de texto y archivos binarios en modo binario.

Verifique fopen() por falla

El intento de abrir un archivo puede fallar por varias razones: falta de permisos o archivos que no se encuentran como los más comunes. En este caso, fopen() devolverá un puntero NULL . Siempre verifique si fopen devolvió un puntero NULL , antes de intentar leer o escribir en el archivo.

Cuando fopen falla, generalmente establece la variable global errno para indicar por qué falló. (Esto técnicamente no es un requisito del lenguaje C, pero tanto POSIX como Windows garantizan hacerlo). errno es un número de código que se puede comparar con las constantes en errno.h , pero en programas simples, generalmente todo lo que necesita hacer es convertirlo en un mensaje de error e imprimirlo, usando perror() o strerror() . El mensaje de error también debe incluir el nombre de archivo que pasó a fopen ; si no haces eso, estarás muy confundido cuando el problema sea que el nombre de archivo no es lo que pensabas que era.

#include <stdio.h> #include <string.h> #include <errno.h> int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s file/n", argv[0]); return 1; } FILE *fp = fopen(argv[1], "rb"); if (!fp) { // alternatively, just `perror(argv[1])` fprintf(stderr, "cannot open %s: %s/n", argv[1], strerror(errno)); return 1; } // read from fp here fclose(fp); return 0; }

Trampas

Verifique cualquier función que llame para tener éxito

Esto debería ser obvio. Pero revise la documentación de cualquier función que llame para su valor de retorno y manejo de errores, y verifique esas condiciones.

Estos son errores que son fáciles cuando se detecta la enfermedad temprano, pero si no lo hace se rasca la cabeza.

EOF, o "por qué la última línea se imprime dos veces"

La función feof() devuelve true si se ha alcanzado EOF. Un malentendido de lo que realmente significa "alcanzar" EOF hace que muchos principiantes escriban algo como esto:

// BROKEN CODE while (!feof(fp)) { fgets(buffer, BUFFER_SIZE, fp); printf("%s", buffer); }

Esto hace que la última línea de la entrada se imprima dos veces , porque cuando se lee la última línea (hasta la nueva línea final, el último carácter en la secuencia de entrada), EOF no se establece.

¡EOF solo se establece cuando intentas leer más allá del último personaje!

Entonces, el código anterior se repite una vez más, fgets() no puede leer otra línea, establece EOF y deja intacto el contenido del buffer , que luego se imprime nuevamente.

En cambio, verifique si los fgets fallaron directamente:

// GOOD CODE while (fgets(buffer, BUFFER_SIZE, fp)) { printf("%s", buffer); }

No use gets() , nunca

No hay forma de usar esta función de forma segura. Debido a esto, se ha eliminado del lenguaje con la llegada de C11.

No use fflush() en stdin o cualquier otra secuencia abierta para leer, nunca

Muchas personas esperan que fflush(stdin) descarte la entrada del usuario que aún no se ha leído. No hace eso. En ISO C simple, llamar a fflush() en una secuencia de entrada tiene un comportamiento indefinido . Tiene un comportamiento bien definido en POSIX y en MSVC, pero ninguno de los dos descarta la entrada del usuario que aún no se ha leído.

Por lo general, la forma correcta de borrar la entrada pendiente es leer y descartar caracteres hasta e incluyendo una nueva línea, pero no más allá de:

int c; do c = getchar(); while (c != EOF && c != ''/n'');

No use *scanf() para entradas potencialmente malformadas

Muchos tutoriales le enseñan a usar *scanf() para leer cualquier tipo de entrada, porque es muy versátil.

Pero el propósito de *scanf() es realmente leer datos masivos que se pueden confiar en un formato predefinido. (Como ser escrito por otro programa).

Incluso entonces *scanf() puede disparar al no observador:

  • El uso de una cadena de formato que de alguna manera puede ser influenciada por el usuario es un gran vacío de seguridad.
  • Si la entrada no coincide con el formato esperado, *scanf() deja de analizar inmediatamente, dejando los argumentos restantes sin inicializar.
  • Le indicará cuántas tareas ha realizado con éxito, razón por la cual debe verificar su código de retorno (ver arriba), pero no exactamente dónde dejó de analizar la entrada, lo que dificulta la recuperación de errores.
  • Omite los espacios en blanco iniciales en la entrada, excepto cuando no lo hace ( [ , c , y n conversiones). (Ver el siguiente párrafo)
  • Tiene un comportamiento algo peculiar en algunos casos de esquina.

Cuando *scanf() no funciona como se esperaba

Un problema frecuente con *scanf() es cuando hay un espacio en blanco no leído ( '' '' , ''/n'' , ...) en la secuencia de entrada que el usuario no tuvo en cuenta.

La lectura de un número ( "%d" et al.), O una cadena ( "%s" ), se detiene en cualquier espacio en blanco. Y aunque la mayoría de los especificadores de conversión *scanf() omiten los espacios en blanco iniciales en la entrada, [ , c y n no lo hacen. Por lo tanto, la nueva línea sigue siendo el primer carácter de entrada pendiente, lo que hace que %c y %[ no coincidan.

Puede omitir la nueva línea en la entrada, leyéndola explícitamente, por ejemplo, a través de fgetc() , o agregando un espacio en blanco a su cadena de formato *scanf() . (Un solo espacio en blanco en la cadena de formato coincide con cualquier número de espacios en blanco en la entrada).

Leer, luego analizar

Acabamos de desaconsejar el uso de *scanf() excepto cuando realmente, de manera positiva, sabes lo que estás haciendo. Entonces, ¿qué usar como reemplazo?

En lugar de leer y analizar la entrada de una vez, como *scanf() intenta hacer, separe los pasos.

Leer (parte de) una línea de entrada a través de fgets()

fgets() tiene un parámetro para limitar su entrada como máximo a esa cantidad de bytes, evitando el desbordamiento de su búfer. Si la línea de entrada encaja completamente en su búfer, el último carácter en su búfer será la nueva línea ( ''/n'' ). Si no todo encaja, está viendo una línea parcialmente leída.

Analiza la línea en memoria

Especialmente útiles para el análisis en memoria son las familias de funciones strtol() y strtod() , que proporcionan una funcionalidad similar a los especificadores de conversión *scanf() d , i , u , o , x , a , e , f y g .

Pero también le dicen exactamente dónde dejaron de analizar y tienen un manejo significativo de números demasiado grandes para el tipo de destino.

Más allá de eso, C ofrece una amplia gama de funciones de procesamiento de cadenas . Como tiene la entrada en la memoria y siempre sabe exactamente hasta qué punto ya la ha analizado, puede retroceder tantas veces como desee tratando de darle sentido a la entrada.

Y si todo lo demás falla, tiene toda la línea disponible para imprimir un mensaje de error útil para el usuario.

Limpiar

Asegúrese de cerrar explícitamente cualquier transmisión que haya abierto (con éxito). Esto elimina cualquier búfer aún no escrito y evita la pérdida de recursos.

fclose(fp);