1

Tengo un problema, quiero hacer que el usuario ingrese un dígito del 1.00 al 10.00 y que si ingresa un dígito fuera de ese rango le mencione que se ha equivocado y lo intente de nuevo. Creo yo que lo he hecho bien pero el problema es que si el usuario ingresa una letra, el bucle se repite de forma infinita, agradecería su ayuda. Gracias. :)

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

int main()
{
    float a;

    do
    {
        printf("Ingresa tu calificacion de matematicas (1.00 - 10.00):\n");
        scanf("%f", &a);

        if (a < 1.00 || a > 10.0)
        {
            printf("Ingresa una calificación valida\n");
        }
    } while (a < 1.00 || a > 10.00);

    system("pause");
    return 0;
}

MrDave1999
  • 7,491
  • 1
  • 7
  • 22
  • 2
    Debes validar que solo ingrese números. – Bicho Jul 23 '20 at 20:38
  • El usuario introduce la letra X en lugar de un número. ¿Qué valor asumirá `a`? Será menos de 1 o más de 10? Lo que es peor, el programa no consuma la letra X, porque no es un número. Entonces, en la próxima iteración encontrará la letra otra vez, y señalará el error otra vez. Y otra vez y otra vez... Ver dos métodos diferentes [uno](https://es.stackoverflow.com/questions/375760/bucle-infinito-si-se-introduce-una-letra-en-lugar-de-un-numero) [dos](https://stackoverflow.com/questions/54296060/i-am-trying-to-validate-the-user-input-but-if-i-entered-an-invalid-character-th) – n. 1.8e9-where's-my-share m. Jul 23 '20 at 21:05

2 Answers2

4

¿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);
}
MrDave1999
  • 7,491
  • 1
  • 7
  • 22
-1

Este problema es muy frecuente cuando inicias a programar... básicamente lo que hay que hacer es leer bien que hace la función scanf adicionalmente tener muy en cuenta que es lo que retorna.

La funciona scanf retorna el numero de caracteres leídos de acuerdo a la máscara definida, en tu caso la función debería retornar 0, por que los valores leídos no concuerdan con la máscara, en este caso leer un carácter no concuerda con lo esperado y retorna 0.

leido = scanf("%f", &a);
if (a < 1.00 || a > 10.0 || leido == 0)
{
  fflush(stdin);
  printf("Ingresa una calificación valida\n");
}

Entonces ya tienes una forma de validar si la entrada leída esta OK, ahora es necesario para evitar el bucle limpiar el flujo de entrada, esto lo puedes lograr con la función fflush, si no se hace no importa que hayas podido identificar que no te digitaron un número, la función scanf seguirá fallando y produciendo el bucle infinito.

Jonnathan Q
  • 522
  • 3
  • 7
  • No, la función [fflush](http://www.cplusplus.com/reference/cstdio/fflush/) no limpia el búfer **stdin**, sino, el **stdout**. – MrDave1999 Jul 23 '20 at 23:11
  • @MrDave1999 Si y No, la función de acuerdo a la documentación esta concebida para el **stdout**, sin embargo es posible usarla para el **stdin**, lo que ayuda a resolver el problema del bucle. – Jonnathan Q Jul 23 '20 at 23:17
  • @JonnathanQ ¿Como declaro "ledio"?, lo siento si hago estas preguntas pues justo estoy comenzando y la universidad no ayuda mucho con documentación, gracias.. – Agustin Medrano Jul 23 '20 at 23:39
  • @MrDave1999 Ahora ha surgido otro problema, cuando digito una letra o un numero fuera de rango ya no se provoca el bucle infinito y todo va bien pero al poner un numero dentro del rango el programa no hace nada a menos de que vuelvas a poner un numero o letra para que continue con el ```system("pause")```. ): – Agustin Medrano Jul 23 '20 at 23:55
  • @JonnathanQ ¿Dónde puedo ver la documentación oficial de C++? –  Jul 24 '20 at 05:48
  • 1
    @JonnathanQ una solución que funciona solo *a veces* no es una respuesta sólida. En ningún momento te han dicho qué compilador se está usando, luego no puedes garantizar en que con `fflush(stdin)` el OP vaya a obtener el resultado esperado. – eferion Jul 24 '20 at 06:24