Как отбросить непрочитанные символы из cin?
Зачем отбрасывать символы? Попробуем решить такую задачу: нужно прочитать из cin целое число, причем если вместо целого числа нам подсунут какую-нибудь гадость, нужно сообщить об этом и попытаться прочитать число снова. Попытка номер один:
1 2 3 4 5 6 7 | using namespace std; int value = 0; while (not (cin >> value)) { cout << "Invalid format" << endl; cin.clear(); } cout << "Your input: " << value << endl; |
Попытавшись ввести нечто, непохожее на целое число, получим бесконечный цикл. Попытка прочитать число в строке 3 заканчивается неудачей, но введенные символы из буфера ввода никуда не исчезают. Повторное чтение, натыкаясь на те же самые символы, заканчивается с тем же результатом.
Беглый гуглеж приводит меня к функции istream::sync(), которая, как говорит один источник, делает именно то, что нам нужно:
Synchronizes the buffer associated with the stream to its controlled input sequence. This effectively means that the unread characters in the buffer are discarded.
Что ж, попробуем:
1 2 3 4 5 6 7 8 | using namespace std; int value = 0; while (not (cin >> value)) { cout << "Invalid format" << endl; cin.clear(); cin.sync(); } cout << "Your input: " << value << endl; |
Хм. В моей системе результат не изменился, код работает точно так же, как и первый. Беда в том, что функция istream::sync() в некоторых реализациях стандартной библиотеки таки отбрасывает непрочитанные символы. Но не в моем случае.
А я человек простой — лезу в код своей стандартной библиотеки и смотрю что да как. Выходит, что istream::sync() вызывает stdio_sync_filebuf::pubsync(), которая вызывает stdio_sync_filebuf::sync(), которая, наконец, вызывает fflush(stdin). Понятно, что fflush(stdin) не может оказать никакого влияния на входной буфер cin.
Так что упомянутый выше «один источник» не стоит считать истиной в последней инстанции. Но надо сказать, что попытки найти истину в стандарте — еще более неблагодарное занятие.
В общем, правильное решение заключается в использовании функции istream::ignore() таким вот образом:
1 2 3 4 5 6 7 8 | using namespace std; int value = 0; while (not (cin >> value)) { cout << "Invalid format" << endl; cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n'); } cout << "Your input: " << value << endl; |
Надо отметить, что вызов ignore() неплохо бы вставлять даже после успешного ввода, так как при вводе строки «123qwe» первые три символа будут прочитаны и распознаны как число, а «qwe» останутся во входном буфере и будут портить жизнь при следующем чтении. Еще одна тонкость — обработка конца файла. Если пользователь в ответ на приглашение к вводу нажмет Ctrl+D или что там в вашей системе для этого предназначено, то программа вместо того, чтобы завершиться, сообщит о неверном формате и предложит ввести число еще раз. Поэтому по-хорошему код должен быть вроде этого (вся программа):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #include <iostream> #include <limits> using namespace std; template <typename Type> bool read_value(const std::string &prompt, Type &value) { bool repeat = true; bool eof = cin.eof(); while (repeat and not eof) { cout << prompt << ": "; cin >> value; eof = cin.eof(); repeat = cin.fail() and not eof; if (repeat) { cout << "***Invalid data format!" << endl; cin.clear(); } else if (eof) { cout << "(EOF)" << endl; } cin.ignore(numeric_limits<streamsize>::max(), '\n'); } return not eof; } int main() { int value = 0; while (read_value("Input integer", value)) cout << "Your input: " << value << endl; } |
Но и в этом, казалось бы, безошибочном, коде затаилась пара граблей. Например, что если функцию read_value() использовать для ввода символов или строк? Сумеем мы ввести символ пробела? Сумеем ли ввести строку из нескольких слов, разделенных пробелами? Хрен! С вводом символа пробела еще несложно — надо только использовать манипулятор noskipws, а вот ввод строк с пробелами потребует перегрузки read_value() и использования getline() вместо оператора >>.
В общем, писать на C++ — сплошное счастье.


