[C++] Primitive type checking problem

Started by
3 comments, last by rippingcorpse 16 years, 2 months ago
OK, laugh at me all you want because I understand there is an insider joke on these boards involving newbs using char... Anyways, all is well in my program until the user enters incorrect input. I made it so it compares the user's input to a value of zero. If the input is greater than zero, it continues, otherwise it warns the user that the input was incorrect. The problem is that if the first if-statement is true, it skips the ending input. I entered a system("PAUSE") to check this, and the last line 'cout << "Would you like to continue? (Y or N)" << endl;' is read but cin >> cOptions is completely skipped. I have no idea why. :| TL;DR; 'cin >> cOptions;' is skipped if the first if-statement passes as true. Need help. ALSO, what is a better way to check user input, such as when a user enters a char instead of an integer? Comparing it to zero seemed to be OK for this situation but it wouldn't work well if I needed negative data as well.
#include "stdafx.h"
#include <iostream>
using namespace std;

int main()
{
	float fFirstNumber;
	float fSecondNumber;
	float fCompareNumber = 0;
	char cOptions;

	do{

	cout << "Enter 2 numbers to add: " << endl;

	cin >> fFirstNumber;
	cin >> fSecondNumber;

	if ((fFirstNumber < fCompareNumber) || (fSecondNumber < fCompareNumber)){
			cout<<"Your input is incorrect" << endl;
	}
	else{
		cout << "The product is " << fFirstNumber + fSecondNumber << endl;
	}

	cout << "Would you like to continue? (Y or N)" << endl;
	cin >> cOptions;
	}while(cOptions == 'y' || cOptions == 'Y');
}

Advertisement
I'm not sure on this, but it might be that there is still a character left in the buffer when cin >> cOptions is reached, so it takes that character from the buffer rather than grabbing what the user typed in. I'm guessing that character might be from you pressing Enter. If this was the case, you would have to clear the buffer, also the problem might not arise if you were using strings.

Try adding an extra cin >> cOptions2 or something, and read that second cin. If I am correct, that second cin should work fine since the first cin is grabbing that leftover character in the buffer, which allows the second cin to properly grab the users input.

Once again, not quite sure on this, but I've ran into problems similar to yours in the past, and a few times that was the problem.
To test to see if a formatted extraction operation succeeded, you can check the status of the stream itself. If the stream compares as false, then there was a bad input. To reset the stream to receive new input, you need to clear the status bits with the clear() member function. However, this leaves the bad characters in the input, so you will also need to use ignore() to ignore the remaining characters in the input buffer. Ex:
  int bob;  std::cin >> bob;  if (std::cin) {     std::cout << bob << std::endl;  } else {    std::cout << "Bad input." << std::endl;    std::cin.clear();    std::cin.ignore(std::cin.rdbuf()->in_avail());  }
Quote:Original post by rippingcorpse
OK, laugh at me all you want because I understand there is an insider joke on these boards involving newbs using char...


It's not a joke. We don't laugh at "newbs" here for being inexperienced; we ridicule people for refusal to learn anything. And there is nothing funny about doing things the hard way for no good reason.

Quote:The problem is that if the first if-statement is true, it skips the ending input. I entered a system("PAUSE") to check this, and the last line 'cout << "Would you like to continue? (Y or N)" << endl;' is read but cin >> cOptions is completely skipped. I have no idea why. :|


The diagnosis is quite incorrect, but I can't blame you.

There are two things you have to know about here.

1) When input isn't consumed, it sticks around. Reading from std::cin doesn't "ask for" input; it simply starts reading whatever is available - just like with a file stream. However, the special thing about std::cin, compared to files, is that it commonly will be in a "blocked" state: there is more data in the stream (it hasn't reached "EOF" - with the standard input, you have to indicate that explicitly with Ctrl-Z on Windows or Ctrl-D on Linux), but it isn't "available" yet (because the user hasn't typed it yet. The data your program receives goes into some kind of buffer, and any excess is *immediately* available the next time you read from std::cin; if the program determines that there is "not enough" data (for example, you tried to read into a std::string, and there is no trailing whitespace in the buffer, so you must be "in the middle of" a word), *then* it will wait for more.

Meanwhile, by default at the console, there is buffering being done by the console. You can't (portably) do anything about that from within your program, because it's another program doing it. Your program actually receives data in line-sized chunks, not simply as it's typed.

2) When the input is incorrect, two things happen. Or more accurately, one thing doesn't happen, and one thing happens. What doesn't happen is any actual reading from the input stream. The incorrect input stays there. What does happen is that the stream goes into a "failed" state.

Every stream has a few special state flags: 'eof', which means an attempt was made to read past the end of the "file", 'fail', which means input data was in a wrong format, and 'bad', which means something else went wrong. But normally we just think of the stream as "ok" (none of those are the case) or not ok. Whenever the stream is not ok, any attempt to read more data from it will fail automatically. Also, using a stream instance in an if-condition, while loop, etc. implicitly tests the ok-ness of the stream.

Quote:ALSO, what is a better way to check user input, such as when a user enters a char instead of an integer? Comparing it to zero seemed to be OK for this situation


It's not at all OK, for a couple of reasons.

First off, because nothing is read, the integer remains at its previous value. That isn't necessarily zero. If you didn't initialize it, it isn't necessarily anything in particular. It could be a different value each time you run the program. When you choose not to initialize something, you are accepting responsibility for making sure it is written to before it ever gets read. Otherwise, anything can happen. In debug mode, it might be given a special value (something unlikely to be a number you'd actually use, and which looks suspicious when output in hex) to help you find the problem. In release mode, it will probably be whatever happened to be lying around in memory at the location that happened to be allocated for your variable.

Also, what if the user actually types 0?



Anyway, let's analyze what happens.

Program prompts for two numbers. User types "foo" and hits return.

The characters 'f', 'o', 'o', '\n' become available to the program. The program attempts to read into fFirstNumber using operator>>. The first character it sees is 'f', which can't possibly start a floating-point number. The stream fails, and the 'f' is left in the input buffer.

The second read immediately fails, because the stream has the failbit set.

You compare two uninitialized numbers to zero (BTW, there is not really a reason to make a constant for this zero value), and they happen to be negative for whatever reason (which you cannot rely on), and the program outputs an error.

The program prompts for continuation, and tries to read a character. The stream still has failbit set, so cOptions is left alone, and is still uninitialized. Then you compare an uninitialized value to 'y' and 'Y', and it happens not to match either of those (which you still cannot rely upon).

The reading-in lines are never "skipped"; they just fail to do anything.



So by now, you must be begging to know how we fix these things. ;)

The member function .clear() of input streams allows us to reset the failed state, effectively telling it to try again. Of course, nothing has changed yet; a read that failed before is likely to fail again if tried again. In particular, if we're trying to read a number and get a word, the word is still there if we just .clear() and try to read the number again.

Or, in our case, the 'f' of "foo" will be detected as the response to "Would you like to continue?", which is not what we want.

Usually we will pair a .clear() call with a .ignore() call, to "skip past" the "bad" input. (Of course, this doesn't help when eofbit gets set. ;) ) The interface for .ignore() is clumsy: you specify a maximum number of characters to ignore, and a character to ignore until. Typically, if we are asking for input from a user, the character to ignore until is obvious: it makes sense to ignore until "\n" (newline), because we will receive input a line at a time, and that will ignore the current line. But we don't really want a limit on the number of characters to specify. We need to put in a big number there, or use a special constant (std::numeric_limits<std::streamsize>::max, from the header <limits>) that is guaranteed to be "big enough".

This is ugly, and too much work. Also, consider that the user might type something like "42 76 trombones"; even when we get valid input, we still probably want to ignore the rest of the line. Or maybe we even want to check if there was a non-empty "rest of the line", so we can scold the user. Our code will quickly become a mess.



To avoid this, we need a different approach. We will start by always reading a line of input at a time. This makes sense; we can be sure that our program's input process is always in sync with the console line buffering. We read a line at a time by using a std::string object to hold the line, and the free function std::getline to read it.

Then, because we need to interpret the line contents in different ways, we need a way to re-parse the input text as if it were coming from a stream. Fortunately, we have a way to do that: the library class std::stringstream, from the header <sstream>. We simply construct it from the string, and treat it like any old input stream after that. Then we can check the result in an if condition to see if the input was valid. (In fact, we can do the reading inside the if-condition, as well. This is a standard way to write the code, in fact. See also.)

And if it isn't? That's the beautiful part: just report the error. We don't need to do any clean-up. The std::stringstream instance gets destructed, and if we have this in a loop, it will get re-constructed the next time around using the next line of user input.

It looks like:

#include <iostream>#include <sstream>#include <string> // for std::string, naturallyusing namespace std;// I pull the "ask yes or no" bit into a separate function because (a) it makes// the control structure a little trickier if we try to do it in-place, and (b)// in a real program, it's likely to be useful all over.bool askUser() {	string input;	getline(cin, input);	// This time, the parsing is really simple, so we don't bother with	// a stringstream object:	if (input.empty()) { return false; } // user just hit return without typing anything.	char answer = input[0];	return answer == 'y' || answer == 'Y';}int main() {	// To avoid the EOF or "bad" cases with std::cin, we check those in	// the loop condition as well:	while (cin) {		// Scope your variables properly, please.		cout << "Enter 2 numbers to add: " << endl;		string input;		getline(cin, input);		stringstream ss(input);		float first, second;		if (ss >> first >> second) { // we read them OK			cout << "The product is " << first + second << endl;			// Er, that's a sum, not a product, but OK...		} else {			cout<<"Your input is incorrect" << endl;		}		cout << "Would you like to continue? (Y or N)" << endl;		if (!askUser()) { break; }	}}
OK, I fixed it. I went with Zahlman's technique, which I'm still learning.

I was just used to programming languages that had automatic garbage collection. Thanks for your help.

[Edited by - rippingcorpse on January 31, 2008 1:43:38 PM]

This topic is closed to new replies.

Advertisement