Solicitando entrada de usuario en C

Soy nuevo en la progtwigción de C y encuentro que las solicitudes de entrada de los usuarios son un desafío para los principiantes como yo.

Solo estoy escribiendo un código C simple para solicitar continuamente la entrada del usuario hasta que ingrese un número negativo que detiene el progtwig. En cierto modo, quiero que el formato de solicitud de entrada del usuario sea similar al que solíamos hacer en Python donde:

Enter a number: (userinput a number here and press enter) 

y la salida será:

 The number you entered is 10 (for example) 

el cual aún no detiene el ciclo y solicita otro número ya que no se ingresó ningún número negativo.

 Enter a number: (userinput another number which is negative) 

Salida:

 The number you entered is -10 (for example) 

y el progtwig se detiene.

Intenté escribir en C:

 #include  int value = 0; while (value >= 0) { do { printf("Enter a number: "); scanf("%d",&value); printf("The number you entered is %d", &value); } 

pero parece que no puedo obtener la statement “Ingrese un número:” para mostrar primero cuando ejecuto el progtwig, ya que solicita de inmediato la entrada del usuario al comienzo sin que aparezca el mensaje de solicitud hasta que cuando ingrese un número entero, el mensaje de solicitud aparece que es lo contrario de lo que quería. Agradecería alguna ayuda en esto.

Uno de los mayores problemas que enfrentan los nuevos progtwigdores de C es el manejo correcto de las entradas del usuario, especialmente cuando se utiliza la familia de funciones scanf . ¿Por qué? Al usar scanf , debe tener en cuenta todos los caracteres que quedan en el búfer de entrada ( stdin here) en el caso de un error de coincidencia . ¿Por qué? Cuando se produce una falla coincidente , scanf deja de procesar los caracteres en el punto de la falla, y los caracteres que causaron la falla permanecen en su búfer de entrada sin leer , esperando a que lo muerda en su próximo bash de entrada.

Lo que complica aún más el problema es cómo los especificadores de conversión de scanf diferencia tratan los espacios en blanco. Sus especificadores de entrada numéricos y %s consumirán espacios en blanco iniciales, mientras que el rest de los especificadores de conversión no. Eso significa que si está tomando información con un especificador de conversión que no sea numérico o %s : debe tener en cuenta y eliminar el '\n' final antes de intentar la siguiente conversión de caracteres o clases de caracteres .

Dicho esto, siempre que use scanf , depende de usted verificar la devolución para que pueda determinar si el usuario canceló la entrada con un EOF generado manualmente o si se produjo una falla de entrada o coincidencia .

Como mínimo, debe verificar la devolución y manejar cualquier devolución que indique menos del número de conversiones previstas. En su caso, con una conversión a int , debe verificar que la devolución sea 1 antes de utilizar el value . De lo contrario, puede invocar fácilmente un comportamiento indefinido . Ahora, solo comprobar si todas las conversiones se produjeron no le permite discriminar entre EOF o error de emparejamiento , lo que le deja sin la información necesaria para continuar. Lo mejor que puede hacer es salir con una entrada no válida, por ejemplo.

 #include  int main (void) { int value = 0; while (value >= 0) { printf("Enter a number: "); if (scanf("%d",&value) != 1) { fputs ("error: invalid input\n", stderr); return 1; } printf("The number you entered is %d\n", value); } return 0; } 

Ejemplo de uso / salida

 $ ./bin/scanfpos2 Enter a number: 1 The number you entered is 1 Enter a number: 10 The number you entered is 10 Enter a number: foo error: invalid input 

( nota: en la entrada de un número entero no válido, todo lo que el progtwig puede hacer es terminar, no sabe si la entrada se canceló o si se produjo un error de coincidencia que le permitiría tomar las medidas adecuadas para continuar vaciando el búfer de entrada si el el fracaso fue un fallo coincidente .)

La manera correcta de manejar la entrada con scanf es cubrir todas las posibles condiciones de error y responder con gracia a una falla coincidente borrando el búfer de entrada de los caracteres ofensivos que le permiten continuar con la entrada. Es útil tener una pequeña función de ayuda para borrar la stdin lugar de tener que incluir repetidamente un ciclo de borrado en todos los lugares donde se usa scanf en su código. Un breve ejemplo sería:

 /** remove all characters that remain in stdin */ void empty_stdin (void) { int c = getchar(); while (c != '\n' && c != EOF) c = getchar(); } 

Que simplemente lee todos los caracteres de la stdin hasta que se encuentra el '\n' o EOF . Combinando eso con una verificación ampliada de la devolución de scanf le permitirá manejar una falla coincidente con gracia, por ejemplo

 #include  /** remove all characters that remain in stdin */ void empty_stdin (void) { int c = getchar(); while (c != '\n' && c != EOF) c = getchar(); } int main (void) { int value = 0; while (value >= 0) { int rtn; /* variable to store return on scanf */ printf ("Enter a number: "); rtn = scanf ("%d",&value); if (rtn == EOF) { /* handle EOF */ fputs ("user canceled input.\n", stderr); break; } else if (rtn == 0) { /* handle matching/input failure */ fputs ("error: invalid input\n", stderr); empty_stdin(); } else /* good input - output value */ printf("The number you entered is %d\n", value); } return 0; } 

Ejemplo de uso / salida

 $ ./bin/scanfpos2 Enter a number: 1 The number you entered is 1 Enter a number: 10 The number you entered is 10 Enter a number: foo error: invalid input Enter a number: -1 The number you entered is -1 

Aquí, si se ingresa un no-entero como foo , se captura, los caracteres eliminados de la stdin y el bucle solicita nuevamente la entrada.

Mira las cosas encima. C no es python. Python le oculta gran parte de los detalles de la implementación para protegerlo de las instancias de esta manera, pero también tiene sus inconvenientes. Con C, nada está oculto de ti. Se le da rienda suelta para escribir en la memoria que no posee, intenta repetidamente leer un búfer de entrada después de un error de conversión, etc. Usted es responsable de los detalles.

Por último, todo esto es la razón principal por la que se recomienda la entrada de datos con fgets o POSIX getline para los nuevos usuarios. Con un búfer de tamaño suficiente (no escatime en tamaño), fgets leerá una línea a la vez desde el búfer de entrada, evitando que los caracteres ofensivos queden a la espera de volver a morderlo. getline asignará un búfer de tamaño suficiente, independientemente de la longitud de la línea, pero usted es responsable de liberar la memoria cuando haya terminado con ella. Investigue ambos como alternativas al uso de scanf . Siempre puede llamar a sscanf en el búfer que contiene la línea después de leerla para analizar los valores numéricos de la misma.