¿Por qué genera un bucle infinito si se introduce una letra en lugar de un numero?
Cuando esta sentencia se ejecute:
scanf("%f", &a);
El programa se pausa a la espera de un dato, pero si el usuario ingresa la letra H
, la lectura fallará, debido a que, solo se puede leer valores flotantes.
Entonces el búfer internamente quedó así:
H\n
Claro, el búfer quedó con el salto de línea por el ENTER
que presionó el usuario.
Luego, la función scanf
le asigna a la variable a
un 0
(ya que no pudo leer ningún dato) y la condición a < 1.00 || a > 10.0
se cumple, generando que el bucle genere otra iteración. Pero en este caso scanf
no pausará el programa porque el búfer del teclado está sucio, por lo tanto, la lectura vuelve a fallar y la variable a
sigue quedando con el valor 0
y otra vez se repite el mismo proceso anterior, generando un bucle infinito. Este es el motivo del porque ocurre este problema.
Para solucionar este problema debemos saber que en este caso la función scanf retornará 0
si la lectura falló y con esto podemos detectar el error. Además, tendremos que limpiar el búfer del teclado para asegurar que la próxima lectura se pueda llevar a cabo.
¿Cómo limpiamos el búfer del teclado?
Con la función fflush. ¡No!, esta función no limpia el búfer stdin, sino, el stdout. Por más que funcione en algunos casos, no se lo debe usar. Por ejemplo, en Linux no funcionará esta sentencia (no tendrás el resultado esperado):
fflush(stdin);
Así que lo ideal es usar una solución estándar (que funciona en cualquier lado), como por ejemplo:
int ch;
while((ch = getchar()) != '\n' && ch != EOF);
La función getchar retorna el siguiente caracter que se encuentre en el búfer del teclado.
Ahora, para plantear la solución final debemos dividir el problema en subproblemas. Por ejemplo, podemos crear una función llamada clearBuf
(sin parámetros) en la cual se encargue de limpiar el búfer stdin y otra función denominada validFormat
para que valide la entrada del usuario.
La primera función quedaría así:
void clearBuf()
{
int ch;
//Limpia el búfer del teclado hasta encontrar un salto de línea o fin del archivo
while((ch = getchar()) != '\n' && ch != EOF);
}
Y la segunda función tendrá dos parámetros y retornará 1
si el usuario no cumple con el formato, de lo contrario, devuelve 0
.
int validFormat(const char* fm, void* a)
{
//Si la función scanf retorna 0, fue porque hubo una falla en la lectura..
if(!scanf(fm, a))
{
//Limpiamos el búfer del teclado para que la próxima lectura se lleve a cabo.
clearBuf();
return 1;
}
//Si no encontramos un salto de línea en el búfer, es porque el usuario no cumplió con el formato.
if(getchar() != '\n')
{
//Volvemos a limpiar el búfer para la próxima lectura.
clearBuf();
return 1;
}
return 0;
}
Claro, la función validFormat
servirá para leer únicamente una entrada y valida si el usuario cumple con el formato.
Es más, si el usuario intentara ingresar por ejemplo:
23.fd
La función scanf
solo tomará la parte entera que en este caso sería 23
y el resto de caracteres se quedarán en el búfer. Sin embargo, la función validFormat
lo detectará con la condición: getchar() != '\n'
y retornará 1
y esto significa que hubo un error.
Esta función también sirve para validar enteros, por esa razón el segundo parámetro es un puntero genérico.
La solución completa quedaría así:
//Cabeceras:
#include <stdio.h>
#include <stdlib.h>
//La declaración de cada función:
void clearBuf(void);
int validFormat(const char*, void*);
int main(void)
{
float a;
int result;
do
{
printf("Ingresa tu calificacion de matematicas (1.00 - 10.00):\n");
//Si la función retorna 1 fue porque el usuario no respetó el formato.
if(validFormat("%f", &a))
{
printf("Error: No puedes ingresar letras!\n");
//La sentencia continue hace que pasemos a la próxima iteración.
continue;
}
//Guardamos el resultado de la condición en la variable.
result = a < 1.00 || a > 10.00;
if (result) //Equivalente a (result != 0)
printf("Error: Ingrese una calificacion valida!\n");
} while(result); //Equivalente a (result != 0)
return 0;
}
int validFormat(const char* fm, void* a)
{
//Si la función scanf retorna 0, fue porque hubo una falla en la lectura..
if(!scanf(fm, a))
{
//Limpiamos el búfer del teclado para que la próxima lectura se lleve a cabo.
clearBuf();
return 1;
}
//Si no encontramos un salto de línea en el búfer, es porque el usuario no cumplió con el formato.
if(getchar() != '\n')
{
//Volvemos a limpiar el búfer para la próxima lectura.
clearBuf();
return 1;
}
return 0;
}
void clearBuf()
{
int ch;
//Limpia el búfer del teclado hasta encontrar un salto de línea o fin del archivo
while((ch = getchar()) != '\n' && ch != EOF);
}