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

86

В учебнике C ++ от Google Code University использовался этот код:

// 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?

JacKeown
источник
1
Это мой самый популярный пост за все время. Должно быть, я учился в старшей школе.
JacKeown,

Ответы:

102

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

Джеремайя Уиллкок
источник
57
+1. Хочу добавить, что вместо игнорирования до 10000 символов лучше было бы использовать cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');.
Деннис
30
Если хотите использовать std::numeric_limits, обязательно #include <limits>.
gbmhunter 07
1
Может кто-нибудь объяснить синтаксис std::numeric_limits<std::streamsize>::max()?
Минь Тран
4
@Minh Tran: std :: streamsize представляет собой целое число со знаком, которое дает количество переданных символов ввода-вывода или размер буфера ввода-вывода. Здесь, используя шаблонный класс «numeric_limits», мы хотели узнать максимальный лимит буфера ввода-вывода или передаваемых символов.
Pankaj
1
@Minh Tran: всего одна небольшая поправка: «размер потока» - это не класс, это просто интегральный тип. Мы можем получить и другой предел, т.е. int, char и т. д. проверьте его на [ссылка] ( en.cppreference.com/w/cpp/types/numeric_limits )
Pankaj
46

Вы входите в

if (!(cin >> input_var))

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

cin.clear();

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

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

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

рейс
источник
11
Просто добавим, что по умолчанию игнорируется один символ, поэтому вам нужно большее число, чтобы пропустить целую строку.
Bo Persson
22

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

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", но программа не спросит "возраст". Он просто закончится сразу после печати «Аркадиуш».

А «Влодарчик» не показан. Похоже, если бы его не было (?) *

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

Потому что между «Аркадиушем» и «Влодарчиком» есть пробел.

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

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

Таким образом, компьютер присваивает «Аркадиуш» «имя» (2), и поскольку вы помещаете более одной строки в поток (входной), компьютер будет пытаться присвоить значение «Влодарчик» переменной «возраст» (!). У пользователя не будет возможности поставить что-либо на 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 :-)

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

ЗАЧЕМ?!

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

Вам нужно удалить этот кусок дерева "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' не имеет значения. Это игнорируется.

а) функция 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;

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

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

Морфидон
источник
1

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

Фи Лиенг
источник