Jak wyczyścić bufor wejściowy w C?

84

Mam następujący program:

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

Jak wyjaśnił autor powyższego kodu: Program nie będzie działał poprawnie, ponieważ w linii 1, gdy użytkownik naciśnie klawisz Enter, pozostawi w buforze wejściowym 2 znaki: Enter key (ASCII code 13)i \n (ASCII code 10). Dlatego w linii 2 odczyta \ni nie będzie czekać, aż użytkownik wprowadzi znak.

OK, rozumiem. Ale moje pierwsze pytanie brzmi: dlaczego druga getchar()( ch2 = getchar();) nie czyta Enter key (13), a nie \nznaku?

Następnie autor zaproponował 2 sposoby rozwiązania takich problemów:

  1. posługiwać się fflush()

  2. napisz funkcję taką jak ta:

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

Ten kod faktycznie działał. Ale nie potrafię sobie wytłumaczyć, jak to działa? Ponieważ w instrukcji while używamy getchar() != '\n', oznacza to przeczytanie dowolnego pojedynczego znaku z wyjątkiem '\n'? jeśli tak, to w buforze wejściowym nadal pozostaje '\n'znak?

ipkiss
źródło

Odpowiedzi:

89

Program nie będzie działał poprawnie, ponieważ w wierszu 1 użytkownik naciśnie klawisz Enter, pozostawi w buforze wejściowym 2 znaki: klawisz Enter (kod ASCII 13) i \ n (kod ASCII 10). Dlatego w linii 2 odczyta \ n i nie będzie czekać, aż użytkownik wprowadzi znak.

Zachowanie, które widzisz w linii 2, jest poprawne, ale to nie jest do końca poprawne wyjaśnienie. W przypadku strumieni w trybie tekstowym nie ma znaczenia, jakich zakończeń wierszy używa Twoja platforma (czy powrót karetki (0x0D) + wysuw wiersza (0x0A), nieosłonięta CR, czy czysty LF). Biblioteka środowiska wykonawczego C zajmie się tym za Ciebie: Twój program będzie widział tylko '\n'nowe linie.

Jeśli wpiszesz znak i naciśniesz klawisz Enter, ten znak wejściowy zostanie odczytany w wierszu 1, a następnie w '\n'wierszu 2. Zobacz sekcję Używam scanf %cdo odczytywania odpowiedzi T / N, ale później dane wejściowe są pomijane. z FAQ comp.lang.c.

Jeśli chodzi o proponowane rozwiązania, zobacz (ponownie z FAQ comp.lang.c):

które zasadniczo stwierdzają, że jedyne przenośne podejście polega na:

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

Twoja getchar() != '\n'pętla działa, ponieważ po wywołaniu getchar()zwrócony znak został już usunięty ze strumienia wejściowego.

Czuję się również zobowiązany zniechęcić Cię do scanfcałkowitego używania : Dlaczego wszyscy mówią, że nie należy używać scanf? Czego powinienem użyć zamiast tego?

jamesdlin
źródło
1
To zawiesi się, czekając, aż użytkownik naciśnie Enter. Jeśli chcesz po prostu wyczyścić stdin, użyj bezpośrednio termios. stackoverflow.com/a/23885008/544099
ishmael
@ishmael Twój komentarz nie ma sensu, ponieważ naciśnięcie Enter nie jest wymagane do wyczyszczenia stdin; najpierw trzeba uzyskać dane wejściowe. (I to jedyny standardowy sposób odczytu danych wejściowych w C. Jeśli chcesz mieć możliwość natychmiastowego odczytu kluczy, musisz użyć niestandardowych bibliotek.)
jamesdlin
@jamesdlin W systemie Linux getchar () będzie czekać, aż użytkownik naciśnie Enter. Dopiero wtedy wyjdą z pętli while. O ile mi wiadomo, termios to standardowa biblioteka.
ishmael
@ishmael Nie, mylisz się w obu przypadkach. To pytanie dotyczy wyczyszczenia pozostałego wejścia ze standardowego wejścia po przeczytaniu wejścia, aw standardowym C, pod warunkiem, że wejście wymaga najpierw wpisania linii i naciśnięcia Enter. Po wprowadzeniu tej linii getchar()odczyta i zwróci te znaki; użytkownik nie będzie musiał ponownie naciskać klawisza Enter. Ponadto podczas termios może być wspólne w systemie Linux , nie jest częścią biblioteki standardowej C .
jamesdlin
Jaki jest powód scanf(" %c", &char_var)pracy i ignorowania znaku nowej linii przez dodanie spacji przed specyfikatorem% c?
Cătălina Sîrbu
44

Możesz to (również) zrobić w ten sposób:

fseek(stdin,0,SEEK_END);
Ramy Al Zuhouri
źródło
Wow ... przy okazji, czy standard mówi, że fseekjest dostępny stdin?
ikh
3
Nie wiem, myślę, że nie wspomina o tym, ale stdin to PLIK *, a fseek akceptuje parametr PLIK *. Przetestowałem to i działa na Mac OS X, ale nie na Linuksie.
Ramy Al Zuhouri
Proszę wytłumacz. Byłoby świetną odpowiedzią, gdyby autor zapisał implikacje tej metody. Czy są jakieś problemy?
EvAlex
7
@EvAlex: jest z tym wiele problemów. Jeśli to działa w twoim systemie, świetnie; jeśli nie, nie jest to zaskakujące, ponieważ nic nie gwarantuje, że zadziała, gdy standardowe wejście jest urządzeniem interaktywnym (lub urządzeniem, którego nie można przeszukiwać, takim jak rura, gniazdo lub FIFO, żeby wymienić tylko kilka innych sposobów, w jakie może zawieść).
Jonathan Leffler
Dlaczego powinien być SEEK_END? Czy możemy zamiast tego użyć SEEK_SET? Jak zachowuje się stdin FP?
Rajesh
11

Przenośny sposób na wyczyszczenie do końca wiersza, który już próbowałeś częściowo przeczytać, to:

int c;

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

To czyta i odrzuca znaki, dopóki nie otrzyma, \nco oznacza koniec pliku. Sprawdza również EOFw przypadku, gdy strumień wejściowy zostanie zamknięty przed końcem linii. Typ cmusi być int(lub większy), aby móc utrzymać wartość EOF.

Nie ma przenośnego sposobu sprawdzenia, czy po bieżącej linii jest więcej wierszy (jeśli ich nie ma, getcharblokuje wejście).

MM
źródło
dlaczego while ((c = getchar ())! = EOF); nie działa? To nieskończona pętla. Nie można zrozumieć lokalizacji EOF w stdin.
Rajesh
@Rajesh To będzie jasne, dopóki strumień wejściowy nie zostanie zamknięty, co nie nastąpi, jeśli będzie więcej danych wejściowych później
MM
@Rajesh Tak, masz rację. Jeśli na stdin nie ma nic do spłukiwania, gdy zostanie wywołany, zablokuje się (zawiesi się), ponieważ zostanie złapany w nieskończonej pętli.
Jason Enochs
11

Linie:

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

nie czyta tylko znaków przed wysuwem wiersza ( '\n'). Odczytuje wszystkie znaki w strumieniu (i odrzuca je), aż do następnego nowego wiersza (lub napotkanego EOF) włącznie . Aby test był prawdziwy, musi najpierw odczytać nowy wiersz; więc kiedy pętla się zatrzymuje, wysuw wiersza był ostatnim odczytanym znakiem, ale został odczytany.

Jeśli chodzi o to, dlaczego czyta znak nowego wiersza zamiast powrotu karetki, to dlatego, że system przetłumaczył powrót na nowy wiersz. Po naciśnięciu klawisza Enter sygnalizuje koniec linii ... ale zamiast tego strumień zawiera wysunięcie wiersza, ponieważ jest to normalny znacznik końca linii dla systemu. Może to zależeć od platformy.

Ponadto używanie fflush()na strumieniu wejściowym nie działa na wszystkich platformach; na przykład generalnie nie działa w systemie Linux.

Dmitri
źródło
Uwaga: while ( getchar() != '\n' );to nieskończona pętla powinna getchar()powrócić z EOFpowodu końca pliku.
chux - Przywróć Monikę
Aby sprawdzić również EOF, int ch; while ((ch = getchar()) != '\n' && ch != EOF);można użyć
Dmitri
8

Ale nie potrafię sobie wytłumaczyć, jak to działa? Ponieważ w instrukcji while używamygetchar() != '\n' , oznacza to odczytanie dowolnego pojedynczego znaku oprócz '\n'?? jeśli tak, w buforze wejściowym nadal pozostaje '\n'znak ??? Czy coś nie rozumiem?

Możesz nie zdawać sobie sprawy z tego, że porównanie ma miejsce po getchar() usunięciu znaku z bufora wejściowego. Więc kiedy osiągniesz '\n', jest zużyty, a następnie wyrwiesz się z pętli.

zwol
źródło
6

możesz spróbować

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

gdzie% * c akceptuje i ignoruje znak nowej linii

jeszcze jedna metoda zamiast fflush (stdin), która wywołuje niezdefiniowane zachowanie, które możesz napisać

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

nie zapomnij o średniku po pętli while

kapil
źródło
2
1) "%*c"skanuje dowolny znak (i ​​nie zapisuje go), czy to nowy wiersz, czy coś innego. Kod opiera się na tym, że drugi znak to nowa linia. 2) while((getchar())!='\n');to nieskończona pętla, która powinna getchar()powrócić z EOFpowodu końca pliku.
chux - Przywróć Monikę
1
druga metoda zależy od warunków takich jak nowa linia, znak null, EOF itp. (nad nią była nowa linia)
kapil
1

Napotykam problem podczas próby wdrożenia rozwiązania

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

Publikuję małe dostosowanie „Kod B” dla każdego, kto może mieć ten sam problem.

Problem polegał na tym, że program wychwycił znak „\ n”, niezależnie od znaku enter, oto kod, który spowodował problem.

Kod A

int y;

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

a korekta polegała na `` złapaniu '' znaku (n-1) tuż przed oceną warunku w pętli while, oto kod:

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

Możliwym wyjaśnieniem jest to, że aby pętla while została przerwana, musi przypisać wartość „\ n” zmiennej y, więc będzie to ostatnia przypisana wartość.

Jeśli przegapiłem coś z wyjaśnieniem, kodem A lub kodem B, powiedz mi, jestem ledwo nowy w c.

mam nadzieję, że to komuś pomoże

antadlp
źródło
Powinieneś zająć się swoimi „ oczekiwanymi ” znakami wewnątrz whilepętli. Po pętli możesz uznać, stdinże jest czysty / wyraźny i ybędzie trzymać albo ' \n', albo EOF. EOFjest zwracany, gdy nie ma nowej linii i bufor jest wyczerpany (jeśli wejście przepełniłoby bufor przed [ENTER]naciśnięciem). - Skutecznie używasz while((ch=getchar())!='\n'&&ch!=EOF);do przeżuwania każdego znaku w stdinbuforze wejściowym.
veganaiZe
0
unsigned char a=0;
if(kbhit()){
    a=getch();
    while(kbhit())
        getch();
}
cout<<hex<<(static_cast<unsigned int:->(a) & 0xFF)<<endl;

-lub-

użyć może użyć _getch_nolock().. ???

Michael Grieswald
źródło
Pytanie dotyczy C, a nie C ++.
Spikatrix
1
Zauważ, że kbhit()i getch()są funkcjami zadeklarowanymi <conio.h>i dostępnymi standardowo tylko w systemie Windows. Mogą być emulowane na maszynach typu Unix, ale wymaga to pewnej ostrożności.
Jonathan Leffler
0

Innym rozwiązaniem, o którym jeszcze nie wspomniano, jest użycie: rewind (stdin);

James Blue
źródło
2
To całkowicie zepsuje program, jeśli stdin zostanie przekierowany do pliku. W przypadku wprowadzania interaktywnego nie ma gwarancji, że nic się nie stanie.
melpomene
0

Dziwię się, że nikt o tym nie wspomniał:

scanf("%*[^\n]");
nowox
źródło
-1

Krótki, przenośny i zadeklarowany w stdio.h

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

Nie zawiesza się w nieskończonej pętli, gdy na stdin nie ma nic do spłukiwania, jak następująca dobrze znana linia:

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

Trochę drogie, więc nie używaj go w programie, który musi wielokrotnie czyścić bufor.

Ukradł od współpracownika :)

Jason Enochs
źródło
Nie działa w systemie Linux. Zamiast tego użyj termios bezpośrednio. stackoverflow.com/a/23885008/544099
ishmael
2
Czy możesz wyjaśnić, dlaczego dobrze znana linia jest możliwą nieskończoną pętlą?
Cacahuete Frito
Cały powód freopenjest taki, że nie można go przenosić stdin. To jest makro.
melpomene
1
ishmael: Wszystkie nasze komputery tutaj w Cyber ​​Command to Linux i działa na nich dobrze. Przetestowałem to na Debianie i Fedorze.
Jason Enochs
To, co nazywasz „nieskończoną pętlą”, to program oczekujący na wejście. To nie to samo.
jamesdlin
-1

Spróbuj tego:

stdin->_IO_read_ptr = stdin->_IO_read_end;

Inkwizycje
źródło
3
Dodaj więcej szczegółów na temat działania tego rozwiązania i użytecznego sposobu rozwiązania problemu stackoverflow.com/help/how-to-answer
Suit Boy Apps