简体   繁体   中英

while loop to infinity when the input of a cin is a 'dot'

I am having trouble using the cin method to acquire a variable. When the input is a number there is no problem, but when it is a special character like a dot [.], the whileloop loops into infinity. What am I doing wrong?

cout << "What is your race" <<endl<<"1.Human\n2.troll\n3.zombie"<<endl;
    cin >> *race;
    while(*race<1||*race>3)
    {
    system("cls");
    cout << "Wrong choice"<<endl<< "What is your race" <<endl<<"1.Human\n2.troll\n3.zombie"<<endl;
    cin >> *race;
    }

I searched for the answer and i should have to flush the buffer but i don"t get how to do it. I'm rather new with c++. Thanx

Make race an char, then you will be able do to:

while (*race < '1' || *race > '3')

which is probably what you want to achieve.

Explanation:

When you cin >> into an int, it converts given ASCII string to integer value. . doesn't have an integer meaning, so it isn't read into race and failbit is set - further >>s are no-op, until you clear them. However, if you cin >> into char and compare it with other char s (well, their ASCII codes, actually), you will be able to check it without troubles.

The other solution besides the one accepted is to clear the cin's failbit and ignore the last input like below:

cout << "What is your race" <<endl<<"1.Human\n2.troll\n3.zombie"<<endl;
cin >> *race;
while(*race<1||*race>3)
{
    // Clears the state of cin to be in good state
    cin.clear();
    // Ignores the last input read so that it's not read back again
    cin.ignore();
    system("cls");
    cout << "Wrong choice"<<endl<< "What is your race" <<endl<<"1.Human\n2.troll\n3.zombie"<<endl;
    cin >> *race;
}

This example exactly reproduces your problem:

#include <iostream>

int main()
{
    int i = 5;
    while (i < 1 || i > 3)
    {
        std::cin >> i;
    }
}

Here's what happens: When operator>> fails to read an integer (eg when you type a dot and press enter), whatever you typed stays in the stream, including the newline character. So in the next iteration of the while loop the next input is already there and since it's not a valid integer, the loop can never break. You need to make sure that, when operator>> fails, you empty the stream and clear all the error flags that got set.

#include <iostream>
#include <limits>

int main()
{
    int i = 5;
    while (i < 1 || i > 3)
    {
        if (!(std::cin >> i))
        {
            // clear streams internal error flags
            std::cin.clear();
            // ignore what's left in the stream, up to first newline character
            // or the entire content, whichever comes first
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
    }
}

There are several problems with your code. The first is that you don't verify that your input has succeeded; the correct condition for the while should be:

while ( !cin || (*race < 1 || *race > 3) )

As written, if the input fails (which is what is happening when you enter a '.' , supposing that race has type int* ), then *race contains its previous value, whatever that was.

The second is that if you do get an error from cin , you don't clear it. Once the stream is in an error state, it stays that way until you explicitly clear it. If cin has failed, you need to execute:

cin.clear();

somewhere in the loop.

The third is that if cin fails, you don't extract the character which made it failed, so that after clearing the error status, you need to extract it. Given the way you've structured your dialog, you probably want to ignore everything until the end of the line:

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

You may want to do this even if cin didn't fail, either in the loop (if entered because of the *race < 1 || *race > 3 condition), or in case of success. Alternatively, you may want to shift to reading lines, and ensure that the line only contains whitespace after the character you're interested in.

This last solution is the one I would adopt, since it handles pretty much all of the above problems. So my code would look something like:

//  return -1 on error in input,
//  throw exception on (unexpected) end of file
int
getRace( std::istream& source )
{
    std::string line;
    if ( !std::getline( source, line ) ) {
        throw std::ios_base::failure( "Unexpected end of file" );
    }
    std::istringstream tmp( line );
    int results;
    return tmp >> results >> std::ws && tmp.get() == EOF
        ? results
        : -1;
}

//  ...
int race = -1;
while ( race < 0 ) {
    std::cout << "What is your race\n"
                 "1. Human\n"
                 "2. Troll\n"
                 "3.  Zombie\n" << std::flush;
    race = getRace( std::cout );
    if ( race < 0 ) {
        std::cout << "Wrong choice" << std::endl;
    }
}

Note that by inputting through a line, you avoid any problems with resetting format errors, skipping erroneous input or resynchronizing in case of error.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM