Validar el tipo de entrada en un bucle do- while
Básicamente, necesito asegurarme de que la entrada sea un número entero , así:
do {
printf("Enter > ");
scanf("%d", &integer);
} while (/* user entered a char instead of an int */);
He probado varios métodos, pero siempre termina con un error de tiempo de ejecución o un bucle infinito cuando intento ingresar un archivo char
. Sabía que fflush(stdin)
es un comportamiento indefinido, por lo que es mejor no incluirlo en mi código para evitar cualquier error, además ya no funciona en VS2015 por algunas razones.
Los códigos a continuación son el método que he probado:
typedef enum {false, true} bool;
int ipt;
char c;
bool wrong_ipt;
do {
c = '\0';
printf("Enter > ");
scanf("%d%c", &ipt, &c); //infinite loop occurs while a char has been entered
} while (c != '\n');
do {
c = '\0';
printf("Enter > ");
} while (scanf("%d", &ipt) != EOF);
do {
wrong_ipt = false;
do {
ipt = NULL;
printf("Enter > ");
scanf("%d", &ipt);
if (ipt == NULL) {
wrong_ipt = true;
break;
}
} while (ipt == NULL);
} while (wrong_ipt);
¿Hay alguna otra forma además de fflush(stdin)
cuál pueda usarse para evitar el bucle infinito cuando el usuario ingresa a char
en C ?
Gracias
El problema es que "scanf()" puede dejar datos no leídos en su búfer de entrada. De ahí el "bucle infinito".
Otro problema es que debes validar el valor de retorno de scanf()
. Si espera un valor entero... y scanf devuelve "0" elementos leídos... entonces sabe que algo salió mal.
Aquí hay un ejemplo:
#include <stdio.h>
void discard_junk ()
{
int c;
while((c = getchar()) != '\n' && c != EOF)
;
}
int main (int argc, char *argv[])
{
int integer, i;
do {
printf("Enter > ");
i = scanf("%d", &integer);
if (i == 1) {
printf ("Good value: %d\n", integer);
}
else {
printf ("BAD VALUE, i=%i!\n", i);
discard_junk ();
}
} while (i != 1);
return 0;
}
Salida de muestra:
Enter > A
BAD VALUE, i=0!
Enter > B
BAD VALUE, i=0!
Enter > 1
Good value: 1
'¡Espero que ayude!
Este es un muy buen ejemplo de por qué scanf
generalmente no debería usarse para la entrada del usuario.
Dado que la entrada del usuario se basa en líneas, uno esperaría que una función de entrada siempre leyera una línea de entrada a la vez. Sin embargo, esa no es la forma en que scanf
se comporta la función. En cambio, consume sólo tantos caracteres como sean necesarios para coincidir con el %d
especificador de formato de conversión. Si scanf
no puede coincidir con nada, entonces no consume ningún carácter, por lo que la siguiente llamada scanf
fallará exactamente por el mismo motivo (suponiendo que se utilice el mismo especificador de conversión y que usted no descarte explícitamente la entrada no válida). ). Esto es lo que está sucediendo en tu código.
Al momento de escribir este artículo, tres de las otras respuestas resuelven este problema verificando el valor de retorno scanf
y descartando explícitamente la entrada no válida. Sin embargo, las tres (!) respuestas tienen el problema de que, por ejemplo, aceptan "6sdfj23jlj"
como entrada válida el número 6
, aunque obviamente toda la línea de entrada debería rechazarse en este caso. Esto se debe a que scanf
, como se mencionó anteriormente, no lee una línea de entrada a la vez.
Por lo tanto, la mejor solución a su problema probablemente sería utilizar la entrada basada en líneas fgets
en su lugar. De esa manera, siempre leerá exactamente una línea de entrada a la vez (suponiendo que el búfer de entrada sea lo suficientemente grande como para almacenar una línea completa de entrada). Después de leer la línea, puedes intentar convertirla en un número usando strtol
. Incluso si la conversión falla, la línea de entrada se habrá consumido del flujo de entrada, por lo que no tendrá la mayoría de los problemas descritos anteriormente.
Una solución simple fgets
podría verse así:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main( void )
{
char line[100];
long number;
//retry until user enters valid input
for (;;) //infinite loop, equivalent to while(1)
{
char *p;
//prompt user for input
printf( "Please enter a number: " );
//attempt to read one line of input
if ( fgets( line, sizeof line, stdin ) == NULL )
{
fprintf( stderr, "Unrecoverable input error!\n" );
exit( EXIT_FAILURE );
}
//attempt to convert input to number
number = strtol( line, &p, 10 );
//verify that conversion was successful
if ( p == line )
{
printf( "Invalid input!\n" );
continue;
}
//verify that remainder of line only contains
//whitespace, so that input such as "6sdfj23jlj"
//gets rejected
for ( ; *p != '\0'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "Encountered invalid character!\n" );
//cannot use `continue` here, because that would go to
//the next iteration of the innermost loop, but we
//want to go to the next iteration of the outer loop
goto continue_outer_loop;
}
}
//input was valid, so break out of infinite loop
break;
//label for breaking out of nested loop
continue_outer_loop:
continue;
}
printf( "Input was valid.\n" );
printf( "The number is: %ld\n", number );
return 0;
}
Tenga en cuenta que la goto
declaración generalmente no debe usarse. Sin embargo, en este caso, es necesario para salir de un bucle anidado.
Este programa tiene el siguiente resultado:
Please enter a number: 94hjj
Encountered invalid character!
Please enter a number: 5455g
Encountered invalid character!
Please enter a number: hkh7
Invalid input!
Please enter a number: 6sdfj23jlj
Encountered invalid character!
Please enter a number: 67
Input was valid.
The number is: 67
Sin embargo, este código aún no es perfecto. Todavía tiene los siguientes problemas:
Si el usuario ingresa 100 caracteres en una sola línea, entonces toda la línea no cabe en el búfer de entrada. En ese caso, se necesitarán dos llamadas
fgets
para leer la línea completa y el programa tratará incorrectamente esa línea como dos líneas de entrada separadas.El código no verifica si el número que el usuario ingresó se puede representar como
long
(por ejemplo, si el número es excesivamente grande). La funciónstrtol
informa esto configurándoseerrno
en consecuencia (que es una característica de la quescanf
carece).
Estos dos problemas también se pueden solucionar realizando comprobaciones adicionales y manejo de errores. Sin embargo, el código se está volviendo tan complejo que tiene sentido ponerlo todo en su propia función:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
int get_int_from_user( const char *prompt )
{
//loop forever until user enters a valid number
for (;;)
{
char buffer[1024], *p;
long l;
//prompt user for input
fputs( prompt, stdout );
//get one line of input from input stream
if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
{
fprintf( stderr, "Unrecoverable input error!\n" );
exit( EXIT_FAILURE );
}
//make sure that entire line was read in (i.e. that
//the buffer was not too small)
if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
{
int c;
printf( "Line input was too long!\n" );
//discard remainder of line
do
{
c = getchar();
if ( c == EOF )
{
fprintf( stderr, "Unrecoverable error reading from input!\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' );
continue;
}
//attempt to convert string to number
errno = 0;
l = strtol( buffer, &p, 10 );
if ( p == buffer )
{
printf( "Error converting string to number!\n" );
continue;
}
//make sure that number is representable as an "int"
if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
{
printf( "Number out of range error!\n" );
continue;
}
//make sure that remainder of line contains only whitespace,
//so that input such as "6sdfj23jlj" gets rejected
for ( ; *p != '\0'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "Unexpected input encountered!\n" );
//cannot use `continue` here, because that would go to
//the next iteration of the innermost loop, but we
//want to go to the next iteration of the outer loop
goto continue_outer_loop;
}
}
return l;
continue_outer_loop:
continue;
}
}
int main( void )
{
int number;
number = get_int_from_user( "Please enter a number: " );
printf( "Input was valid.\n" );
printf( "The number is: %d\n", number );
return 0;
}