Чому ми називаємо cin.clear () та cin.ignore () після прочитання вводу?


86

Підручник 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параметри , необхідні?


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

1
Хтось може пояснити синтаксис std::numeric_limits<std::streamsize>::max()?
Minh Tran

4
@Minh Tran: std :: streamsize підписаний інтеграл, який вказує кількість переданих символів вводу-виводу або розмір буфера вводу-виводу. Тут, використовуючи клас шаблону "numeric_limits", ми хотіли знати максимальне обмеження переданого буфера вводу-виводу або символу.
Панкадж

1
@Minh Tran: лише одна незначна корекція "streamsize" - це не клас, це просто інтегральний тип. Ми також можемо отримати обмеження іншого, тобто. INT, символ і т.д .. будь ласка , перевірте його на [посилання] ( en.cppreference.com/w/cpp/types/numeric_limits )
Панкай

46

Ви вводите

if (!(cin >> input_var))

оператор, якщо під час отримання вхідних даних з cin. Якщо виникає помилка, тоді встановлюється прапорець помилки, і майбутні спроби отримати введення не вдаються. Ось чому вам потрібно

cin.clear();

щоб позбутися позначки помилки. Крім того, вхід, який не вдався, буде сидіти в тому, що я припускаю, це якийсь буфер. Коли ви спробуєте отримати вхід ще раз, він прочитає той самий вхід у буфері, і він знову не зможе. Ось чому вам потрібно

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

Він виймає з буфера 10000 символів, але зупиняється, якщо зустрічає новий рядок (\ n). 10000 - це просто загальне велике значення.


11
Просто додавши, що за замовчуванням для ігнорування пропускається один символ, тому вам потрібно більше число, щоб пропустити цілий рядок.
Бо Перссон

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 Wlodarczyk" на перше запитання?

Запустіть програму, щоб переконатися на власні очі.

Ви побачите на консолі "Arkadiusz", але програма не запитає вас про "вік". Це просто закінчиться відразу після друку "Arkadiusz".

А "Влодарчик" не показують. Здається, якби його не було (?) *

Що сталося? ;-)

Бо між «Аркадіушем» та «Влодарчиком» є простір.

"пробіл" між іменем та прізвищем є знаком для комп'ютера, що є дві змінні, які очікують на вилучення у потоці "введення".

Комп’ютер вважає, що ви намагаєтесь надіслати на вхід більше однієї змінної. Цей «космічний» знак - це знак для того, щоб він так його трактував.

Отже, комп’ютер призначає «Arkadiusz» «імені» (2), а оскільки ви поклали більше одного рядка на потік (вхід), комп'ютер спробує присвоїти значення «Wlodarczyk» змінній «вік» (!). Користувач не матиме можливості розмістити що-небудь на "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). А це означає, що його внутрішній стан вже не дорівнює добробут. Щось заплутано. Це досить очевидно, чи не так? Ви намагалися присвоїти значення типу рядка ("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 у користувача не запитується "вік". Програма зупиняється.

ЧОМУ ?!

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

Вам потрібно видалити "Влодарчик" той шматок дерева з потоку. Вимкнення будильника зовсім не вирішує проблему. Ви щойно замовкли це, і вважаєте, що проблема зникла? ;)

Тож настав час для іншого інструменту:

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;

"Влодарчик" збирається видалити перед тим, як видавати шум у рядку 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' перед початком лінії.

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

а)

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" як вік, вас не запитають nameAndSurname.

Але якщо ви робите це так:

б)

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першим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.