Wie lösche ich den Eingabepuffer in C?

82

Ich habe folgendes Programm:

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;
}

Wie der Autor des obigen Codes erklärt hat: Das Programm funktioniert nicht richtig, da es in Zeile 1, wenn der Benutzer die Eingabetaste drückt, im Eingabepuffer 2 Zeichen belässt: Enter key (ASCII code 13)und \n (ASCII code 10). Daher liest es in Zeile 2 das \nund wartet nicht darauf, dass der Benutzer ein Zeichen eingibt.

OK, ich habe das verstanden. Aber meine erste Frage ist: Warum liest die zweite getchar()( ch2 = getchar();) nicht das Zeichen Enter key (13), sondern \ndas?

Als nächstes schlug der Autor zwei Möglichkeiten vor, um solche Probleme zu lösen:

  1. verwenden fflush()

  2. schreibe eine Funktion wie diese:

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

Dieser Code hat tatsächlich funktioniert. Aber ich kann mir nicht erklären, wie es funktioniert? Weil in der while-Anweisung, die wir verwenden getchar() != '\n', bedeutet dies, dass Sie ein einzelnes Zeichen außer '\n'? wenn ja, bleibt im eingabepuffer noch das '\n'zeichen?

ipkiss
quelle

Antworten:

88

Das Programm funktioniert nicht ordnungsgemäß, da in Zeile 1, wenn der Benutzer die Eingabetaste drückt, im Eingabepuffer 2 Zeichen verbleiben: Eingabetaste (ASCII-Code 13) und \ n (ASCII-Code 10). Daher liest es in Zeile 2 das \ n und wartet nicht darauf, dass der Benutzer ein Zeichen eingibt.

Das Verhalten in Zeile 2 ist korrekt, aber das ist nicht ganz die richtige Erklärung. Bei Streams im Textmodus spielt es keine Rolle, welche Zeilenenden Ihre Plattform verwendet (ob Wagenrücklauf (0x0D) + Zeilenvorschub (0x0A), ein bloßer CR oder ein bloßer LF). Die C-Laufzeitbibliothek erledigt das für Sie: Ihr Programm sieht nur'\n' erledigt Zeilenumbrüche.

Wenn Sie ein Zeichen eingegeben und die Eingabetaste gedrückt haben, wird dieses Eingabezeichen in Zeile 1 und dann '\n'in Zeile 2 gelesen. Siehe Ich verwende scanf %czum Lesen einer J / N-Antwort, aber die spätere Eingabe wird übersprungen. aus den häufig gestellten Fragen zu comp.lang.c.

Informationen zu den vorgeschlagenen Lösungen finden Sie (erneut in den häufig gestellten Fragen zu comp.lang.c):

die im Grunde sagen, dass der einzige tragbare Ansatz zu tun ist:

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

Ihre getchar() != '\n'Schleife funktioniert, da nach dem Aufruf getchar()das zurückgegebene Zeichen bereits aus dem Eingabestream entfernt wurde.

Außerdem fühle ich mich verpflichtet, Sie davon abzuhalten, scanfganz zu verwenden: Warum sagen alle, nicht zu verwenden scanf? Was soll ich stattdessen verwenden?

Jamesdlin
quelle
1
Dies bleibt hängen und wartet darauf, dass der Benutzer die Eingabetaste drückt. Wenn Sie stdin einfach löschen möchten, verwenden Sie termios direkt. stackoverflow.com/a/23885008/544099
ishmael
@ishmael Dein Kommentar macht keinen Sinn, da das Drücken der Eingabetaste nicht erforderlich ist, um stdin zu löschen . Es ist zunächst erforderlich, Eingaben zu erhalten. (Und das ist die einzige Standardmethode zum Lesen von Eingaben in C. Wenn Sie Schlüssel sofort lesen möchten, müssen Sie nicht standardmäßige Bibliotheken verwenden.)
Jamesdlin
@jamesdlin Unter Linux wartet getchar () darauf, dass der Benutzer die Eingabetaste drückt. Nur dann brechen sie aus der while-Schleife aus. Termios ist meines Wissens eine Standardbibliothek.
Ishmael
@ishmael Nein, du liegst in beiden Punkten falsch. In dieser Frage geht es darum, wie Sie die verbleibende Eingabe nach dem Lesen der Eingabe aus stdin löschen können. In Standard C müssen Sie für die Eingabe zuerst eine Zeile eingeben und die Eingabetaste drücken. Nachdem diese Zeile eingegeben wurde, getchar()werden diese Zeichen gelesen und zurückgegeben. Ein Benutzer muss die Eingabetaste nicht ein anderes Mal drücken. Obwohl Termios unter Linux häufig vorkommen , sind sie nicht Teil der C-Standardbibliothek .
Jamesdlin
44

Sie können es (auch) so machen:

fseek(stdin,0,SEEK_END);
Ramy Al Zuhouri
quelle
Wow ... übrigens, ist Standard Say fseekverfügbar für stdin?
Ikh
3
Ich weiß es nicht, ich denke, es wird nicht erwähnt, aber stdin ist FILE * und fseek akzeptiert einen FILE * -Parameter. Ich habe es getestet und es funktioniert unter Mac OS X, aber nicht unter Linux.
Ramy Al Zuhouri
Bitte erkläre. Es wäre eine gute Antwort, wenn der Autor Implikationen dieser Methode schreiben würde. Gibt es irgendwelche Probleme?
EvAlex
6
@ EvAlex: Es gibt viele Probleme damit. Wenn es auf Ihrem System funktioniert, großartig; Wenn nicht, ist es nicht überraschend, da nichts garantiert, dass es funktioniert, wenn die Standardeingabe ein interaktives Gerät ist (oder ein nicht durchsuchbares Gerät wie eine Pipe oder eine Buchse oder ein FIFO, um nur einige andere Möglichkeiten zu nennen, wie es kann Scheitern).
Jonathan Leffler
Warum sollte es SEEK_END sein? Können wir stattdessen SEEK_SET verwenden? Wie verhält sich das FP stdin?
Rajesh
11

Eine tragbare Methode zum Löschen bis zum Ende einer Zeile, die Sie bereits teilweise gelesen haben, ist:

int c;

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

Dies liest und verwirft Zeichen, bis es \ndas Ende der Datei signalisiert. Es wird auch geprüft, EOFob der Eingabestream vor dem Zeilenende geschlossen wird. Der Typ von cmuss int(oder größer) sein, um den Wert halten zu können EOF.

Es gibt keine tragbare Möglichkeit, um herauszufinden, ob nach der aktuellen Zeile weitere Zeilen vorhanden sind (wenn dies nicht der Fall ist, getcharwird die Eingabe blockiert).

MM
quelle
warum while ((c = getchar ())! = EOF); funktioniert nicht? Es ist eine Endlosschleife. Nicht in der Lage, die Position von EOF in stdin zu verstehen.
Rajesh
@ Rajesh Das würde klar werden, bis der Eingabestream geschlossen wird, was nicht der Fall sein wird, wenn später mehr Eingaben kommen
MM
@ Rajesh Ja, du bist richtig. Wenn auf stdin nichts zu spülen ist, wenn dies aufgerufen wird, blockiert es (hängt), weil es in einer Endlosschleife gefangen wird.
Jason Enochs
11

Die Linien:

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

liest nicht nur die Zeichen vor dem Zeilenvorschub ( '\n'). Es liest alle Zeichen im Stream (und verwirft sie) bis einschließlich des nächsten Zeilenvorschubs (oder EOF wird angetroffen). Damit der Test wahr ist, muss zuerst der Zeilenvorschub gelesen werden. Wenn die Schleife stoppt, war der Zeilenvorschub das letzte gelesene Zeichen, aber es wurde gelesen.

Der Grund, warum ein Zeilenvorschub anstelle eines Wagenrücklaufs gelesen wird, liegt daran, dass das System den Zeilenvorschub in einen Zeilenvorschub übersetzt hat. Wenn die Eingabetaste gedrückt wird, signalisiert dies das Zeilenende ... aber der Stream enthält stattdessen einen Zeilenvorschub, da dies die normale Zeilenende-Markierung für das System ist. Das könnte plattformabhängig sein.

Die Verwendung fflush()in einem Eingabestream funktioniert nicht auf allen Plattformen. Zum Beispiel funktioniert es im Allgemeinen nicht unter Linux.

Dmitri
quelle
Hinweis: while ( getchar() != '\n' );ist eine Endlosschleife sollte getchar()zurückkehren EOFaufgrund End-of-Datei.
chux - Wiedereinsetzung Monica
Um auch nach EOF zu suchen, int ch; while ((ch = getchar()) != '\n' && ch != EOF);kann verwendet werden
Dmitri
8

Aber ich kann mir nicht erklären, wie es funktioniert? Weil wir in der while-Anweisung getchar() != '\n'ein einzelnes Zeichen außer '\n'?? lesen. wenn ja, bleibt im eingabepuffer noch das '\n'zeichen ??? Verstehe ich etwas falsch?

Möglicherweise stellen Sie nicht fest, dass der Vergleich erfolgt, nachdem getchar() das Zeichen aus dem Eingabepuffer entfernt wurde. Also , wenn Sie das erreichen '\n', ist es verbraucht und dann brechen Sie aus der Schleife heraus.

zwol
quelle
6

Du kannst es versuchen

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

Dabei akzeptiert% * c die neue Zeile und ignoriert sie

eine weitere Methode anstelle von fflush (stdin), die undefiniertes Verhalten aufruft, das Sie schreiben können

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

Vergessen Sie nicht das Semikolon nach der while-Schleife

kapil
quelle
2
1) "%*c" scannt ein beliebiges Zeichen (und speichert es nicht), sei es eine neue Zeile oder etwas anderes. Code setzt voraus, dass das 2. Zeichen eine neue Zeile ist. 2) while((getchar())!='\n');ist eine unendliche Schleife sollte getchar()zurückkehren EOFaufgrund end of file.
chux
1
Die zweite Methode hängt von der Bedingung wie Zeilenumbruch, Nullzeichen, EOF usw. ab (darüber war Zeilenumbruch)
kapil
1
unsigned char a=0;
if(kbhit()){
    a=getch();
    while(kbhit())
        getch();
}
cout<<hex<<(static_cast<unsigned int:->(a) & 0xFF)<<endl;

-oder-

benutze vielleicht benutze _getch_nolock().. ???

Michael Grieswald
quelle
Die Frage betrifft C, nicht C ++.
Spikatrix
1
Beachten Sie, dass kbhit()und getch()Funktionen deklariert sind <conio.h>und nur unter Windows als Standard verfügbar sind. Sie können auf Unix-ähnlichen Computern emuliert werden, dies erfordert jedoch einige Sorgfalt.
Jonathan Leffler
1

Beim Versuch, die Lösung zu implementieren, tritt ein Problem auf

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

Ich poste eine kleine Anpassung 'Code B' für alle, die vielleicht das gleiche Problem haben.

Das Problem war, dass ich mit dem Programm das Zeichen '\ n' unabhängig vom Eingabezeichen abfing. Hier ist der Code, der mir das Problem gab.

Code A.

int y;

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

und die Anpassung bestand darin, das (n-1) -Zeichen kurz vor der Auswertung der Bedingung in der while-Schleife zu "fangen". Hier ist der Code:

Code 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));

Die mögliche Erklärung ist, dass die while-Schleife, um zu unterbrechen, der Variablen y den Wert '\ n' zuweisen muss, damit sie der zuletzt zugewiesene Wert ist.

Wenn ich etwas mit der Erklärung, Code A oder Code B verpasst habe, sag mir bitte, ich bin kaum neu in c.

hoffe es hilft jemandem

antadlp
quelle
Sie sollten sich mit Ihren " erwarteten " Zeichen innerhalb der whileSchleife befassen . Nach der Schleife können Sie prüfen , stdinum sauber / klar und yhalten wird entweder ‚ \noder‘ EOF. EOFwird zurückgegeben, wenn kein Zeilenumbruch vorhanden ist und der Puffer erschöpft ist (wenn die Eingabe den Puffer überlaufen würde, bevor er [ENTER]gedrückt wurde). - Sie verwenden das effektiv while((ch=getchar())!='\n'&&ch!=EOF);, um jedes einzelne Zeichen im stdinEingabepuffer zu zerkauen .
veganaiZe
1

Eine andere noch nicht erwähnte Lösung ist die Verwendung von: Rücklauf (stdin);

James Blue
quelle
2
Dadurch wird das Programm vollständig unterbrochen, wenn stdin in eine Datei umgeleitet wird. Und für interaktive Eingaben ist garantiert nichts zu tun.
Melpomene
0

Versuche dies:

stdin->_IO_read_ptr = stdin->_IO_read_end;

Inquisitionen
quelle
3
Bitte fügen Sie weitere Details zur Funktionsweise dieser Lösung und zur nützlichen Lösung des Problems hinzu. Stackoverflow.com/help/how-to-answer
Suit Boy Apps
-2

Kurz, tragbar und in stdio.h deklariert

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

Wird nicht in einer Endlosschleife aufgehängt, wenn auf stdin nichts zu spülen ist, wie in der folgenden bekannten Zeile:

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

Ein wenig teuer, verwenden Sie es also nicht in einem Programm, das den Puffer wiederholt löschen muss.

Von einem Kollegen gestohlen :)

Jason Enochs
quelle
Funktioniert nicht unter Linux. Verwenden Sie Termios stattdessen direkt. stackoverflow.com/a/23885008/544099
ishmael
2
Können Sie näher erläutern, warum die bekannte Linie eine mögliche Endlosschleife ist?
Cacahuete Frito
Der ganze Grund freopenist, dass Sie nicht portabel zuweisen können stdin. Es ist ein Makro.
Melpomene
ishmael: Alle unsere Computer hier in Cyber ​​Command sind Linux und es funktioniert gut auf ihnen. Ich habe es sowohl auf Debian als auch auf Fedora getestet.
Jason Enochs