cin.get() и его друзья

Функция

int istream::get();

определенная в классе istream библиотеки <iostream> по определению извлекает из входного потока один символ и возвращает его целочисленный код. Популярность ей принес тот факт, что ее удобно использовать в программах с консольным интерфейсом, которые запускаются не из консоли, например, из проводника или из графического интерфейса IDE. После завершения работы программы мы не сможем увидеть ее финальный вывод, поскольку выполнится инструкция return и программа завершится, закрывая «за собой» консольное окно.

Функция get() же стандартного потока ввода cin заставляет систему ожидать ввода пользователем любого символа, который она считывает, и программа завершается.

Проблема в том, что работает cin.get() далеко не всегда. Почему? Рассмотрим ситуацию издалека.

Начнем с того, что же такое поток (stream). Ненаучным языком говоря, поток — последовательность символов. Источником символов может служить в частности клавиатура. Символы идут один за другим:

abcdef

К ним возможен только последовательный доступ, но не произвольный, иными словами, чтобы извлечь из потока символ d, надо предварительно извлечь из него a, b и c. Извлеченный (прочитанный) символ удаляется из потока.

Надо сказать, что если по каким-то причинам из потока прочитаны не все символы до конца строки (символа '\n') включительно, то после операции чтения поток не будет пустым. Два самых распространенных способа дают нам два хороших примера.

1)
cout<<cin.get();

Уже рассматривавшаяся выше функция get() читает из потока один символ, так что если мы ввели несколько символов, то она оставит за собой непустой поток. И следующий вызов cin.get() будет обречен на «фиаско»: программа вместо того, чтобы остановиться и ждать пользовательского ввода, прочитает из входного потока очередной символ, оставшийся от предыдущего ввода, и продолжит свое выполнение. Или завершится «без спроса». Напомню, что get() возвращает приведенный к типу int код введенного символа, поэтому мы может использовать его в вышеприведенной инструкции — код просто будет выведен на экран.

Собственно вывод: если в конце программы cin.get() не ждет пользовательского ввода, значит, вы оставили за собой непустой входной поток.

2)
int a;
cin>>a;

Перегруженный оператор сдвига, использующийся для ввода данных из потока, который в свою очередь перегружен для работы с целыми числами (так как вызван с параметром a типа int), считывает символы, являющиеся десятичными цифрами, до тех пор, пока не встретит нецифровой символ. Это может быть пробел, буква, табуляция, конец строки, и так далее.
Все эти символы остались в потоке. Даже если вы просто ввели число и нажали Enter, символ '\n' остался в потоке.

На него и «нарывается» впоследствии cin.get().

Следующая шуточная программка позволяет воочию увидеть «обидчика». Скомпилируйте ее, запустите и введите, к примеру,

1234i

Она выведет символ, на котором «споткнулся» cin.get().

#include<iostream>

using namespace std;

int main()
{
int a;
cin>>a;
cout<<(char)cin.get();
return 0;
}

Теперь, когда проблема очевидна, рассмотрим ее возможные решения. В порядке увеличения их сложности.

1)
Функция

istream& istream::ignore(streamsize n = 1, int delim = EOF);

определенная в классе istream, извлекает из потока символы и отбрасывает их. Причем она так поступает либо с n символами, либо со всеми символами, пока в потоке не встретится символ, заданный параметром delim.

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

cin.ignore(numeric_limits<streamsize>::max(), '\n');

2)
streambuf* istream::rdbuf() const;
streamsize streambuf::in_avail();

У потока ввода есть буфер чтения, в котором он хранит символы. Мы обращаемся к нему, вызывая функцию rdbuf(). А любой уважающий себя буфер знает, сколько символов в нем содержится. Поскольку и мы хотим это узнать, мы вызываем функцию in_avail() этого буфера. Это количество символов нам и нужно проигнорировать, что достигается так:

cin.ignore(cin.rdbuf()->in_avail());

Второй параметр функции ignore() имеет значение по умолчанию, что делает его необязательным, и мы его просто опускаем при вызове.

3)
int istream::sync();

Использование функции sync() — путь наименьшего сопротивления. Она просто очищает поток от имеющихся в нем символов.

Почему этот способ самый сложный — поищите в Гугле (если есть желание). А я в это время порекомендую Вам выбрать более понравившийся из первых двух и пойду заниматься более полезными делами 😉

Хотя нет, напоследок еще расскажу о функции

void ios::clear(iostate state = goodbit);

Эта функция восстанавливает поток, если он по какой-либо причине оказался в ошибочном состоянии. Например, из потока пытались прочитать число, а там в это время находились буквы. В этом случае, поток переводится в состояние отказа, и дальнейшие операции с ним неосуществимы. В том числе и cin.get(). Функция же clear() «очищает» состояние потока, делая его вновь веселым и работоспособным.

На закуску пример и ссылки.

#include<iostream>
#include<limits>

using namespace std;

int main()
{
int a;
cin>>a;
cout<<a;
cin.ignore(numeric_limits<streamsize>::max(), '\n');
cin.clear();
cin.get();
}

istream::get()
istream::ignore()
ios::rdbuf()
streambuf::in_avail()
istream::sync()
ios::clear()

Реклама

14 комментариев

Filed under Стандартная библиотека, Язык

14 responses to “cin.get() и его друзья

  1. romsuh

    Классно. И я почти всё понял. Сейчас прочту еще раз и всё пойму.

  2. zombaine

    на редкость доступно написано))

  3. zoomion

    Спасибо!!!!
    Очень помогло, вам книгу надо писать :-), серьезно…
    Много книг а пишут наподобие справочника….

  4. Алексей

    Действительно, очень понятно написано, спасибо автору)

  5. Фурс Александр

    Cпасибо парень,На основе твоего сайта(информации) я создаю свой собственный справочник по С++ в формате ФБ2,чтобы всегда был под рукой в зл.книге.Но почему ты не создашь книгу по С++ в формате ФБ2.Сейчас почти все електронные книги поддеоживают ФБ2,но немногие ХТМЛ.

    • Не за что. Можете кинуть потом ссылку на справочник, будет полезно.
      На собственную книгу нет времени. Собственно, даже статьи писать некогда.

  6. «Собственно вывод: если в конце программы cin.get() не ждет пользовательского ввода, значит, вы оставили за собой непустой входной поток.» — эх … сколько бы мне нервов сэкономило — знай я это чуть раньше! Автору большое спасибо — теперь всё прояснилось:)

  7. neo

    Спасибо старина все понятно написано. Долго я мучился не понимая почему не срабатывают следующие cin.get().
    Книги тебе точно надо писать!

  8. Oleg

    А если я введу qw1234erty.
    Что тогда мне делать?

  9. Max

    Отличная статья, так сказать для «простого люда» 🙂 Столкнулся с такой проблемой в лабораторке; хоть это было не обязательно, но захотелось узнать, в чём тут дело. Автору спасибо.

  10. anti-k

    Автору спасибо!!

  11. SunM

    После прочтения появляется желание с благодарностью обнять автора :_)

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s