Почему мы будем вызывать cin.clear() и cin.ignore() после чтения ввода?

Учебник по Google C++ для университета Google Code имел этот код:

// Description: Illustrate the use of cin to get input
// and how to recover from errors.

#include <iostream>
using namespace std;

int main()
{
  int input_var = 0;
  // Enter the do while loop and stay there until either
  // a non-numeric is entered, or -1 is entered.  Note that
  // cin will accept any integer, 4, 40, 400, etc.
  do {
    cout << "Enter a number (-1 = quit): ";
    // The following line accepts input from the keyboard into
    // variable input_var.
    // cin returns false if an input operation fails, that is, if
    // something other than an int (the type of input_var) is entered.
    if (!(cin >> input_var)) {
      cout << "Please enter numbers only." << endl;
      cin.clear();
      cin.ignore(10000,'\n');
    }
    if (input_var != -1) {
      cout << "You entered " << input_var << endl;
    }
  }
  while (input_var != -1);
  cout << "All done." << endl;

  return 0;
}

Какое значение имеет cin.clear() а также cin.ignore()? Почему 10000 а также \n параметры нужны?

4 ответа

Решение

cin.clear() очищает флаг ошибки на cin (чтобы будущие операции ввода-вывода работали правильно), а затем cin.ignore(10000, '\n') переходит к следующей новой строке (чтобы игнорировать что-либо еще в той же строке, что и не-число, чтобы это не вызывало очередной сбой разбора). Он пропустит только до 10000 символов, поэтому в коде предполагается, что пользователь не введет очень длинную недопустимую строку.

Вы вводите

if (!(cin >> input_var))

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

cin.clear();

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

cin.ignore(10000,'\n');

Он забирает 10000 символов из буфера, но останавливается, если встречает символ новой строки (\n). 10000 - это просто большое значение.

Почему мы используем:

1) cin.ignore

2) cin.clear

?

Просто:

1) игнорировать (извлекать и отбрасывать) значения, которые нам не нужны в потоке

2) Очистить внутреннее состояние потока. После использования cin.clear внутреннее состояние снова возвращается в Goodbit, что означает отсутствие "ошибок".

Длинная версия:

Если что-то помещено в "поток" (cin), то оно должно быть взято оттуда. Под "взятым" мы подразумеваем "использованный", "удаленный", "извлеченный" из потока. Поток имеет поток. Данные текут по кину как вода по течению. Вы просто не можете остановить поток воды;)

Посмотрите на пример:

string name; //line 1
cout << "Give me your name and surname:"<<endl;//line 2
cin >> name;//line 3
int age;//line 4
cout << "Give me your age:" <<endl;//line 5
cin >> age;//line 6

Что произойдет, если пользователь ответит: "Arkadiusz Wlodarczyk" за первый вопрос?

Запустите программу, чтобы убедиться в этом.

Вы увидите на консоли "Arkadiusz", но программа не спросит вас о "возрасте". Это просто закончится сразу же после печати "Arkadiusz".

И "Влодарчик" не показан. Похоже, что если он ушел (?)*

Что случилось?;-)

Потому что между "Аркадиушем" и "Влодарчиком" есть пространство.

Символ "пробел" между именем и фамилией является для компьютера признаком того, что две переменные ожидают извлечения в потоке "input".

Компьютер считает, что вы пытаетесь отправить на вход более одной переменной. Этот "космический" знак является знаком для него, чтобы интерпретировать это таким образом.

Таким образом, компьютер назначает "Arkadiusz" для "name" (2), и, поскольку вы помещаете более одной строки в потоковый (входной) компьютер, он попытается присвоить значение "Wlodarczyk" переменной "age" (!). У пользователя не будет возможности поместить что-либо в 'cin' в строке 6, потому что эта инструкция уже выполнена (!). Зачем? Потому что там еще что-то осталось в потоке. И, как я сказал ранее, поток находится в потоке, поэтому все должно быть удалено из него как можно скорее. И такая возможность появилась, когда компьютер увидел инструкцию cin >> age;

Компьютер не знает, что вы создали переменную, в которой хранится возраст кого-либо (строка 4). "возраст" - это просто ярлык. Для компьютера "возраст" можно так же назвать: "afsfasgfsagasggas", и это будет то же самое. Для него это просто переменная, которой он будет пытаться присвоить "Wlodarczyk", потому что вы приказали / дали команду компьютеру сделать это в строке (6).

Это неправильно, но эй, это ты сделал это! Это твоя ошибка! Ну, может быть, пользователь, но все же...


Все хорошо хорошо Но как это исправить?!

Давайте попробуем поиграть с этим примером, прежде чем мы исправим его, чтобы выучить еще несколько интересных вещей:-)

Я предпочитаю подход, где мы понимаем вещи. Исправление чего-либо без знания того, как мы это сделали, не приносит удовлетворения, вам не кажется?:)

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate(); //new line is here :-)

После вызова приведенного выше кода вы заметите, что состояние вашего потока (cin) равно 4 (строка 7). Это означает, что его внутреннее состояние больше не равно goodbit. Что-то напутало. Это довольно очевидно, не так ли? Вы попытались присвоить строковое значение типа ("Wlodarczyk") переменной типа int 'age'. Типы не совпадают. Пришло время сообщить, что что-то не так. И компьютер делает это, изменяя внутреннее состояние потока. Это как: "Ты, блин, чувак, поправь меня, пожалуйста. Я сообщаю тебе" любезно ";-)"

Вы просто не можете больше использовать cin (поток). Он застрял. Как будто вы положили большие деревянные бревна на поток воды. Вы должны исправить это, прежде чем сможете его использовать. Данные (вода) не могут быть получены из этого потока (cin) больше, потому что журнал древесины (внутреннее состояние) не позволяет вам сделать это.

О, так что, если есть препятствие (деревянные бревна), мы можем просто удалить его, используя специально предназначенные для этого инструменты?

Да!

внутреннее состояние cin, установленное на 4, похоже на сигнал тревоги, который воет и издает шум.

cin.clear очищает состояние до нормального (goodbit). Это как если бы ты пришел и отключил будильник. Вы просто отложите это. Вы знаете, что что-то случилось, поэтому вы говорите: "Можно прекратить шуметь, я знаю, что что-то уже не так, заткнись (очистить)".

Хорошо, давайте сделаем это! Давайте использовать cin.clear().

Вызвать приведенный ниже код, используя "Arkadiusz Wlodarczyk" в качестве первого ввода:

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate() << endl; 
cin.clear(); //new line is here :-)
cout << cin.rdstate()<< endl;  //new line is here :-)

После выполнения приведенного выше кода мы наверняка увидим, что состояние равно goodbit.

Отлично, так что проблема решена?

Вызвать приведенный ниже код, используя "Arkadiusz Wlodarczyk" в качестве первого ввода:

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate() << endl;; 
cin.clear(); 
cout << cin.rdstate() << endl; 
cin >> age;//new line is here :-)

Даже после того, как состояние установлено на goodbit после строки 9, пользователь не запрашивает "возраст". Программа останавливается.

ЗАЧЕМ?!

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

Вы должны удалить "Wlodarczyk" этот кусок дерева из ручья. Отключение будильника не решает проблему вообще. Вы только что заставили его замолчать и думаете, что проблема ушла?;)

Итак, пришло время для другого инструмента:

cin.ignore можно сравнить со специальным грузовиком с веревками, который приходит и удаляет деревянные бревна, которые застряли в потоке. Это очищает проблему, созданную пользователем вашей программы.

Так можем ли мы использовать его еще до срабатывания будильника?

Да:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
int age;
cout << "Give me your age:" << endl;
cin >> age;

"Wlodarczyk" будет удален до создания шума в строке 7.

Что такое 10000 и '\n'?

В нем говорится, что нужно удалить 10000 символов (на всякий случай), пока не встретится '\ n' (ENTER). Кстати, это можно сделать лучше, используя numeric_limits, но это не тема этого ответа.


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

Зачем нам тогда "ясно"?

Что, если кто-то задал вопрос "дай мне свой возраст" в строке 6, например: "двадцать лет" вместо того, чтобы писать 20?

Типы не совпадают снова. Компьютер пытается присвоить строку int. И начинается тревога. У вас нет шанса даже отреагировать на такую ​​ситуацию. cin.ignore не поможет вам в таком случае.

Таким образом, мы должны использовать clear в таком случае:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
int age;
cout << "Give me your age:" << endl;
cin >> age;
cin.clear();
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow

Но вы должны очистить государство "на всякий случай"?

Конечно, нет.

Если что-то пойдет не так (cin >> age;), инструкция сообщит вам об этом, вернув false.

Таким образом, мы можем использовать условный оператор, чтобы проверить, не поместил ли пользователь неверный тип в поток

int age;
if (cin >> age) //it's gonna return false if types doesn't match
    cout << "You put integer";
else
    cout << "You bad boy! it was supposed to be int";

Итак, мы можем исправить нашу первоначальную проблему, например, так:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow

int age;
cout << "Give me your age:" << endl;
if (cin >> age)
  cout << "Your age is equal to:" << endl;
else
{
 cin.clear();
 cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
 cout << "Give me your age name as string I dare you";
 cin >> age;
}

Конечно, это можно улучшить, например, выполнив то, что вы делали, используя цикл while.

БОНУС:

Вы можете быть удивлены. А что, если я хочу получить имя и фамилию в одной строке от пользователя? Возможно ли даже использование cin, если cin интерпретирует каждое значение, разделенное пробелом, как другую переменную?

Конечно, вы можете сделать это двумя способами:

1)

string name, surname;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin >> surname;

cout << "Hello, " << name << " " << surname << endl;

2) или с помощью функции getline.

getline(cin, nameOfStringVariable);

и вот как это сделать:

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

Второй вариант может иметь неприятные последствия, если вы используете его после использования 'cin' перед getline.

Давайте проверим это:

а)

int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << "Your age is" << age << endl;

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

Если вы указали "20" в качестве возраста, вам не нужно будет указывать имя и фамилию.

Но если вы сделаете это так:

б)

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << "Your age is" << age << endll

Все отлично.

КАКИЕ?!

Каждый раз, когда вы помещаете что-то на вход (поток), вы оставляете в конце белый символ ENTER ('\n'). Вы должны каким-то образом вводить значения в консоль. Так должно быть, если данные поступают от пользователя.

б) характеристики cin - это то, что он игнорирует пробелы, поэтому, когда вы читаете информацию из cin, символ новой строки '\ n' не имеет значения. Это игнорируется.

a) функция getline возвращает всю строку до символа новой строки ('\n'), и когда символ новой строки является первым, функция getline получает '\ n', и это все, что нужно получить. Вы извлекаете символ новой строки, который был оставлен в потоке пользователем, который поставил "20" в потоке в строке 3.

Поэтому, чтобы исправить это, нужно всегда вызывать cin.ignore(); каждый раз, когда вы используете cin, чтобы получить какое-либо значение, если вы когда-либо собираетесь использовать getline() внутри вашей программы.

Таким образом, правильный код будет:

int age;
cout << "Give me your age:" <<endl;
cin >> age;
cin.ignore(); // it ignores just enter without arguments being sent. it's same as cin.ignore(1, '\n') 
cout << "Your age is" << age << endl;


string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

Я надеюсь, что потоки более понятны для вас.

Ха, молчи меня, пожалуйста!:-)

Использование cin.ignore(1000,'\n') очистить все символы предыдущего cin.get() в буфере, и он выберет остановку, когда встретится '\n' или 1000 chars первый.

Другие вопросы по тегам