Avoiding a infinite loop caused by non integer input.

Started by
13 comments, last by Zahlman 14 years, 1 month ago
Firstly, thankyou Zahlman for giving me that little challenge at the end of your post, that's the kind of stuff that I believe makes a programmers knowledge that little bit better.

I first had some troubles and it was because of something SO stupid, SO simple and I really cannot believe I made the mistake and spent so long trying to fix it.

I had my function worked out correctly and tried so many methods to try and get it working, even though I didnt have to, but in the end, all my problems were caused by typing out a declaration...not a function call.

I put 'string' infront of the function call, which ultimately skipped the function all together resulting in an infinite loop.

Here is my new code with the function. If you think it needs any improvements please let me know, and maybe let me know why. (I think that MAYBE I did more than I needed to....maybe).

//Critter Caretaker//Simulates caring for a virtual pet#include<iostream>#include<string>#include<sstream>using namespace std;class Critter{public:	Critter(int hunger = 0, int boredom = 0);	void talk();	void eat(int food = 4);	void play(int fun = 4);	void stats();private:	int m_hunger;	int m_boredom;	int GetMood() const;	void PassTime(int time = 1);};Critter::Critter(int hunger, int boredom):m_hunger(hunger),m_boredom(boredom){}inline int Critter::GetMood() const{	return (m_hunger + m_boredom);}void Critter::PassTime(int time){	m_hunger += time;	m_boredom += time;}void Critter::talk(){	cout << "I'm a critter and I feel ";	int mood = GetMood();	if (mood > 15)		cout << "mad.\n";	else if (mood > 10)		cout << "frustrated.\n";	else if (mood > 5)		cout << "okay\n";	else		cout << "HAPPY! :D \n";	PassTime();}void Critter::eat(int food){	cout << "Brruuppp\n";	m_hunger -= food;	if (m_hunger < 0)		m_hunger = 0;	PassTime();}void Critter::play(int fun){	cout << "WHEEEE! THIS IS FUN!\n";	m_boredom -= fun;	if (m_boredom < 0)		m_boredom = 0;	PassTime();}void Critter::stats(){	cout << "Creature's Happiness level is: ";	cout << Critter::GetMood() << "\n";	cout << "Creature's Hunger level is: \n";	cout << Critter::m_hunger << "\n";	cout << "Creature's Boredom level is: \n";	cout << Critter::m_boredom << "\n";}string getchoice(string& choice);int main(){	Critter crit;	crit.talk();	int choice;		do	{				cout << "\nCritter Caretaker\n\n";		cout << "0 - QUIT\n";		cout << "1 - LISTEN TO YOUR CRITTER\n";		cout << "2 - FEED YOUR CRITTER\n";		cout << "3 - PLAY WITH YOUR CRITTER\n\n";		cout << "(DEVELOPERS MENU)\n";		cout << "4 - (LIST CREATURES STATISTICS)\n";		cout << "Please type a number to choose an action\n";		cout << "Choice: ";		string line;		getchoice(line);		// We set 'choice' to an invalid value first. That way,		// if the input is garbage, nothing is read in, so it's still		// invalid, and the default case of the switch is triggered.		choice = -1;		stringstream parser(line);		parser >> choice;		// If you want, you can do additional checking that there is		// no extra text at the end of the line, e.g. "76 trombones":		char c;		if (parser >> c) 		{			choice = -1;		}		// That way lets the user put spaces and tabs at the end, which		// I think is reasonable. If you want to be even stricter, try		// using parser.get().						switch (choice)		{		case 0:				cout << "Good-bye.\n";				break;		case 1:				crit.talk();				break;		case 2:				crit.eat();				break;		case 3: 				crit.play();				break;		case 4:				crit.stats();				break;		case 5:				cout << "ERROR!\n";				break;		default:			cout << "\nSorry, but " << line << " is not a valid choice.\n";						}			}while(choice != 0);	return 0;	}string getchoice(string& choice){	string line;	getline(cin, line);    choice = line;		return choice;}


I have some questions for you again.

- Say for example I type 546 as my choice.
Is parser >> choice; getting the value of line (546) and seeing if it is an integer value (choice)? I'm still not entirely sure how the parser works.

----------------------

Nobodynews, thankyou for your explanation, unfortunately though, i'm still a little lost. So i'm going to ask a few more questions about your explanation which might help me understand your post a bit better.

- Firstly, what is the difference between an "internal string" and "data from a keyboard"?

- You said "Both of these differences can make stringstream more useful for converting text to an integer than cin". What is the purpose of converting text into an integer?

I probably could ask more, but i'll just leave it at that for now.

Thankyou again Nobodynews for your explanation, i'm sorry that I STILL could not make sense of it all though. I'm sure I will soon enough lol.
Advertisement
Quote:Original post by otreum
- Firstly, what is the difference between an "internal string" and "data from a keyboard"?
That was bad terminology on my part. By internal string I meant that stringstream contains a hidden member that stores string data. That data is supplied by you passing in a string. That string could come from any other source. On the other hand, cin also has a method of storing string data, except this string data is supplied from what was typed on the keyboard. cin only gets its string data from the keyboard.

Quote:- You said "Both of these differences can make stringstream more useful for converting text to an integer than cin". What is the purpose of converting text into an integer?
This might be where some of your confusion comes from. When you do "cin >> choice;" cin takes text from the keyboard and converts it to whatever type of variable you made 'choice'. If choice is an int (which in your case it is) then cin will take the text it took from the keyboard try to convert it to an int. stringstream can also take a string of characters and try to convert it to an int, except it can do it with any string supplied to it and not just from the keyboard.

The point is to separate what are really two different activities. Reading text from the keyboard and converting that text to the type of variable you want.

C++: A Dialog | C++0x Features: Part1 (lambdas, auto, static_assert) , Part 2 (rvalue references) , Part 3 (decltype) | Write Games | Fix Your Timestep!

Uh oh, confused again! But I will keep trying to understand...at least until you give up.

Ok...so, stringstream CAN get data from the input on the keyboard, ASWELL as any other source, such as a default value of a variable?

Where as cin can ONLY get input from the keyboard (unless I do an operator overload on cin)?

------------------

Getting back to the conversion topic again.

When I input something like "H" into choice, and I then output choice, does it output that strange negative number (-85372395 etc) because the compiler is trying to convert a character into an integer?

(more questions to come when my brain doesn't feel like it's falling apart from trying to understand this stuff aswell as something like operator overloading).

Thanks :)
Quote:Original post by otreum
1. Creates a string variable called line. This will serve as a temporary value holder.

2. Getline reads/gets the user input and stores/writes it to 'line'.


Yes.

Quote:
3. THIS IS WHERE I HIT A BUMP.
Choice is then set to a non-valid value. So that if the string in 'line' is not an integer, then choice will go to the default arguement in the switch statement.
(But i'm stuck wondering how the compiler knows to not change the value of choice if the user input for line is something like 'h' or 'bobs your uncle')


First off, "the compiler" doesn't "change" anything here; it produces a program. The program then has the "knowledge" of whether or not to change the value.

It works, briefly, because of how the action of reading is described, using the operator>>. Basically, you are calling a function that has already been set up to do exactly that.

Quote:
4. THIS IS WHERE I HIT A BRICK WALL.
stringstream parser(line);parser >> choice;

I really don't understand what a parser is



Then look up the word "parser" in a dictionary, or with Google, or something.

But that's not relevant. You know how the line "string line;" tells you that you are declaring a variable called 'line' of type 'string'? And "int x;" is declaring a variable called 'x' of type 'int'? And how "Watermelon my_melon;", a variable called 'my_melon' of type 'Watermelon'?

OK, so we haven't defined the type Watermelon, but that's the point: there is nothing special about the words 'string' or 'int'. They are names of types.

Similarly, 'stringstream' names a type that is defined in the <sstream> header. Thus, 'stringstream parser(line);' is creating a variable of type 'parser' which is of type 'stringstream'.

The only part that's unfamiliar to you here at this point is the '(line)' bit. This is a parameter for the stringstream constructor. You've heard of constructors, right?

I called the variable 'parser' because I am using it to parse the input. The word 'parse' is a perfectly ordinary English word that is used by non-programmers, by the way. (Just, perhaps, more-educated-than-average non-programmers.) You can look it up in an ordinary English dictionary.

Quote:
char c;if (parser >> c) {  choice = -1;}



You know std::cin? That thing you use to read the user input? It's a stream. When you write something like '{variable of stream type here} >> {some other variable here}', you are requesting to read from the stream's data "into" the other variable. Specifically, the stream data is treated as text, and the stream looks at whatever data is at the beginning of the stream, and tries to interpret it as a human-readable representation of a value that can fit in the other variable.

So if you read into an int, it looks for text that represents digits, and converts the resulting base-10 number into something that will fit into the variable. It goes one character at a time and looks at what symbol they represent.

In the first stage, it will just skip past any spaces, tabs or newlines, because those are "invisible" and not really relevant to human-readable formatted data.

Then it looks at characters one by one, to see if they represent digits - i.e., the symbols '0' through '9'.

If the first symbol it encounters isn't a digit, then the data obviously isn't an integer. So it gives up: it leaves the destination variable alone, leaves the stream data alone (so that you can try reading it into another kind of variable, or doing something else), and it marks itself as having encountered an error (so that you can check).

When you wrap that whole process with an 'if ()', you are basically checking whether such an error occurred. There is a bunch of cleverness that the standard library does for you in order to make that work.

So, the above code uses the same principles, except that:

1) We are reading from a stringstream, which is also a kind of stream, but which uses a string as its data source. This means there's another way for reading to fail: we can run out of data.

2) We are reading into a 'char'. This means that any symbol will work, and we will only ever read one symbol - but we still skip whitespace before looking for the symbol to read.

So what the code above does is: starting at the current position in the string (which is just after having read a number), we skip whitespace, and then try to read any symbol. If we succeed at this, then that means the string was not empty beyond the number, i.e. it had some garbage text after the number. So then we enter the if-block and set choice to -1, to signal "I am not happy with this input; it started with a number, but then there was other stuff after the number".
Quote:Original post by otreum
Uh oh, confused again! But I will keep trying to understand...at least until you give up.

Ok...so, stringstream CAN get data from the input on the keyboard, ASWELL as any other source, such as a default value of a variable?


A stringstream gets its data from one place: the string that you put into it.

But you can get that string from anywhere.

Quote:Where as cin can ONLY get input from the keyboard


std::cin gets its input from the "standard input" to your program.

When you run your program from the console, there are three programs involved: your program, the console, and the operating system. The operating system reads the keyboard and translates key-presses into characters, which it then passes to the console. The console does some more translation (for example, when it sees a "backspace" character, it cancels that off against the previous character; and it builds up text a line at a time in a buffer), and then, normally, feeds that data to the standard input of your program. However, when you run the program, you can instead tell the console to open a file, and use the contents of the file to supply the standard input for your program. (Similarly, you can tell it to put the standard output from your program into a file, instead of displaying the text in the console window.)

Operator overloading is not relevant to this. The purpose of operator overloading, in this context, is to tell the stream classes how to interpret text as an instance of your own class, or how to represent an instance of your own class as text.

Quote:When I input something like "H" into choice, and I then output choice, does it output that strange negative number (-85372395 etc) because the compiler is trying to convert a character into an integer?


No. It outputs "that strange number" because that's the value that 'choice' already had before the attempt at input. The attempt at input failed, so the variable was not changed. That's part of how the stream input works.

A variable in C++ always has a value. There is no special state for the 'choice' variable that represents "nothing was ever put here". The program reserves some bytes in memory to represent the variable, and that's it.

For primitive types like ints, if you didn't assign anything, then whatever happened to be in that chunk of memory ahead of time gets used.

For objects (instances of classes or structs), some kind of constructor normally gets called when you declare the variable. This is responsible for initializing the variable with an initial value. You can initialize ints, too: 'int foo = 0;' initializes the variable. Conceptually, it is different from 'int foo; foo = 0;' - although not in a way that you can actually observe in the final running program.

This topic is closed to new replies.

Advertisement