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
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 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;
}
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.
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 (e.g. 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');
}
}
}