Как очистить буфер ввода в C?

87

У меня есть такая программа:

Как объяснил автор приведенного выше кода: Программа не будет работать должным образом, потому что в строке 1, когда пользователь нажимает Enter, во входном буфере остается 2 символа: Enter key (ASCII code 13)и \n (ASCII code 10). Следовательно, в строке 2 он будет читать \nи не будет ждать, пока пользователь введет символ.

Хорошо, я понял. Но мой первый вопрос: почему второй getchar()( ch2 = getchar();) не читает Enter key (13), а не \nсимвол?

Далее автор предложил 2 способа решения таких проблем:

  1. использовать fflush()

  2. напишите такую ​​функцию:

Этот код действительно работал. Но я не могу себе объяснить, как это работает? Поскольку в операторе while, который мы используем getchar() != '\n', это означает чтение любого отдельного символа, кроме '\n'? если да, то во входном буфере все равно остается '\n'символ?

ipkiss
источник

Ответы:

92

Программа не будет работать должным образом, потому что в строке 1, когда пользователь нажимает Enter, он оставит в буфере ввода 2 символа: клавишу Enter (код ASCII 13) и \ n (код ASCII 10). Следовательно, в строке 2 он будет читать \ n и не будет ждать, пока пользователь введет символ.

Поведение, которое вы видите в строке 2, правильное, но это не совсем правильное объяснение. В потоках текстового режима не имеет значения, какие окончания строки использует ваша платформа (будь то возврат каретки (0x0D) + перевод строки (0x0A), чистый CR или простой LF). Библиотека времени выполнения C позаботится об этом за вас: ваша программа будет видеть только '\n'символы новой строки.

Если вы набрали символ и нажали Enter, то этот вводимый символ будет прочитан в строке 1, а затем '\n'будет прочитан в строке 2. См. Я использую, scanf %cчтобы прочитать ответ Y / N, но более поздний ввод пропускается.из FAQ по comp.lang.c.

Что касается предлагаемых решений, см. (Опять же из FAQ comp.lang.c):

которые в основном заявляют, что единственный переносимый подход - это сделать:

Твой getchar() != '\n' цикл работает, потому что после вызова getchar()возвращенный символ уже был удален из входного потока.

Кроме того, я чувствую себя обязанным отговорить вас от употребления scanfполностью: почему все говорят не использовать scanf? Что мне использовать вместо этого?

Jamesdlin
источник
1
Это зависнет, ожидая, пока пользователь нажмет Enter. Если вы хотите просто очистить stdin, используйте termios напрямую. stackoverflow.com/a/23885008/544099
ishmael
@ishmael Ваш комментарий не имеет смысла, поскольку нажатие Enter не требуется для очистки стандартного ввода; в первую очередь требуется вводная информация. (И это единственный стандартный способ чтения ввода в C.Если вы хотите иметь возможность читать ключи немедленно, вам придется использовать нестандартные библиотеки.)
jamesdlin
@jamesdlin В Linux getchar () будет ждать, пока пользователь нажмет Enter. Только тогда они вырвутся из цикла while. Насколько мне известно, termios - это стандартная библиотека.
ishmael
@ishmael Нет, ты ошибаешься по обоим пунктам. Этот вопрос касается очистки оставшегося ввода от стандартного ввода после чтения ввода, а в стандарте C при условии, что для ввода сначала требуется ввести строку и нажать Enter. После того, как эта строка была введена, getchar()будет читать и возвращать эти символы; пользователю не нужно будет нажимать Enter в другой раз. Кроме того, в то время как termios может быть общим на Linux , это не является частью стандартной библиотеки C .
jamesdlin
в чем причина scanf(" %c", &char_var)работы и игнорирования новой строки, просто добавив пробел перед спецификатором% c?
Cătălina Sîrbu
44

Вы можете сделать это (также) следующим образом:

Рами Аль Зухури
источник
Вау ... кстати, стандартное слово fseekдоступно для stdin?
ikh
3
Не знаю, думаю, об этом не упоминается, но stdin - это FILE *, а fseek принимает параметр FILE *. Я тестировал его, и он работает в Mac OS X, но не в Linux.
Рами Аль Зухури
Пожалуйста, объясни. Будет отличным ответом, если автор напишет последствия этого метода. Есть ли проблемы?
EvAlex
7
@EvAlex: с этим много проблем. Если он работает в вашей системе, отлично; если нет, то это неудивительно, поскольку ничто не гарантирует, что он будет работать, когда стандартный ввод представляет собой интерактивное устройство (или устройство без поиска, такое как канал, сокет или FIFO, чтобы назвать лишь несколько других способов, которыми он может потерпеть поражение).
Джонатан Леффлер,
Почему это должно быть SEEK_END? Можем ли мы использовать вместо этого SEEK_SET? Как ведет себя стандартный ввод FP?
Rajesh
11

Переносимый способ очистить до конца строку, которую вы уже пытались частично прочитать:

Он читает и отбрасывает символы до тех пор, пока не получит \nсигнал о конце файла. Он также проверяет, EOFзакрывается ли входной поток до конца строки. Тип cдолжен быть int(или больше), чтобы можно было хранить значение EOF.

Нет переносимого способа узнать, есть ли еще строки после текущей строки (если их нет, то getcharввод будет заблокирован).

ММ
источник
почему while ((c = getchar ())! = EOF); не работает? Это бесконечный цикл. Невозможно понять расположение EOF в stdin.
Раджеш
@Rajesh Это будет очищено, пока входной поток не будет закрыт, чего не будет, если будет больше входных данных, которые появятся позже
ММ
@Rajesh Да, ты прав. Если при вызове stdin нечего сбрасывать, он блокируется (зависает) из-за того, что он попадает в бесконечный цикл.
Джейсон Енохс,
11

Линии:

не читает только символы перед переводом строки ( '\n'). Он считывает все символы в потоке (и отбрасывает их) вплоть до следующего перевода строки (или встречается EOF). Чтобы тест был верным, он должен сначала прочитать перевод строки; поэтому, когда цикл останавливается, перевод строки был последним прочитанным символом, но он был прочитан.

Что касается того, почему он читает перевод строки вместо возврата каретки, это потому, что система перевела возврат на перевод строки. Когда нажата клавиша ввода, это сигнализирует об окончании строки ... но вместо этого поток содержит перевод строки, поскольку это обычный маркер конца строки для системы. Это может зависеть от платформы.

Кроме того, использование fflush()входящего потока работает не на всех платформах; например, это обычно не работает в Linux.

Дмитрий
источник
Примечание: while ( getchar() != '\n' );должен getchar()возвращаться бесконечный цикл EOFиз-за конца файла.
chux
Чтобы проверить EOF, int ch; while ((ch = getchar()) != '\n' && ch != EOF);можно также использовать
Дмитрий
8

Но я не могу себе объяснить, как это работает? Поскольку в операторе while, который мы используем getchar() != '\n', это означает чтение любого отдельного символа, кроме '\n'?? если да, то во входном буфере все равно остается '\n'символ ??? Я что-то недопонимаю ??

Вы можете не осознавать, что сравнение происходит после getchar() удаления символа из входного буфера. Итак, когда вы дойдете до '\n', он потребляется, а затем вы выходите из цикла.

Zwol
источник
6

ты можешь попробовать

где% * c принимает и игнорирует новую строку

еще один метод вместо fflush (stdin), который вызывает неопределенное поведение, вы можете написать

не забывайте точку с запятой после цикла while

капил
источник
2
1) "%*c"сканирует любой символ (и не сохраняет его), будь то перевод строки или что-то еще. Код полагается на то, что второй символ - это новая строка. 2) while((getchar())!='\n');- бесконечный цикл должен getchar()возвращаться EOFиз-за конца файла.
chux
1
второй метод зависит от таких условий, как
новая строка
1

Я столкнулся с проблемой при попытке реализовать решение

Я публикую небольшую корректировку «Код В» для всех, у кого может быть такая же проблема.

Проблема заключалась в том, что программа заставляла меня ловить символ '\ n', независимо от символа ввода, вот код, который дал мне проблему.

Код А

и корректировка заключалась в том, чтобы `` поймать '' символ (n-1) непосредственно перед вычислением условного оператора в цикле while, вот код:

Код B

Возможное объяснение состоит в том, что для прерывания цикла while он должен присвоить значение '\ n' переменной y, так что это будет последнее присвоенное значение.

Если я что-то пропустил с пояснением, кодом A или кодом B, скажите, пожалуйста, я новичок в c.

надеюсь, это поможет кому-то

Antadlp
источник
Вы должны иметь дело с вашим « ожидаемым » персонажем (ами) внутри whileцикла. После цикла вы можете считать stdinего чистым / чистым и yудерживать либо ' \n', либо EOF. EOFвозвращается, когда новой строки нет и буфер исчерпан (если ввод переполнял буфер до того, как [ENTER]был нажат). - Вы эффективно используете, while((ch=getchar())!='\n'&&ch!=EOF);чтобы пережевывать каждый символ во stdinвходном буфере.
veganaiZe
0

-или же-

использовать возможно использовать _getch_nolock().. ???

Майкл Грисвальд
источник
Речь идет о C, а не о C ++.
Spikatrix
1
Обратите внимание, что kbhit()и getch()- это функции, объявленные в <conio.h>Windows и доступные только в качестве стандартных. Их можно эмулировать на Unix-подобных машинах, но это требует некоторой осторожности.
Джонатан Леффлер,
0

Другое решение, о котором еще не упоминалось, - это использовать: rewind (stdin);

Джеймс Блю
источник
2
Это полностью нарушит работу программы, если stdin будет перенаправлен в файл. А для интерактивного ввода ничего не гарантируется.
melpomene
0

Я удивлен, что никто об этом не упомянул:

Nowox
источник
-1

Короткий, переносимый и заявленный в stdio.h

Не зависает в бесконечном цикле, когда на стандартном вводе нечего сбрасывать, как в следующей хорошо известной строке:

Немного дорого, поэтому не используйте его в программе, которая должна многократно очищать буфер.

Украл у коллеги :)

Джейсон Енохс
источник
Не работает в Linux. Вместо этого используйте termios напрямую. stackoverflow.com/a/23885008/544099
ishmael
2
Не могли бы вы подробно объяснить, почему хорошо известная строка является возможным бесконечным циклом?
alx 02
Вся причина в freopenтом, что вы не можете переносить stdin. Это макрос.
melpomene
1
Ишмаэль: Все наши компьютеры здесь, в Cyber ​​Command, работают под управлением Linux и на них отлично работают. Я тестировал его как на Debian, так и на Fedora.
Джейсон Енохс
То, что вы называете «бесконечным циклом», - это программа, ожидающая ввода. Это не одно и то же.
jamesdlin
-1

Попробуй это:

stdin->_IO_read_ptr = stdin->_IO_read_end;

Инквизиции
источник
3
Пожалуйста, добавьте немного более подробной информации о том, как работает это решение, и как оно является полезным способом решения проблемы stackoverflow.com/help/how-to-answer
Suit Boy Apps