6

Look at this code:

#include <iostream>
using namespace std;
int main()
{
    string s;
    int n;
    float x;
again:
    cout << "Please Type this: ABC456 7.8 9 XYZ\n";
    cin >> s >> n >> x >> s;
    cout << "\nDo you want to try Again(y/n)? ";
    char t;
    cin >> t;
    if (t=='y' || t=='Y')
        goto again;
    return 0;
}

Just try to type "ABC456 7.8 9 XYZ" and press enter, it will cause the program exit before prompting the user to try again. I know the inputs are wrong and they are not in their types, but why it causes the exit? and how to avoid such exit?

Mat
  • 202,337
  • 40
  • 393
  • 406
Inside Man
  • 4,194
  • 12
  • 59
  • 119
  • @CppLearner It is a label for the labeled statement `again: cout << "Please Type this: ABC456 7.8 9 XYZ\n";` – Mankarse Apr 30 '12 at 06:15
  • @CppLearner: Its a label indicated for goto statement to be used. see 8th line dear.... – Addicted Apr 30 '12 at 06:17
  • Thanks. Well first, I hope `` is included in the header. Let me execute... It's hard to read with code are on the same line. Thanks. – CppLearner Apr 30 '12 at 06:17
  • 2
    You ask for a 7.8 (float) as the second item, but try to read that into `n`, an `int`. This is probably not a recipe for happiness (because `x` gets 0.8, s gets 9; and the blank or 'X' of XYZ is read as `t`). – Jonathan Leffler Apr 30 '12 at 06:19
  • @Jonathan Leffler I know the inputs are wrong, but why should it exit the program? – Inside Man Apr 30 '12 at 06:25
  • 1
    Because neither blank nor X is the same as either y or Y, so the character read into `t` doesn't satisfy the looping criterion. The first thing to do when puzzled about something like this is to print out the key controlling values, in this case `t`. If you printed the value in `t` (`cout << "t = [" << t << "]" << endl;`), you'd immediately see why the `goto` isn't executed. Or step through the code in a debugger and check the value that way. This technique is fundamental to debugging. – Jonathan Leffler Apr 30 '12 at 13:45

7 Answers7

7

Change

cin >> s >> n >> x >> s;

to

cin >> s >> x >> n >> s;

As you are entering 7.8 as 2nd input but you are collecting it in a integer variable instead of a floating point variable. As a result of this when you enter:

ABC456 7.8 9 XYZ

s gets ABC456, n gets 7 (as it is of int type and the input buffer still has .8 9 XYZ\n in it). Next n gets .8 and finally s gets "9". Now the input buffer has XYZ\n in it. The next time when you read the input into t to get the user's choice, X gets read into t and since it is not y or Y, the loop exits.

codaddict
  • 445,704
  • 82
  • 492
  • 529
  • 3
    This is not the answer. OP wants to know why the program exits. He swapped the types intentionally. – Shiplu Mokaddim Apr 30 '12 at 06:21
  • @shiplu.mokadd.im: Was updating the answer. – codaddict Apr 30 '12 at 06:26
  • @codaddict I just understand one things, if inputs are more than variables to store, then the program will exit entirely. But why it is like this? – Inside Man Apr 30 '12 at 06:30
  • 1
    @Stranger: You need to understand what the user enters and what gets read in the variables. After each variable gets read, you need to understand what is left in the input buffer. – codaddict Apr 30 '12 at 06:34
  • @codaddict so it seems I should erase left buffer after inputs. is there any way to do this? – Inside Man Apr 30 '12 at 06:38
3

Enter a debug line after the cin

cout << "s = " << s << "   n = " << n << "   x = " << x;  

And run

Please Type this: ABC456 7.8 9 XYZ
ABC456 7.8 9 XYZ
s = 9   n = 7   x = 0.8

Clearly the first ABC456 is read into the string s. The next was a integer therefore only the 7 is read into n and the 0.8 part was read into float x. Now the next input 9 was assigned to s again therefore the final value of s is the string "9". And now the first character of X gets fed into the next cin where it gets assigned to t. To confirm just insert another debug line cout << "\nt = " << t; after you input t. Therefore the if is false with the value of t assigned to 'X' therefore the program exits.

If you input ABC456 7.8 9 YZ instead the program will ask for input another time, as now t will have 'Y'.

phoxis
  • 60,131
  • 14
  • 81
  • 117
2

When the stream extraction operator >> encounters invalid input, it puts the stream into a mode where no more input is extracted.

You can detect this mode using a Boolean test such as if ( cin ), and reset it using cin.clear(). After doing so, the invalid input remains in cin's input buffer, so you need to process it somehow, such as by ignore.

Better yet, the extraction operator returns cin so you can test while you extract:

if ( ! ( cin >> s >> n >> x >> s ) ) {
    cout << "You fail horribly!\n";
    cin.clear();
    cin.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );
}

For more depth see Semantics of flags on basic_ios (and I'm sure there are a few questions on this site that are exact duplicates of this).

Community
  • 1
  • 1
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
2

Answer https://stackoverflow.com/a/10379322/924727 expalins what happens. About the why, we must go a bit into philosophy.

The C++ stream model is not thought for "human interaction": it is a generic converter of a virtually infinite sequence of characters into a list of space separated "values" to be converted into the supplied "variables".

There is no concept of "input and output interleaving to for a dialog". If you write your input into a text file like myinput.txt (unsing correct input)

ABC456 9 7.8 XYZ
Y
ABC456 5 6.7 XYZ
N

and ron your program from the command prompt like

   myprogram < myinput.txt

your program will run ... and no "pause" can be required to see the output, since no-one is sitting there to see it and answer it.

The program pauses to wait user input not because of cin >>, but because cin is not in fail state and the buffer is empty and the source the buffer remaps is the console. It is the console that waits for '\n' before returning, not cin.

When cin >> n is called...

  • the operator>> function is called, and it ...
  • gets the num_get facet from the stream locale and call its get function that...
  • call the stream buffer sbumpc repeatedly to get the digits and compute the number value.
  • If the buffer has content, it just return its characters one after the other. When no more character are present (or if it is empty) ...
  • The buffer ask the operating system to read from the low level file.
  • If the file is the console, the console internal line editor is invoked:
  • This makes the console to stuck letting the user press character and some controls (like the backspace, for example) until, when Enter is pressed
  • The console line editor returns the line to the operating system that will let the contents available to the input CON file ...
  • That is read by the buffer (after passing the read characters to the cvt locale facet, but this is a detail) that fills up itself.
  • Now do yourself the returns an unrolling.

All this mechanism makes the fact that - if you type more than required - the buffer content remains available to the next >> calls, independently it is or not another program line.

A proper "safer" parse requires, afer an input has been read, the stream state to be cleared and the following content to be ignored up to the next '\n'. This is typically done with

cin.clear(); 
cin.ignore(numeric_limits<std::streamsize>::max(), '\n');

So that, whatever had been typed is discarded, and the next cin>> finds a buffer with no data (just the '\n', that is trimmed as "beginning space"), thus causing the console to go in line edit mode again.

Community
  • 1
  • 1
Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63
  • -1: There is a concept of input and output interleaving interactively, and `cin` and `cout` are linked together in just such a way. See `basic_istream::tie()`. Besides being incorrect, this answer is rambling and unclear. – Potatoswatter Apr 30 '12 at 09:01
  • @Potatoswatter: When talking about philosophy everything is "rambling": The point is not to be correnct in Touring sense, but to be correct in the effects. cin and cout the way are used by the OP are not in sync each other. The OP clearly state he wants to know "why" not "what". And to explain "why" you must focus only on the OP selected aspects. I respect your opinion, but my opinion about it is that you have just a prejudice. – Emilio Garavaglia Apr 30 '12 at 09:12
  • `cin` and `cout` *are* in sync with each other because any operation on `cin` automatically flushes `cout`. (This is missing from your sequence of steps.) You are supporting an incorrect assertion about what the library does *not* do with invented philosophy unconnected to the library's design intent. A proper philosophical argument would be supported by references, of which you have none. – Potatoswatter Apr 30 '12 at 09:28
  • "*are in sync with each other because any operation on cin automatically flushes cout*". No: this makes cout in sync with cin, but not viceversa: cin is not "flushed" (how??) by cout. Exceeded input remain in cin no matter of cout. That's the whole point. If you don't get it, that can be may fault, only after no-one else gets it. You tell me abut doing false assertion, but you do the same in telling me so. About the "library design intent ..." please, at least qualify yourself as a library designer! – Emilio Garavaglia Apr 30 '12 at 09:39
  • I have contributed to GCC's iostreams implementation, so I've studied this a fair bit https://www.google.com/search?q=david+krauss+site%3Agnu.org http://gcc.gnu.org/ml/libstdc++/2010-09/msg00133.html . Flushing `cout` is sufficient to keep them in sync, there is no such thing as flushing an input stream and I said no such thing. The answer to the question is that `cin` enters an error state, as James Kanze and I answered. If that is also your intended message, it is very unclear and rambling. – Potatoswatter Apr 30 '12 at 10:09
  • 1
    "*The answer to the question is that cin enters an error state*" No, in this particular case cin is not errored. The data are distributed differently as expected, letting a "tail" available to the next input, that is not flushed by existence of an intemediate output, and that is considered a valid answer of value "No". I don't want to enter to the reason that made the design as such (anyway, your contribution in the implementation is not "design", the spec already existed), but that's the point. – Emilio Garavaglia Apr 30 '12 at 11:56
1

The program hits a problem or exception when it tries to but the wrong data types together. Perhaps you want to look at the documentation for the >> operator on cin, look for specifics on what it does when it hits wrong input for data types, and look over the cin>> line and your input for any places that this might be occurring so you can validate the input is handled correctly

Adam Miller
  • 1,756
  • 1
  • 25
  • 44
1

when you call cin.clear(), you should call cin.sync() as the same time.

1

Once the stream detects an error, it is in an error state, and all further attempts to input will be no-ops. Accessing variables read by a stream without first testing whether the read succeeded is undefined behavior, so in theory at least, you're program could do anything. (In practice, if the uninitialized variable is of type char, all you risk is a random value.)

When reading line oriented input, the best solution is to use std::getline. then use the string that was input to construct an std::istringstream to parse the line. This leaves the input stream in a good state, and already synchronized for the next input. I'd use something like:

void
getInput()
{
    std::string line;
    std::cout << "Please type this: ABC456 7.8 9 XYZ" << std::endl;
    std::getline( std::cin, line );
    if ( ! std::cin ) {
        //  Very unexpected error... 
        throw SomethingSerious();
    }
    std::string s;
    int n;
    float f;
    std::istringstream toParse( line );
    toParse >> s >> f >> n >> s;
    if ( ! toParse ) {
        //  Input incorrect...
    }
}

bool
tryAgain()
{
    std::cout << "Do you want to try again (y/n)? ";
    std::string line;
    std::getline( std::cin, line );
    return line.size() == 1 && (line[0] == 'y' || line[0] == 'Y');
        //  But I'd be more tolerant with regards to spaces...
}

bool
oneInput()
{
    getInput();
    return tryAgain();
}

int
main()
{
    while ( oneInput() ) {
    }
    return 0;
}
James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • Thanks For reply, but it is a long trick! BTW Thanks again, it can help too ;) – Inside Man Apr 30 '12 at 08:15
  • 1
    @Stranger Handling input correctly usually is fairly complex, since you have to be able to handle anything the user inputs. And users can be very unpredictable in what they enter. – James Kanze Apr 30 '12 at 08:32