¿Cómo borrar el búfer de entrada en C?

84

Tengo el siguiente programa:

int main(int argc, char *argv[])
{
  char ch1, ch2;
  printf("Input the first character:"); // Line 1
  scanf("%c", &ch1); 
  printf("Input the second character:"); // Line 2
  ch2 = getchar();

  printf("ch1=%c, ASCII code = %d\n", ch1, ch1);
  printf("ch2=%c, ASCII code = %d\n", ch2, ch2);

  system("PAUSE");  
  return 0;
}

Como ha explicado el autor del código anterior: El programa no funcionará correctamente porque en la Línea 1, cuando el usuario presione Enter, dejará en el búfer de entrada 2 el carácter: Enter key (ASCII code 13)y \n (ASCII code 10). Por lo tanto, en la Línea 2, leerá el \ny no esperará a que el usuario ingrese un carácter.

OK, tengo esto. Pero mi primera pregunta es: ¿Por qué el segundo getchar()( ch2 = getchar();) no lee el carácter Enter key (13), en lugar de \n?

A continuación, el autor propuso 2 formas de resolver tales problemas:

  1. utilizar fflush()

  2. escribe una función como esta:

    void
    clear (void)
    {    
      while ( getchar() != '\n' );
    }
    

Este código funcionó realmente. ¿Pero no puedo explicarme cómo funciona? Porque en la declaración while, usamos getchar() != '\n', eso significa leer cualquier carácter excepto '\n'? si es así, en el búfer de entrada aún permanece el '\n'carácter?

ipkiss
fuente

Respuestas:

89

El programa no funcionará correctamente porque en la Línea 1, cuando el usuario presiona Enter, dejará en el búfer de entrada 2 el carácter: Tecla Enter (código ASCII 13) y \ n (código ASCII 10). Por lo tanto, en la Línea 2, leerá \ n y no esperará a que el usuario ingrese un carácter.

El comportamiento que ves en la línea 2 es correcto, pero esa no es la explicación correcta. Con las transmisiones en modo texto, no importa qué finales de línea use su plataforma (ya sea retorno de carro (0x0D) + salto de línea (0x0A), un CR simple o un LF simple). La biblioteca de tiempo de ejecución de C se encargará de eso por usted: su programa verá solo las '\n'nuevas líneas.

Si escribió un carácter y presionó enter, entonces ese carácter ingresado se leerá en la línea 1 y luego '\n'se leerá en la línea 2. Vea Estoy usando scanf %cpara leer una respuesta S / N, pero la entrada posterior se salta.de las preguntas frecuentes de comp.lang.c.

En cuanto a las soluciones propuestas, consulte (nuevamente de las preguntas frecuentes de comp.lang.c):

que básicamente establecen que el único enfoque portátil es hacer:

int c;
while ((c = getchar()) != '\n' && c != EOF) { }

Su getchar() != '\n'ciclo funciona porque una vez que llama getchar(), el carácter devuelto ya ha sido eliminado del flujo de entrada.

Además, me siento obligado a disuadirlo de consumir por scanfcompleto: ¿Por qué todos dicen que no lo use scanf? ¿Qué debo usar en su lugar?

jamesdlin
fuente
1
Esto se colgará esperando que el usuario presione Entrar. Si simplemente desea borrar stdin, use termios directamente. stackoverflow.com/a/23885008/544099
ishmael
@ishmael Tu comentario no tiene sentido ya que no es necesario presionar Enter para borrar stdin; es necesario obtener información en primer lugar. (Y esa es la única forma estándar de leer la entrada en C. Si desea poder leer las claves de inmediato, tendrá que usar bibliotecas no estándar).
jamesdlin
@jamesdlin En Linux, getchar () esperará a que el usuario presione Enter. Solo entonces saldrán del bucle while. Hasta donde yo sé, termios es una biblioteca estándar.
ismael
@ishmael No, estás equivocado en ambos aspectos. Esta pregunta trata sobre cómo borrar la entrada restante de stdin después de leer la entrada, y en el estándar C, siempre que la entrada primero requiera escribir una línea y presionar Enter. Después de que se haya ingresado esa línea, getchar()leerá y devolverá esos caracteres; un usuario no necesitará presionar Enter en otro momento. Por otra parte, mientras que termios podrían ser comunes en Linux , no es parte de la biblioteca estándar C .
jamesdlin
¿Cuál es la razón para scanf(" %c", &char_var)trabajar e ignorar la nueva línea simplemente agregando un espacio antes del especificador% c?
Cătălina Sîrbu
44

Puedes hacerlo (también) de esta manera:

fseek(stdin,0,SEEK_END);
Ramy Al Zuhouri
fuente
Wow ... por cierto, ¿estándar dice que fseekestá disponible para stdin?
ikh
3
No lo sé, creo que no lo menciona, pero stdin es FILE * y fseek acepta un parámetro FILE *. Lo probé y funciona en Mac OS X pero no en Linux.
Ramy Al Zuhouri
Por favor explique. Sería una gran respuesta si el autor escribiera las implicaciones de este método. ¿Hay algún problema?
EvAlex
7
@EvAlex: hay muchos problemas con esto. Si funciona en su sistema, genial; si no, entonces no es sorprendente ya que nada garantiza que funcionará cuando la entrada estándar es un dispositivo interactivo (o un dispositivo que no se puede buscar como una tubería, un enchufe o un FIFO, por nombrar algunas otras formas en las que puede fallar).
Jonathan Leffler
¿Por qué debería ser SEEK_END? ¿Podemos usar SEEK_SET en su lugar? ¿Cómo se comporta el FP stdin?
Rajesh
11

Una forma portátil de borrar hasta el final de una línea que ya ha intentado leer parcialmente es:

int c;

while ( (c = getchar()) != '\n' && c != EOF ) { }

Esto lee y descarta caracteres hasta que obtiene lo \nque indica el final del archivo. También verifica EOFen caso de que el flujo de entrada se cierre antes del final de la línea. El tipo de cdebe ser int(o mayor) para poder mantener el valor EOF.

No hay una forma portátil de averiguar si hay más líneas después de la línea actual (si no las hay, getcharse bloqueará la entrada).

MM
fuente
por qué mientras ((c = getchar ())! = EOF); ¿No funciona? Es un bucle sin fin. No puedo entender la ubicación de EOF en stdin.
Rajesh
@Rajesh Eso se aclararía hasta que se cierre el flujo de entrada, lo cual no será así si hay más entradas más adelante
MM
@Rajesh Sí, tienes razón. Si no hay nada en stdin para vaciar cuando se llama a esto, se bloqueará (colgará) debido a que está atrapado en un bucle infinito.
Jason Enochs
11

Las líneas:

int ch;
while ((ch = getchar()) != '\n' && ch != EOF)
    ;

no lee solo los caracteres antes del salto de línea ('\n' ). Lee todos los caracteres de la secuencia (y los descarta) hasta el siguiente salto de línea (o se encuentra EOF) incluido . Para que la prueba sea verdadera, primero debe leer el salto de línea; así que cuando el bucle se detiene, el salto de línea fue el último carácter leído, pero se ha leído.

En cuanto a por qué lee un salto de línea en lugar de un retorno de carro, es porque el sistema ha traducido el retorno a un salto de línea. Cuando se presiona enter, eso indica el final de la línea ... pero la secuencia contiene un avance de línea en su lugar, ya que ese es el marcador normal de fin de línea para el sistema. Eso podría depender de la plataforma.

Además, el uso fflush()en un flujo de entrada no funciona en todas las plataformas; por ejemplo, generalmente no funciona en Linux.

Dmitri
fuente
Nota: while ( getchar() != '\n' );es un bucle infinito que debería getchar()regresar EOFdebido al final del archivo.
chux - Reincorporar a Monica
Para verificar EOF también, int ch; while ((ch = getchar()) != '\n' && ch != EOF);se puede usar
Dmitri
8

¿Pero no puedo explicarme cómo funciona? Porque en la instrucción while, usamos getchar() != '\n', eso significa leer cualquier carácter excepto '\n'?? si es así, en el búfer de entrada aún permanece el '\n'carácter ??? ¿Estoy entendiendo mal algo?

Lo que quizás no se dé cuenta es que la comparación ocurre después de getchar() eliminar el carácter del búfer de entrada. Entonces, cuando llegas al '\n', se consume y luego sales del bucle.

zwol
fuente
6

puedes probar

scanf("%c%*c", &ch1);

donde% * c acepta e ignora la nueva línea

un método más en lugar de fflush (stdin) que invoca un comportamiento indefinido que puede escribir

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

no olvides el punto y coma después del ciclo while

kapil
fuente
2
1) "%*c"escanea cualquier carácter (y no lo guarda), ya sea una nueva línea o algo más. El código se basa en que el segundo carácter es una nueva línea. 2) while((getchar())!='\n');es un bucle infinito que debería getchar()regresar EOFdebido al final del archivo.
chux - Reincorporar a Monica
1
el segundo método depende de la condición como nueva línea, carácter nulo, EOF, etc. (arriba era nueva línea)
kapil
1

Encuentro un problema al intentar implementar la solución.

while ((c = getchar()) != '\n' && c != EOF) { }

Publico un pequeño ajuste 'Código B' para cualquiera que tenga el mismo problema.

El problema fue que el programa me mantuvo capturando el carácter '\ n', independientemente del carácter de entrada, aquí está el código que me dio el problema.

Código A

int y;

printf("\nGive any alphabetic character in lowercase: ");
while( (y = getchar()) != '\n' && y != EOF){
   continue;
}
printf("\n%c\n", toupper(y));

y el ajuste fue 'capturar' el carácter (n-1) justo antes de que se evalúe el condicional en el ciclo while, aquí está el código:

Código B

int y, x;

printf("\nGive any alphabetic character in lowercase: ");
while( (y = getchar()) != '\n' && y != EOF){
   x = y;
}
printf("\n%c\n", toupper(x));

La posible explicación es que para que el bucle while se rompa, tiene que asignar el valor '\ n' a la variable y, por lo que será el último valor asignado.

Si me perdí algo con la explicación, el código A o el código B, por favor dígame, apenas soy nuevo en c.

espero que ayude a alguien

antadlp
fuente
Debes lidiar con tus caracteres " esperados " dentro del whileciclo. Después del bucle, puede considerar stdinque está limpio / despejado y ymantendrá ' \n' o EOF. EOFse devuelve cuando no hay una nueva línea presente y el búfer está agotado (si la entrada desbordaba el búfer antes de [ENTER]presionar). - Usas efectivamente el while((ch=getchar())!='\n'&&ch!=EOF);para masticar todos y cada uno de los caracteres en el stdinbúfer de entrada.
veganaiZe
0
unsigned char a=0;
if(kbhit()){
    a=getch();
    while(kbhit())
        getch();
}
cout<<hex<<(static_cast<unsigned int:->(a) & 0xFF)<<endl;

-o-

usar tal vez usar _getch_nolock().. ???

Michael Grieswald
fuente
La pregunta es sobre C, no sobre C ++.
Spikatrix
1
Tenga en cuenta que kbhit()y getch()son funciones declaradas <conio.h>y solo disponibles como estándar en Windows. Se pueden emular en máquinas similares a Unix, pero hacerlo requiere cierto cuidado.
Jonathan Leffler
0

Otra solución aún no mencionada es usar: rebobinar (stdin);

James azul
fuente
2
Eso romperá completamente el programa si stdin se redirige a un archivo. Y para la entrada interactiva no se garantiza que haga nada.
melpomene
0

Me sorprende que nadie haya mencionado esto:

scanf("%*[^\n]");
nowox
fuente
-1

Corto, portátil y declarado en stdio.h

stdin = freopen(NULL,"r",stdin);

No se cuelga en un bucle infinito cuando no hay nada en stdin para limpiar como la siguiente línea bien conocida:

while ((c = getchar()) != '\n' && c != EOF) { }

Un poco caro, así que no lo use en un programa que necesite borrar repetidamente el búfer.

Robó a un compañero de trabajo :)

Jason Enochs
fuente
No funciona en Linux. Utilice termios directamente en su lugar. stackoverflow.com/a/23885008/544099
ishmael
2
¿Podría detallar por qué la línea conocida es un posible bucle infinito?
Cacahuete Frito
La freopenúnica razón por la que existe es que no se puede asignar de forma portátil a stdin. Es una macro.
melpomene
1
ishmael: Todas nuestras computadoras aquí en Cyber ​​Command son Linux y funcionan bien en ellas. Lo probé tanto en Debian como en Fedora.
Jason Enochs
Lo que usted llama un "bucle infinito" es el programa que espera una entrada. Eso no es lo mismo.
jamesdlin
-1

Prueba esto:

stdin->_IO_read_ptr = stdin->_IO_read_end;

Inquisiciones
fuente
3
Agregue un poco más de detalles sobre cómo funciona esta solución y cómo es una forma útil de resolver el problema stackoverflow.com/help/how-to-answer
Suit Boy Apps