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

от admin

How to clear input buffer in C?

As the author of the above code have explained: The program will not work properly because at Line 1, when the user presses Enter, it will leave in the input buffer 2 character: Enter key (ASCII code 13) and \n (ASCII code 10) . Therefore, at Line 2, it will read the \n and will not wait for the user to enter a character.

OK, I got this. But my first question is: Why the second getchar() ( ch2 = getchar(); ) does not read the Enter key (13) , rather than \n character?

Next, the author proposed 2 ways to solve such probrems:

write a function like this:

This code worked actually. But I cannot explain myself how it works? Because in the while statement, we use getchar() != ‘\n’ , that means read any single character except ‘\n’ ? if so, in the input buffer still remains the ‘\n’ character?

18 Answers 18

The program will not work properly because at Line 1, when the user presses Enter, it will leave in the input buffer 2 character: Enter key (ASCII code 13) and \n (ASCII code 10). Therefore, at Line 2, it will read the \n and will not wait for the user to enter a character.

The behavior you see at line 2 is correct, but that’s not quite the correct explanation. With text-mode streams, it doesn’t matter what line-endings your platform uses (whether carriage return (0x0D) + linefeed (0x0A), a bare CR, or a bare LF). The C runtime library will take care of that for you: your program will see just ‘\n’ for newlines.

If you typed a character and pressed enter, then that input character would be read by line 1, and then ‘\n’ would be read by line 2. See I’m using scanf %c to read a Y/N response, but later input gets skipped. from the comp.lang.c FAQ.

As for the proposed solutions, see (again from the comp.lang.c FAQ):

which basically state that the only portable approach is to do:

Your getchar() != ‘\n’ loop works because once you call getchar() , the returned character already has been removed from the input stream.

Also, I feel obligated to discourage you from using scanf entirely: Why does everyone say not to use scanf ? What should I use instead?

You can do it (also) this way:

A portable way to clear up to the end of a line that you’ve already tried to read partially is:

This reads and discards characters until it gets \n which signals the end of the file. It also checks against EOF in case the input stream gets closed before the end of the line. The type of c must be int (or larger) in order to be able to hold the value EOF .

There is no portable way to find out if there are any more lines after the current line (if there aren’t, then getchar will block for input).

doesn’t read only the characters before the linefeed ( ‘\n’ ). It reads all the characters in the stream (and discards them) up to and including the next linefeed (or EOF is encountered). For the test to be true, it has to read the linefeed first; so when the loop stops, the linefeed was the last character read, but it has been read.

As for why it reads a linefeed instead of a carriage return, that’s because the system has translated the return to a linefeed. When enter is pressed, that signals the end of the line. but the stream contains a line feed instead since that’s the normal end-of-line marker for the system. That might be platform dependent.

Also, using fflush() on an input stream doesn’t work on all platforms; for example it doesn’t generally work on Linux.

Jonathan Leffler's user avatar

But I cannot explain myself how it works? Because in the while statement, we use getchar() != ‘\n’ , that means read any single character except ‘\n’ ?? if so, in the input buffer still remains the ‘\n’ character. Am I misunderstanding something??

The thing you may not realize is that the comparison happens after getchar() removes the character from the input buffer. So when you reach the ‘\n’ , it is consumed and then you break out of the loop.

where %*c accepts and ignores the newline

one more method instead of fflush(stdin) which invokes undefined behaviour you can write

don’t forget the semicolon after while loop

kapil's user avatar

scanf is a strange function, and there’s a classic line from the movie WarGames that’s relevant: "The only winning move is not to play".

If you find yourself needing to "flush input", you have already lost. The winning move is not to search desperately for some magic way to flush the nettlesome input: instead, what you need to do is to do input in some different (better) way, that doesn’t involve leaving unread input on the input stream, and having it sit there and cause problems, such that you have to try to flush it instead.

There are basically three cases:

You are reading input using scanf , and it is leaving the user’s newline on the input stream, and that stray newline is wrongly getting read by a later call to getchar or fgets . (This is the case you were initially asking about.)

You are reading input using scanf , and it is leaving the user’s newline on the input stream, and that stray newline is wrongly getting read by a later call to scanf("%c") .

You are reading numeric input using scanf , and the user is typing non-numeric text, and the non-numeric text is getting left on the input stream, meaning that the next call to scanf fails on it also.

In all three cases, it may seem like the right thing to do is to "flush" the offending input. And you can try, but it’s cumbersome at best and impossible at worst. In the end I believe that trying to flush input is the wrong approach, and that there are better ways, depending on which case you were worried about:

In case 1, the better solution is, do not mix calls to scanf with other input functions. Either do all your input with scanf , or do all your input with getchar and/or fgets . To do all your input with scanf , you can replace calls to getchar with scanf("%c") — but see point 2. Theoretically you can replace calls to fgets with scanf("%[^\n]%*c") , although this has all sorts of further problems and I do not recommend it. To do all your input with fgets even though you wanted/needed some of scanf ‘s parsing, you can read lines using fgets and then parse them after the fact using sscanf .

In case 2, the better solution is, never use scanf("%c") . Use scanf(" %c") instead. The magic extra space makes all the difference. (There’s a long explanation of what that extra space does and why it helps, but it’s beyond the scope of this answer.)

And in case 3, I’m afraid that there simply is no good solution. scanf has many problems, and one of its many problems is that its error handling is terrible. If you want to write a simple program that reads numeric input, and if you can assume that the user will always type proper numeric input when prompted to, then scanf("%d") can be an adequate — barely adequate — input method. But perhaps your goal is to do better. Perhaps you’d like to prompt the user for some numeric input, and check that the user did in fact enter numeric input, and if not, print an error message and ask the user to try again. In that case, I believe that for all intents and purposes you cannot meet this goal based around scanf . You can try, but it’s like putting a onesie on a squirming baby: after getting both legs and one arm in, while you’re trying to get the second arm in, one leg will have wriggled out. It is just far too much work to try to read and validate possibly-incorrect numeric input using scanf . It is far, far easier to do it some other way.

Читать:
Что делать если потекла матрица на мониторе

You will notice a theme running through all three cases I listed: they all began with "You are reading input using scanf . ". There’s a certain inescapable conclusion here. See this other question: What can I use for input conversion instead of scanf?

Now, I realize I still haven’t answered the question you actually asked. When people ask, "How do I do X?", it can be really frustrating when all the answers are, "You shouldn’t want to do X." If you really, really want to flush input, then besides the other answers people have given you here, two other good questions full of relevant answers are:

std::fflush

Для выходных потоков (и для потоков обновления, для которых была выведена последняя операция), записывает любые неписанные данные из буфера stream в соответствующее устройство вывода.

Для потоков ввода (и для потоков обновления,для которых была введена последняя операция)поведение не определено.

Если stream является нулевым указателем, все открытые выходные потоки сбрасываются, включая те, которые манипулируются в пакетах библиотеки или иным образом не доступны напрямую программе.

Parameters

stream поток файлов для записи

Return value

Возвращает ноль в случае успеха. В противном случае возвращается EOF и устанавливается индикатор ошибки потока файлов.

Notes

POSIX расширяет спецификацию fflush , определяя его влияние на входной поток, если этот поток представляет файл или другое устройство для поиска: в этом случае указатель файла POSIX перемещается, чтобы соответствовать указателю потока C (что эффективно отменяет любую буферизацию чтения ) и эффекты любых std::ungetc или std::ungetwc , которые еще не были прочитаны из потока, отбрасываются.

Microsoft также расширяет спецификацию fflush, определяя ее влияние на поток ввода: в Visual Studio 2013 и более ранних версиях он отбрасывал входной буфер , в Visual Studio 2015 и новее он не действует, буферы сохраняются .

Как очистить буфер cin?

Это будет читать и игнорировать все до EOF . (вы также можете указать второй аргумент, который является символом для чтения до (ex: ‘\n’ , чтобы игнорировать одну строку).

Также: вы, вероятно, захотите сделать: std::cin.clear(); до этого также до reset состояния потока.

Я бы предпочел ограничения размера С++ над версиями C:

Это было единственное, что работало для меня при чтении с консоли. В любом другом случае он будет либо читать неопределенно из-за отсутствия \n, либо что-то останется в буфере.

EDIT: Я узнал, что предыдущее решение усугубило ситуацию. ЭТО, однако, работает:

Я нашел два решения.

Первым и самым простым является использование std::getline() , например:

. который отбрасывает входной поток, когда он попадает в новую строку. Подробнее об этой функции здесь.

Другая опция, которая напрямую отбрасывает поток, — это.

Другим возможным (ручным) решением является

Я не могу использовать fflush или cin.flush() с CLion, поэтому это пригодится.

Вот пример, где cin.ignore просто не режет его, но я не могу думать об этом в данный момент. Это было давно, когда мне нужно было использовать его (с Mingw).

Однако fflush (stdin) — это поведение undefined в соответствии со стандартом. fflush() предназначен только для выходных потоков. fflush (stdin) работает, как ожидалось, в Windows (по крайней мере, с компиляторами GCC и MS) как расширение стандарта C.

Ввод и вывод символьных строк в Си

Итак, строки в языке Си. Для них не предусмотрено отдельного типа данных, как это сделано во многих других языках программирования. В языке Си строка – это массив символов. Чтобы обозначить конец строки, используется символ '\0' , о котором мы говорили в прошлой части этого урока. На экране он никак не отображается, поэтому посмотреть на него не получится.

Создание и инициализация строки

Так как строка – это массив символов, то объявление и инициализация строки аналогичны подобным операциям с одномерными массивами.

Следующий код иллюстрирует различные способы инициализации строк.

Объявление и инициализация строк

Рис.1 Объявление и инициализация строк

В первой строке мы просто объявляем массив из десяти символов. Это даже не совсем строка, т.к. в ней отсутствует нуль-символ \0 , пока это просто набор символов.

Вторая строка. Простейший способ инициализации в лоб. Объявляем каждый символ по отдельности. Тут главное не забыть добавить нуль-символ \0 .

Третья строка – аналог второй строки. Обратите внимание на картинку. Т.к. символов в строке справа меньше, чем элементов в массиве, остальные элементы заполнятся \0 .

Четвёртая строка. Как видите, тут не задан размер. Программа его вычислит автоматически и создаст массив символов нужный длины. При этом последним будет вставлен нуль-символ \0 .

Как вывести строку

Дополним код выше до полноценной программы, которая будет выводить созданные строки на экран.

Различные способы вывода строки на экран

Рис.2 Различные способы вывода строки на экран

Как видите, есть несколько основных способов вывести строку на экран.

  • использовать функцию printf со спецификатором %s
  • использовать функцию puts
  • использовать функцию fputs , указав в качестве второго параметра стандартный поток для вывода stdout .

Единственный нюанс у функций puts и fputs . Обратите внимание, что функция puts переносит вывод на следующую строку, а функция fputs не переносит.

Как видите, с выводом всё достаточно просто.

Ввод строк

С вводом строк всё немного сложнее, чем с выводом. Простейшим способом будет являться следующее:

Функция gets приостанавливает работу программы, читает строку символов, введенных с клавиатуры, и помещает в символьный массив, имя которого передаётся функции в качестве параметра.
Завершением работы функции gets будет являться символ, соответствующий клавише ввод и записываемый в строку как нулевой символ.
Заметили опасность? Если нет, то о ней вас любезно предупредит компилятор. Дело в том, что функция gets завершает работу только тогда, когда пользователь нажимает клавишу ввод. Это чревато тем, что мы можем выйти за рамки массива, в нашем случае — если введено более 20 символов.
К слову, ранее ошибки переполнения буфера считались самым распространенным типом уязвимости. Они встречаются и сейчас, но использовать их для взлома программ стало гораздо сложнее.

Итак, что мы имеем. У нас есть задача: записать строку в массив ограниченного размера. То есть, мы должны как-то контролировать количество символов, вводимых пользователем. И тут нам на помощь приходит функция fgets :

Функция fgets принимает на вход три аргумента: переменную для записи строки, размер записываемой строки и имя потока, откуда взять данные для записи в строку, в данном случае — stdin . Как вы уже знаете из 3 урока, stdin – это стандартный поток ввода данных, обычно связанный с клавиатурой. Совсем необязательно данные должны поступать именно из потока stdin , в дальнейшем эту функцию мы также будем использовать для чтения данных из файлов.

Если в ходе выполнения этой программы мы введем строку длиннее, чем 10 символов, в массив все равно будут записаны только 9 символов с начала и символ переноса строки, fgets «обрежет» строку под необходимую длину.

Обратите внимание, функция fgets считывает не 10 символов, а 9 ! Как мы помним, в строках последний символ зарезервирован для нуль-символа.

Давайте это проверим. Запустим программу из последнего листинга. И введём строку 1234567890 . На экран выведется строка 123456789 .

Пример работы функции fgets

Рис.3 Пример работы функции fgets

Возникает вопрос. А куда делся десятый символ? А я отвечу. Он никуда не делся, он остался в потоке ввода. Выполните следующую программу.

Вот результат её работы.

Непустой буфер stdin

Рис.4 Непустой буфер stdin

Поясню произошедшее. Мы вызвали функцию fgets . Она открыла поток ввода и дождалась пока мы введём данные. Мы ввели с клавиатуры 1234567890\n ( \n я обозначаю нажатие клавиша Enter ). Это отправилось в поток ввода stdin . Функция fgets , как и полагается, взяла из потока ввода первые 9 символов 123456789 , добавила к ним нуль-символ \0 и записала это в строку str . В потоке ввода осталось ещё 0\n .

Далее мы объявляем переменную h . Выводим её значение на экран. После чего вызываем функцию scanf . Тут-то ожидается, что мы можем что-то ввести, но т.к. в потоке ввода висит 0\n , то функция scanf воспринимает это как наш ввод, и записывается 0 в переменную h . Далее мы выводим её на экран.

Это, конечно, не совсем такое поведение, которое мы ожидаем. Чтобы справиться с этой проблемой, необходимо очистить буфер ввода после того, как мы считали из него строку, введённую пользователем. Для этого используется специальная функция fflush . У неё всего один параметр – поток, который нужно очистить.

Исправим последний пример так, чтобы его работа была предсказуемой.

Теперь программа будет работать так, как надо.

Сброс буфера stdin функцией fflush

Рис.4 Сброс буфера stdin функцией fflush

Подводя итог, можно отметить два факта. Первый. На данный момент использование функции gets является небезопасным, поэтому рекомендуется везде использовать функцию fgets .

Второй. Не забывайте очищать буфер ввода, если используете функцию fgets .

На этом разговор о вводе строк закончен. Идём дальше.

Сохрани в закладки или поддержи проект.

Практика

Решите предложенные задачи:

Для удобства работы сразу переходите в полноэкранный режим

Похожие статьи