Why would we call cin.clear() and cin.ignore() after reading input?

后端 未结 4 1973
小蘑菇
小蘑菇 2020-11-21 22:59

Google Code University\'s C++ tutorial used to have this code:

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

#in         


        
4条回答
  •  渐次进展
    2020-11-21 23:33

    Why do we use:

    1) cin.ignore

    2) cin.clear

    ?

    Simply:

    1) To ignore (extract and discard) values that we don't want on the stream

    2) To clear the internal state of stream. After using cin.clear internal state is set again back to goodbit, which means that there are no 'errors'.

    Long version:

    If something is put on 'stream' (cin) then it must be taken from there. By 'taken' we mean 'used', 'removed', 'extracted' from stream. Stream has a flow. The data is flowing on cin like water on stream. You simply cannot stop the flow of water ;)

    Look at the example:

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

    What happens if the user answers: "Arkadiusz Wlodarczyk" for first question?

    Run the program to see for yourself.

    You will see on console "Arkadiusz" but program won't ask you for 'age'. It will just finish immediately right after printing "Arkadiusz".

    And "Wlodarczyk" is not shown. It seems like if it was gone (?)*

    What happened? ;-)

    Because there is a space between "Arkadiusz" and "Wlodarczyk".

    "space" character between the name and surname is a sign for computer that there are two variables waiting to be extracted on 'input' stream.

    The computer thinks that you are tying to send to input more than one variable. That "space" sign is a sign for him to interpret it that way.

    So computer assigns "Arkadiusz" to 'name' (2) and because you put more than one string on stream (input) computer will try to assign value "Wlodarczyk" to variable 'age' (!). The user won't have a chance to put anything on the 'cin' in line 6 because that instruction was already executed(!). Why? Because there was still something left on stream. And as I said earlier stream is in a flow so everything must be removed from it as soon as possible. And the possibility came when computer saw instruction cin >> age;

    Computer doesn't know that you created a variable that stores age of somebody (line 4). 'age' is merely a label. For computer 'age' could be as well called: 'afsfasgfsagasggas' and it would be the same. For him it's just a variable that he will try to assign "Wlodarczyk" to because you ordered/instructed computer to do so in line (6).

    It's wrong to do so, but hey it's you who did it! It's your fault! Well, maybe user, but still...


    All right all right. But how to fix it?!

    Let's try to play with that example a bit before we fix it properly to learn a few more interesting things :-)

    I prefer to make an approach where we understand things. Fixing something without knowledge how we did it doesn't give satisfaction, don't you think? :)

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

    After invoking above code you will notice that the state of your stream (cin) is equal to 4 (line 7). Which means its internal state is no longer equal to goodbit. Something is messed up. It's pretty obvious, isn't it? You tried to assign string type value ("Wlodarczyk") to int type variable 'age'. Types doesn't match. It's time to inform that something is wrong. And computer does it by changing internal state of stream. It's like: "You f**** up man, fix me please. I inform you 'kindly' ;-)"

    You simply cannot use 'cin' (stream) anymore. It's stuck. Like if you had put big wood logs on water stream. You must fix it before you can use it. Data (water) cannot be obtained from that stream(cin) anymore because log of wood (internal state) doesn't allow you to do so.

    Oh so if there is an obstacle (wood logs) we can just remove it using tools that is made to do so?

    Yes!

    internal state of cin set to 4 is like an alarm that is howling and making noise.

    cin.clear clears the state back to normal (goodbit). It's like if you had come and silenced the alarm. You just put it off. You know something happened so you say: "It's OK to stop making noise, I know something is wrong already, shut up (clear)".

    All right let's do so! Let's use cin.clear().

    Invoke below code using "Arkadiusz Wlodarczyk" as first input:

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

    We can surely see after executing above code that the state is equal to goodbit.

    Great so the problem is solved?

    Invoke below code using "Arkadiusz Wlodarczyk" as first input:

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

    Even tho the state is set to goodbit after line 9 the user is not asked for "age". The program stops.

    WHY?!

    Oh man... You've just put off alarm, what about the wood log inside a water?* Go back to text where we talked about "Wlodarczyk" how it supposedly was gone.

    You need to remove "Wlodarczyk" that piece of wood from stream. Turning off alarms doesn't solve the problem at all. You've just silenced it and you think the problem is gone? ;)

    So it's time for another tool:

    cin.ignore can be compared to a special truck with ropes that comes and removes the wood logs that got the stream stuck. It clears the problem the user of your program created.

    So could we use it even before making the alarm goes off?

    Yes:

    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;
    

    The "Wlodarczyk" is gonna be removed before making the noise in line 7.

    What is 10000 and '\n'?

    It says remove 10000 characters (just in case) until '\n' is met (ENTER). BTW It can be done better using numeric_limits but it's not the topic of this answer.


    So the main cause of problem is gone before noise was made...

    Why do we need 'clear' then?

    What if someone had asked for 'give me your age' question in line 6 for example: "twenty years old" instead of writing 20?

    Types doesn't match again. Computer tries to assign string to int. And alarm starts. You don't have a chance to even react on situation like that. cin.ignore won't help you in case like that.

    So we must use clear in case like that:

    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
    

    But should you clear the state 'just in case'?

    Of course not.

    If something goes wrong (cin >> age;) instruction is gonna inform you about it by returning false.

    So we can use conditional statement to check if the user put wrong type on the stream

    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";
    

    All right so we can fix our initial problem like for example that:

    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;
    }
    

    Of course this can be improved by for example doing what you did in question using loop while.

    BONUS:

    You might be wondering. What about if I wanted to get name and surname in the same line from the user? Is it even possible using cin if cin interprets each value separated by "space" as different variable?

    Sure, you can do it two ways:

    1)

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

    2) or by using getline function.

    getline(cin, nameOfStringVariable);
    

    and that's how to do it:

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

    The second option might backfire you in case you use it after you use 'cin' before the getline.

    Let's check it out:

    a)

    int age;
    cout << "Give me your age:" <> age;
    cout << "Your age is" << age << endl;
    
    string nameAndSurname;
    cout << "Give me your name and surname:"<< endl;
    getline(cin, nameAndSurname);
    
    cout << "Hello, " << nameAndSurname << endl;
    

    If you put "20" as age you won't be asked for nameAndSurname.

    But if you do it that way:

    b)

    string nameAndSurname;
    cout << "Give me your name and surname:"<< endl;
    getline(cin, nameAndSurname);
    
    cout << "Hello, " << nameAndSurname << endl;
    int age;
    cout << "Give me your age:" <> age;
    cout << "Your age is" << age << endll
    

    everything is fine.

    WHAT?!

    Every time you put something on input (stream) you leave at the end white character which is ENTER ('\n') You have to somehow enter values to console. So it must happen if the data comes from user.

    b) cin characteristics is that it ignores whitespace, so when you are reading in information from cin, the newline character '\n' doesn't matter. It gets ignored.

    a) getline function gets the entire line up to the newline character ('\n'), and when the newline char is the first thing the getline function gets '\n', and that's all to get. You extract newline character that was left on stream by user who put "20" on stream in line 3.

    So in order to fix it is to always invoke cin.ignore(); each time you use cin to get any value if you are ever going to use getline() inside your program.

    So the proper code would be:

    int age;
    cout << "Give me your age:" <> 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;
    

    I hope streams are more clear to you know.

    Hah silence me please! :-)

提交回复
热议问题