Come cancellare il buffer di input in C?


87

Ho il seguente programma:

Come ha spiegato l'autore del codice sopra: Il programma non funzionerà correttamente perché alla Riga 1, quando l'utente preme Invio, lascerà nel buffer di input 2 caratteri: Enter key (ASCII code 13)e \n (ASCII code 10). Pertanto, alla riga 2, leggerà \ne non aspetterà che l'utente inserisca un carattere.

OK, ho capito. Ma la mia prima domanda è: perché la seconda getchar()( ch2 = getchar();) non legge il carattere Enter key (13), invece del \ncarattere?

Successivamente, l'autore ha proposto 2 modi per risolvere tali problemi:

  1. uso fflush()

  2. scrivi una funzione come questa:

Questo codice ha funzionato effettivamente. Ma non riesco a spiegarmi come funziona? Perché nell'istruzione while, che usiamo getchar() != '\n', ciò significa leggere un singolo carattere tranne '\n'? in caso affermativo, nel buffer di input rimane ancora il '\n'carattere?

Risposte:


92

Il programma non funzionerà correttamente perché alla riga 1, quando l'utente preme Invio, lascerà nel buffer di input 2 caratteri: tasto Invio (codice ASCII 13) e \ n (codice ASCII 10). Pertanto, alla riga 2, leggerà \ ne non aspetterà che l'utente inserisca un carattere.

Il comportamento che vedi alla riga 2 è corretto, ma non è proprio la spiegazione corretta. Con i flussi in modalità testo, non importa quali terminazioni di riga utilizza la tua piattaforma (se ritorno a capo (0x0D) + avanzamento riga (0x0A), un semplice CR o un semplice LF). La libreria runtime C si prenderà cura di questo per te: il tuo programma vedrà solo le '\n'nuove righe.

Se si digita un carattere e si preme Invio, il carattere di input verrà letto dalla riga 1 e quindi '\n'dalla riga 2. Vedere Sto usando scanf %cper leggere una risposta S / N, ma l'input successivo viene saltato. dalle FAQ di comp.lang.c.

Per quanto riguarda le soluzioni proposte, vedere (sempre dalla FAQ comp.lang.c):

che sostanzialmente afferma che l'unico approccio portatile è fare:

Il tuo getchar() != '\n' ciclo funziona perché una volta chiamato getchar(), il carattere restituito è già stato rimosso dal flusso di input.

Inoltre, mi sento obbligato a scoraggiarti dall'usare scanfinteramente: perché tutti dicono di non usare scanf? Cosa dovrei usare invece?


1
Questo si bloccherà in attesa che l'utente prema Invio. Se vuoi semplicemente cancellare lo stdin, usa direttamente termios. stackoverflow.com/a/23885008/544099
Ishmael

@ishmael Il tuo commento non ha senso poiché non è necessario premere Invio per cancellare lo stdin; è necessario per ottenere l'input in primo luogo. (E questo è l'unico modo standard per leggere l'input in C. Se vuoi essere in grado di leggere le chiavi immediatamente, dovrai usare librerie non standard.)
jamesdlin

@jamesdlin Su Linux, getchar () attenderà che l'utente prema Invio. Solo allora usciranno dal ciclo while. Per quanto ne so, termios è una libreria standard.
ishmael

@ishmael No, ti sbagli su entrambi i fronti. Questa domanda riguarda come cancellare l'input rimanente dallo stdin dopo aver letto l'input e nello standard C, a condizione che l'input richieda prima di digitare una riga e premere Invio. Dopo che quella riga è stata inserita, getchar()leggerà e restituirà quei caratteri; un utente non dovrà premere Invio un'altra volta. Inoltre, mentre termios potrebbero essere comuni su Linux , non fa parte della libreria standard C .
jamesdlin

qual è la ragione per scanf(" %c", &char_var)lavorare e ignorare la nuova riga semplicemente aggiungendo uno spazio prima dello specificatore% c?
Cătălina Sîrbu,

44

Puoi farlo (anche) in questo modo:


Wow ... btw, lo standard dice che fseekè disponibile per stdin?
ikh

3
Non lo so, penso che non lo menzioni, ma stdin è FILE * e fseek accetta un parametro FILE *. L'ho testato e funziona su Mac OS X ma non su Linux.
Ramy Al Zuhouri

Spiega per favore. Sarebbe un'ottima risposta se l'autore scrivesse le implicazioni di questo metodo. Ci sono problemi?
EvAlex

7
@EvAlex: ci sono molti problemi con questo. Se funziona sul tuo sistema, bene; in caso contrario, non è sorprendente in quanto nulla garantisce che funzioni quando lo standard input è un dispositivo interattivo (o un dispositivo non ricercabile come una pipe o un socket o un FIFO, per citarne solo alcuni altri modi in cui può fallire).
Jonathan Leffler

Perché dovrebbe essere SEEK_END? Possiamo usare SEEK_SET invece? Come si comporta lo stdin FP?
Rajesh

11

Un modo portatile per cancellare fino alla fine di una riga che hai già provato a leggere parzialmente è:

Questo legge e scarta i caratteri finché non ottiene \nche segnala la fine del file. Controlla anche EOFnel caso in cui il flusso di input venga chiuso prima della fine della riga. Il tipo di cdeve essere int(o maggiore) per poter contenere il valore EOF.

Non esiste un modo portatile per scoprire se ci sono altre righe dopo la riga corrente (se non ci sono, getcharbloccherà l'input).


perché while ((c = getchar ())! = EOF); non funziona? È un ciclo infinito. Non in grado di capire la posizione di EOF in stdin.
Rajesh

@Rajesh Ciò verrebbe cancellato fino alla chiusura del flusso di input, cosa che non sarà se ci saranno più input in arrivo
MM

@Rajesh Sì, hai ragione. Se non c'è nulla su stdin da scaricare quando viene chiamato, si bloccherà (si bloccherà) perché viene catturato in un ciclo infinito.
Jason Enochs

11

Le linee:

non legge solo i caratteri prima dell'avanzamento riga ( '\n'). Legge tutti i caratteri nel flusso (e li scarta) fino a includere successivo avanzamento riga (o viene rilevato EOF) . Affinché il test sia vero, deve prima leggere il linefeed; così quando il ciclo si ferma, il linefeed era l'ultimo carattere letto, ma è stato letto.

Per quanto riguarda il motivo per cui legge un avanzamento riga invece di un ritorno a capo, è perché il sistema ha tradotto il ritorno in un avanzamento riga. Quando si preme Invio, ciò segnala la fine della riga ... ma lo stream contiene invece un avanzamento riga poiché è il normale indicatore di fine riga per il sistema. Potrebbe dipendere dalla piattaforma.

Inoltre, l'utilizzo fflush()su un flusso di input non funziona su tutte le piattaforme; per esempio generalmente non funziona su Linux.


Nota: while ( getchar() != '\n' );è un ciclo infinito dovrebbe getchar()tornare a EOFcausa della fine del file.
chux - Ripristina Monica il

Per verificare anche l'EOF, int ch; while ((ch = getchar()) != '\n' && ch != EOF);può essere utilizzato
Dmitri

8

Ma non riesco a spiegarmi come funziona? Perché nell'istruzione while, che usiamo getchar() != '\n', ciò significa leggere ogni singolo carattere tranne '\n'?? in caso affermativo, nel buffer di input rimane ancora il '\n'carattere ??? Sto fraintendendo qualcosa ??

La cosa che potresti non capire è che il confronto avviene dopo aver getchar() rimosso il carattere dal buffer di input. Quindi quando raggiungi il '\n', viene consumato e poi esci dal ciclo.


6

Puoi provare

dove% * c accetta e ignora la nuova riga

un altro metodo invece di fflush (stdin) che invoca un comportamento non definito che puoi scrivere

non dimenticare il punto e virgola dopo il ciclo while


2
1) "%*c"esegue la scansione di qualsiasi carattere (e non lo salva), sia esso una nuova riga o qualcos'altro. Il codice si basa sul fatto che il secondo carattere è una nuova riga. 2) while((getchar())!='\n');è un ciclo infinito che dovrebbe getchar()tornare a EOFcausa della fine del file.
chux - Ripristina Monica il

1
il secondo metodo dipende dalla condizione come nuova riga, carattere nullo, EOF ecc. (sopra era una nuova riga)
kapil

1

Ho riscontrato un problema nel tentativo di implementare la soluzione

Inserisco un piccolo aggiustamento "Codice B" per chiunque abbia lo stesso problema.

Il problema era che il programma mi impediva di catturare il carattere "\ n", indipendentemente dal carattere di immissione, ecco il codice che mi ha dato il problema.

Codice A

e l'aggiustamento era quello di 'catturare' il carattere (n-1) appena prima che il condizionale nel ciclo while venisse valutato, ecco il codice:

Codice B

La possibile spiegazione è che perché il ciclo while si interrompa, deve assegnare il valore '\ n' alla variabile y, quindi sarà l'ultimo valore assegnato.

Se mi sono perso qualcosa con la spiegazione, il codice A o il codice B per favore dimmelo, sono appena nuovo in c.

spero che aiuti qualcuno


Dovresti gestire i tuoi caratteri " attesi " all'interno del whileciclo. Dopo il ciclo puoi considerare stdindi essere pulito / chiaro e ymanterrai ' \n' o EOF. EOFviene restituito quando non è presente una nuova riga e il buffer è esaurito (se l'input andasse in overflow il buffer prima di [ENTER]essere premuto). - Utilizzi efficacemente il while((ch=getchar())!='\n'&&ch!=EOF);per masticare ogni carattere nel stdinbuffer di input.
veganaiZe

0

-o-

usa forse usa _getch_nolock().. ???


La domanda riguarda C, non C ++.
Spikatrix

1
Notare che kbhit()e getch()sono funzioni dichiarate <conio.h>e disponibili solo come standard su Windows. Possono essere emulati su macchine simili a Unix, ma farlo richiede una certa attenzione.
Jonathan Leffler

0

Un'altra soluzione non ancora menzionata è usare: rewind (stdin);


2
Ciò interromperà completamente il programma se stdin viene reindirizzato a un file. E per l'input interattivo non è garantito che faccia nulla.
melpomene

0

Sono sorpreso che nessuno lo abbia menzionato:


-1

Breve, portatile e dichiarato in stdio.h

Non si blocca in un ciclo infinito quando non c'è nulla su stdin da scaricare come la seguente riga ben nota:

Un po 'costoso, quindi non usarlo in un programma che ha bisogno di cancellare ripetutamente il buffer.

Rubato a un collega :)


Non funziona su Linux. Usa invece termios direttamente. stackoverflow.com/a/23885008/544099
Ishmael

2
Potresti spiegare in dettaglio perché la linea ben nota è un possibile loop infinito?
alx

L'intera ragione freopenesiste è che non puoi assegnare in modo portabile a stdin. È una macro.
melpomene

1
ishmael: Tutti i nostri computer qui in Cyber ​​Command sono Linux e funziona bene su di loro. L'ho testato sia su Debian che su Fedora.
Jason Enochs

Quello che chiamate un "loop infinito" è il programma in attesa di input. Non è la stessa cosa.
jamesdlin,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.