Just using the >> (extraction) operator with cin is quite powerful.
data_type var_name; cin >> var_name;
Here we can declare a variable (var_name) of [nearly] any data type (data_type) and have cin extract keystrokes from the user and translate those characters into meaningful data stored in our variable!
Extraction (>>) always skips over leading whitespace as if it were junk. So, if our user typed:
Program prompts with this: user_data
We'd get the same results as if they'd typed this:
Program prompts with this:
user_data
(That was actually " \t\n\n\t " in front of the same data as before...hard to tell, isn't it?)
>> (extraction) also always stops translating when it finds a character that does not belong in the desired data type:
Data Type | Allowed characters |
---|---|
char | [almost] any single keystroke (can't read spaces; difficulty with many control codes) |
short | a leading sign (+ or -) followed by a sequence of digits (0, 1, 2, ..., 9) the leading sign is optional (not to exceed SHRT_MAX or SHRT_MIN in magnitude) |
long | a leading sign (+ or -) followed by a sequence of digits (0, 1, 2, ..., 9) the leading sign is optional (not to exceed LONG_MAX or LONG_MIN in magnitude) |
double | a leading sign (+ or -) followed by a sequence of digits (0, 1, 2, ..., 9) followed
by a dot/decimal point (.) followed by
another sequence of digits followed by an e or E followed by a
sign followed by another sequence of digits the leading sign and the sign after the e/E are optional; the entire e/E clause is optional; either the first digit sequence or the dot and its digit sequence may be absent — but not both (not to exceed DBL_MAX or DBL_MIN in magnitude (except 0.0); not precise beyond DBL_EPSILON or DBL_DIG digits of precision) |
bool | cannot be read by default typically we read a character and test if its low-/upp- er case version is equal to the first letter of the local yes/no pair (or whatever is appropriate for the prompt we gave the user) |
string | [almost] any sequence of keystrokes (can't read spaces; difficulty with many control codes) |
If anything else is found while translating a particular data type, the extraction operator (>>) just stops at that point in cin's buffer. Problems arise when nothing has been successfully translated when such an event occurs!
When cin was trying to extract (>>) data and finds nothing that it can work with, if feels that it has failed us in this simple task. It doesn't actually tell us, however; expecting us to come to it to ask what's wrong. (Very passive-aggressive, no?)
To find out if the input you just attempted worked or not, you use the fail() function. This is one of the many functional behaviors offered by the class of which cin is an object. To access this function with respect to the keyboard input stream (cin), you just mention them together with this syntax:
cin.fail()
This is read from right-to-left as "call the fail function with respect to the input stream cin". The () represent "call" and the . represents "with respect to". (Of course, fail represents "the fail function" and cin represents "the input stream cin".) Here they are aligned for ease of study:
cin . fail () the input stream cin with respect to the fail function call
Or, in order for English reading:
() fail . cin call the fail function with respect to the input stream cin
Okay. Now we can detect that cin had trouble during an extraction (>>) operation. But what do we do about it?
First, we place our detection code immediately after the suspected operation (>>):
cout << "Prompt..."; cin >> var; while (cin.fail()) { }
Notice that we've used a while loop mechanism so that as long as the user's entry continues to elude proper translation, we'll circle back in an attempt to force the slimy beggar to type a decent response!
... Oh, sorry, got carried away there...*breathe* Ahh...
Now, where were we? Oh, yes. We loop the fail check so that we can keep the program from using the variable's data until after we've gotten a successful reading from the user. (If the user never gives us reasonable data, how is the rest of the program supposed to work with it? And what of further input attempts? If we don't fix up cin right now, he'll never be the same again. ...and I can't stand the whimpering...)
So, inside the loop, we need to patch cin up so that we can let the user know that their input wasn't useful to us (and maybe why) and give them another chance to enter their response:
cout << "Prompt..."; cin >> var; while (cin.fail()) { cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n'); cerr << "\n\aInvalid data!\n\nPlease make it "; if (var < 0) { cerr << "larger"; } else { if (var > 0) { cerr << "smaller"; } else { cerr << "a number"; } } cerr << " next time!\n\n"; cout << "Prompt again..."; cin >> var; }
Here we start by clearing cin's memory of what the mean, evil user did to him and >> just moments ago. Then we have ignore() throw away not just one, but as many characters as it takes from cin's buffer until it has cleaned out the entire lot of crap-ola offered to us by the user during that last input attempt.
Recall that the first input to ignore represents how many characters it should throw out and numeric_limits<streamsize>::max() is its idea of ∞. (Don't forget to #include limits to use numeric_limits<streamsize>::max().) The second input is a special character that, once tossed out, signals that the tossing is finished — the ter-mi-na-tor. *bad Austrian accent* Here we use a '\n' character to say we want an entire input line thrown out. (This is because the user hit the <Enter> key to signal they were done entering their response to our prompt and <Enter> is represented by the character '\n'.)
The next part is our error message (hence we send it to the screen via cerr rather than cout). Unfortunately, the whole if structure is suspect and not likely to work on your system (unless you have a new-ish Linux installation or have kept your GCC up to date; g++ 4.4 or later should behave properly; unfortunately I've no such system to check it on; *pout* *sigh*). If it did work, though, it would let the user know that their input was either below the var's type's minimum or above its maximum or simply not even a number in the first place! If you find it not working, don't throw everything out! Just simply comment out what isn't working right now and replace it with a generic bit:
cout << "Prompt..."; cin >> var; while (cin.fail()) { cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n'); cerr << "\n\aInvalid data!\n\nPlease make it ";cerr << "~*useful*~" /* if (var < 0) { cerr << "larger"; } else { if (var > 0) { cerr << "smaller"; } else { cerr << "a number"; } } */cerr << " next time!\n\n"; cout << "Prompt again..."; cin >> var; }
Then, once you get an up-to-date compiler, you can delete the generic bit and uncomment the nicer part to give more accurate instructions to your sad, demented, little user. *smile* *cackle* *mu-ha-ha*
There are several special circumstances that can give us headaches during input other than data that just isn't data(!). First off, humans have a desperate need for notation and formatting — lots of symbols mixed in with the data. Some of it has true meaning of its own. But some doesn't and is just there to make things more orderly or group things together. Secondly, humans are lazy and will often have short-cut ways of handling certain situations. So, not only do we clutter the input stream with annoying symbols, but sometimes we leave out parts of the data that is just 'assumed' to be a certain thing(!). Finally, there is the inevitable need for humans to blather on about useless bits of trivia of only tangential interest or applicability to the situation at hand (see the phone number example below, for instance). All of these can be solved, but we'll need some help.
Because of human psychology and frailty, we have a lot of special notational conveniences for ourselves. We like to put information in certain formats depending on the field of study/application, situation, locality, etc. For example:
Notation | Meaning |
---|---|
(42, -3.5) | ordered pair, possibly a point |
(-42, 3.5) | possibly an ordered pair
possibly interval notation with both ends open |
[-42, -3.5] | interval notation with both ends closed |
12/10/06 | most likely a date; but is it December 10th or October
12th? and the century's suspect, but I'd have to guess
2006 unless the program is some kind of history quiz/review
could also be a division problem...*shrug*...then it has other problems altogether... |
10:08 | most likely a time: ten o'clock and eight minutes (8 after 10);
but is it in the morning or at night?
could also be a ratio of two things...*shrug* |
10:08 p.m. | most definitely a time: ten o'clock and eight minutes (8 after 10) at night |
22:08 | most likely a time: ten o'clock and eight minutes (8 after 10) at
night
could also be a ratio of two things...*shrug* |
42°12'57" | 42 degrees, 12 minutes, 57 seconds — navigational notation |
12'10" | most likely a length measurement: 12 feet and 10 inches
could also be a truncated navigational measurement: 12 minutes, 10 seconds...*shrug* |
123-465-7890
1-123-465-7890 (123)465-7890 1-(123)-465-7890 123/465-7890 1-123/465-7890 |
all different representations of the same phone number (no, I don't
know whose it might be; call it if you're that interested!)
and people seem to make up new ones all the time! (at least we've gotten rid of the days of INK-7890 when people insisted on turning the exchange into a name to help them remember it better... the most famous being KLONDIKE, but I don't know where that was or what it stood for...unless it was KLK which would be 555 which, of course, is the TV/movie/literary exchange — supposedly unused in real life |
And on and on and on the list goes! Humans have this need to break concepts down and break up sequences of digits. All this notation could be the death of us!
Luckily, we've got friends and so does cin — all of which will be helping us solve this problem.
Our first helper is the char data type. It is simple and plain and unassuming, but placed correctly in an input request sequence and it can overcome major obstacles! Take the input of a point in 2D space, for example:
Please enter your 2D point: (42, -3.5)
double x, y; char t; cout << "Please enter your 2D point: "; cin >> t >> x >> t >> y >> t;
As we expect the user to enter an opening parenthesis ('(') followed by their floating-point value for x and then a comma (',') followed by their floating-point value for y and finally a closing parenthesis (')'), we tell cin to expect a character followed by a double followed by another character and then another double and finally another character. Extraction (>>) then gets to work locating and translating each piece — in the order they were requested.
(Note that the variable t is overwritten at each next extraction (>>). Also note that >> would just as happily translate t from a 'x' or a '+' or even a '7' as it would from the intended '(' for the first symbol. cin doesn't understand the context of our program — it just does as it is told.)
(Of course, the middle extraction (>>) of t couldn't be a '7', since that would have been read as part of the x variable...unless they put a space after their value for x...*shrug* Similarly for the final extraction (>>) of t and its following of y.)
This strategy can be applied any time we are expecting the user to enter symbols, letters, or other such notation amongst their numeric values.
[UNDER CONSTRUCTION...]
... something about peeking at the next non-whitespace character from the current input line/buffer ...
optional numeric value in front [of non-numeric data?] |
cin >> ws; ch_var = cin.peek(); if (isdigit(ch_var) ¦¦ ch_var == '-' ¦¦ ch_var == '+') { cin >> num_var; } else { num_var = default_value; } cin >> non_num_var; |
Or perhaps: | |
optional non-numeric value in front [of number?] |
cin >> ws; symb_var = cin.peek(); if (!isdigit(symb_var) && symb_var != '-' && symb_var != '+') { // still need to *read* it in cin >> symb_var; } else { symb_var = default_value; } cin >> num_var; |
Recall that the ws is necessary here for two potential reasons. If our peeking immediately follows a prompt, then the extraction of a ws will force cout to print it. (peek doesn't normally do this itself since it isn't truly 'reading' anything from the user — merely looking at what they've entered.) Secondly, since we are peeking in order to later extract data, we need to align these two operations' world views. Extraction (>>) is a terrible bigot and believes all white-space is useless — throwing it out when it precedes real data or stopping before it when it follows real data. peek, on the other hand, appreciates the differences in all things and respects white-space just as much as a digit or letter or punctuation mark. Since our end goal is extraction, its view of things — however abhorrent and abrasive — must win out. Thus the ws to suck out the white-space before we peek ahead.
Note that if you are looking for a decimal value, you'd need to add a clause such as && cin.peek() != '.'. (This will catch fractional numbers entered without a leading zero.) You'll attach this clause with either && or ¦¦ depending on which branch form you're using, of course.
When you need to have something optional at the end of a line, however, you'll need to replace the ws with our 'while with peek, isspace, '\n' in/equality, and an ignore' loop. This will make sure that you only throw out the spacing that isn't the user's signal of being done with their input (the new-line character).
while (cin.peek() != '\n' && isspace(cin.peek())) { cin.ignore(); } if (cin.peek() != '\n') { // there was something before the end of input! use it... } else { // there was NOTHING before the end of input...default..? }
...something about bool used to remember the presence/absence of an optional part for fixing it up later...
near the beginning of your code:
const char default_symbol = '@'; char symbol; bool symbol_was_read = false, symbol_in_front; cin >> ws; if (ispunct(cin.peek())) { cin >> symbol; symbol_was_read = true; symbol_in_front = true; } cin >> number; // note this code allows for no space after the number before a trailing symbol! // use the while version of ws from above to allow for spacing after the number... if (cin.peek() != '\n') { cin >> symbol; symbol_was_read = true; symbol_in_front = false; } // fill in symbol if it wasn't read above... if (!symbol_was_read) { symbol = default_symbol; symbol_in_front = true; }
later in the program:
if (symbol_in_front) { cout << symbol; } cout << number; if (!symbol_in_front) { cout << symbol; }
With character data, of course, you could use a '\0' flag initialization to remember later whether that data had been entered or not. Although that only works for char-typed data, it can lead to more flexible code:
if (front_symbol != '\0') { cout << front_symbol; } cout << number; if (rear_symbol != '\0') { cout << rear_symbol; }
Here, for instance, the user can have symbols both before and after their input data and we'll recall them — and print them out! — separately later.
[MORE SOON!]
something about just ignore'ing everything that's left over after you've read what you wanted..!
cin >> data_I_want; cin.ignore(numeric_limits<streamsize>::max(), '\n');
... something about NOT using a string to read it all in since that wastes user time and system time and resources (like RAM) ...
cin >> data_I_want; getline(cin, string_variable);
[MORE COMING SOON!]
In the following table n is a numeric variable (i.e. of type short, long, or double). Other useful notes follow the table.
I don't need the character later | I'll need to use the character later | The user may optionally enter the character | |
---|---|---|---|
c n | char t; cin >> t >> n; | char good_name; cin >> good_name >> n; | // declare variable as before -- good or scrap cin >> ws; if ( /* cin.peek() is NOT a valid opener for n */ ) { cin >> ____; // t or good_name } else { cout << "\n\aMissing notation!\n"; // set a bool to remember this? // set a default value to good_name? } cin >> n; |
c \n | cin.ignore(numeric_limits<streamsize>::max(), '\n'); | char good_name; cin >> good_name; | // declare variable as before -- good or scrap while (cin.peek() != '\n' && isspace(cin.peek())) { cin.ignore(); } if ( cin.peek() != '\n' ) { cin >> ____; // t or good_name } else { cout << "\n\aMissing notation!\n"; // set a bool to remember this? // set a default value to good_name? } |
Note that the first character followed by a newline variant will eat the newline as well as the 'character'. The other two leave the newline on the boat for future processing.
In the optional character before a number variant, the if condition is described but not coded. The code would, of course, vary from situation to situation based on what type of number you were dealing with. It is always valid for a number to start with a digit. But sometimes a leading sign is acceptable — positive or negative. In other situations, a fractional value may be begun without a leading 0: .25 for a quarter, for instance. A blanket summary of codes for valid numeric openers would be:
isdigit(cin.peek()) ¦¦ cin.peek() == '-' ¦¦ cin.peek() == '+' ¦¦ cin.peek() == '.'
You'll just have to look at your application to decide which or clauses to leave off. (And don't forget to apply DeMorgan's Laws — the if called for peek to NOT be a valid opener!)