scanf (parte 2)

En el capítulo de hoy, lecturas con más y mejor formato, así como tratamiento de errores, aunque sin venirnos demasiado arriba. Sobre todo, ¿es raro que leer de scanf provoque bucles infinitos?

Cuando trabajamos con la función scanf en C, descubrimos que su uso va mucho más allá de simplemente leer valores básicos como enteros o caracteres. Una de las características más interesantes es que podemos incluir en el formato cadenas fijas que scanf espera encontrar exactamente en la entrada. Esto nos permite leer datos con formatos complejos y estructurados, como archivos con líneas que siguen un patrón específico.

Por ejemplo, imaginemos que tenemos un archivo llamado mediciones.txt donde cada línea comienza con la palabra in, seguida de una fecha en formato año-mes-día, luego el nombre de una ciudad y finalmente una medición de temperatura. Podemos definir variables para almacenar el año, mes, día, la ciudad y la temperatura, y luego usar un único scanf con un formato que incluya tanto las cadenas fijas como los placeholders para extraer todos esos datos de una sola lectura.

El formato podría ser algo así:

scanf("in %d-%d-%d %s %f", &anio, &mes, &dia, ciudad, &temperatura);

Aquí, scanf espera encontrar literalmente la palabra in seguida de un espacio, luego un año, un guion, un mes, otro guion, un día, un espacio, una palabra para la ciudad y finalmente un número flotante para la temperatura. Si la entrada no cumple con este formato exacto, scanf no leerá nada y dejará las variables sin modificar.

Es importante destacar que esta técnica funciona bien cuando la ciudad es una sola palabra. Si la ciudad tuviera espacios, como Santiago de Chile, este método no sería suficiente, ya que %s solo lee hasta el siguiente espacio.

Otro aspecto crucial al usar scanf es cómo maneja los errores. Si esperamos un número y el usuario introduce una letra, scanf no consume esa entrada inválida, sino que la deja en el buffer de entrada. Esto puede provocar problemas, especialmente en bucles donde esperamos que el usuario introduzca una opción válida.

Por ejemplo, en un menú típico donde pedimos al usuario que seleccione una opción entre 1 y 5, podemos tener un bucle que repite la lectura mientras la opción no esté en ese rango. Sin embargo, si el usuario introduce un carácter no numérico, scanf no lo consume y lo deja en el buffer, lo que hace que el siguiente intento de lectura vuelva a fallar con el mismo carácter, generando un bucle infinito.

La razón de este comportamiento es que scanf utiliza internamente una función similar a ungetc, que permite devolver caracteres al buffer de entrada si no le gustan. Así, cuando encuentra un carácter que no encaja con el formato esperado, lo devuelve al buffer para que la próxima lectura lo encuentre de nuevo.

Para evitar este problema, debemos limpiar el buffer de entrada después de un error en scanf. Una forma portátil y efectiva de hacerlo es leer y descartar caracteres hasta encontrar el salto de línea que indica el final de la entrada del usuario. Esto se puede hacer con un simple bucle que llame a getchar hasta encontrar el carácter '\n':

while (getchar() != '\n');

Este código consume todos los caracteres restantes en la línea, incluyendo el salto de línea, dejando el buffer limpio para la siguiente lectura.

Es importante no confiar en funciones como fflush(stdin), que aunque funcionan en Windows, no son portables y pueden causar comportamientos inesperados en otros sistemas operativos.

En definitiva, scanf es una herramienta poderosa que nos permite leer datos con formatos complejos y realizar múltiples lecturas en una sola llamada. Sin embargo, su manejo de errores y del buffer de entrada requiere cuidado para evitar problemas como bucles infinitos o lecturas incorrectas. Por eso, cuando sea posible, es recomendable usar alternativas más robustas como readline o librerías especializadas que gestionan mejor estos casos y nos evitan tener que lidiar con estos detalles manualmente.

Lista de reproducción
  1. 1
    Instalar CodeBlocks
    15 minutos
  2. 2
    Funciones y hola mundo
    17 minutos
  3. 3
    Variables y tipos de datos
    17 minutos
  4. 4
    Condicionales y operadores lógicos
    16 minutos
  5. 5
    Bucles
    11 minutos
  6. 6
    Punteros
    12 minutos
  7. 7
    Arrays
    14 minutos
  8. 8
    Estructuras
    12 minutos
  9. 9
    Otras construcciones de C
    9 minutos
  10. 10
    Memoria dinámica
    8 minutos
  11. 11
    El preprocesador (parte 1)
    10 minutos
  12. 12
    El preprocesador (parte 2)
    9 minutos
  13. 13
    Archivos de cabecera y múltiples .c (parte 1)
    8 minutos
  14. 14
    Archivos de cabecera y múltiples .c (parte 2)
    9 minutos
  15. 15
    C desde la línea de comandos (parte 1)
    8 minutos
  16. 16
    C desde la línea de comandos (parte 2)
    9 minutos
  17. 17
    Break y continue
    10 minutos
  18. 18
    Goto
    13 minutos
  19. 19
    Manipulación de bits
    15 minutos
  20. 20
    Máscaras de bit
    19 minutos
  21. 21
    Archivos (1): fopen y fclose
    13 minutos
  22. 22
    Archivos (2): leer con fgetc
    9 minutos
  23. 23
    Archivos (3): fseek y ftell
    11 minutos
  24. 24
    Archivos (4): leer con fgets
    9 minutos
  25. 25
    Archivos (5): fputc y fputs
    7 minutos
  26. 26
    Archivos (6): volcar en archivos con fwrite
    10 minutos
  27. 27
    Archivos (7): fread, fwrite y los arrays
    10 minutos
  28. 28
    Archivos (8): entrada estándar y salida estándar
    9 minutos
  29. 29
    Archivos (9): buffers
    14 minutos
  30. 30
    Archivos (y 10): otras funciones útiles con archivos
    5 minutos
  31. 31
    printf (1)
    18 minutos
  32. 32
    printf (parte 2)
    12 minutos
  33. 33
    scanf (parte 1)
    17 minutos
  34. 34
    scanf (parte 2)
    17 minutos
  35. 35
    fprintf, sprintf y snprintf
    8 minutos
  36. 36
    Tipos de datos opacos
    13 minutos
  37. 37
    Bibliotecas estáticas
    13 minutos
  38. 38
    Bibliotecas dinámicas
    15 minutos
  39. 39
    Más flags: i mayúscula (include), wall, werror, pedantic...
    12 minutos
  40. 40
    pkg-config
    12 minutos
  41. 41
    Make
    17 minutos
  42. 42
    GDB
    21 minutos
  43. 43
    Variables globales
    6 minutos
  44. 44
    extern
    9 minutos
  45. 45
    Funciones variádicas
    12 minutos
  46. 46
    El optimizador de GCC y la opción -O
    12 minutos
  47. 47
    Volatile
    6 minutos