C++ cin.fail() executes and moves to next line even though input is of another data type

て烟熏妆下的殇ゞ 提交于 2021-02-05 09:17:10

问题


I am using get.fail() to check if there's any char in the input, and if there is I would like to give the user a chance to re-enter. However, the program seems to still accept the user input whenever there's an integer in front of the input no matter the case. Say w1 and 1w, the program will tell the user that the it only accepts integers while the latter one accepts the input and moves over to the next line which then causes another problem.

    void userChoice(int input){

        switch(input)
        {
            case 1:
                insert();
                break;
            case 2:
                display();  
                break;
            case 3:
                update_data();
                break;
            case 4:
                delete_position();
                break;
            case 5:
                cout<<"Thank you for using the program\n";
                exit(0);
                break;
            case 6:
                tellSize();
                break;
            default:
                cout<<"Not an option\n";
                cin>>input;

                while(cin.fail())
                {
                    cin.clear();
                    cin.ignore(INT_MAX, '\n'); 
                    cin>>input;
                    break; 
                }
                userChoice(input);
        }

    }

Referring to the code above, say I give the input 1w. The program will still execute case 1 as if there's nothing wrong, and then w is somehow passed into the insert() function which is not what I want. I would like the program to let the user re-enter the input no matter if it's 1w or w1, in short I do not want the program to move over to the next line if there's a char in an integer input.

tl;dr: Why does the code below still execute when the cin is 1w, shouldn't it print "Enter number only" since there's a character in there?

Edit: Here's a quick program I made, to reproduce the error I am facing, I first enter 1h and here's the first bug I'm facing, why is the program still executing when there's a char h in the input? Afterwards in the second input, I enter 2w and the program prints out 2, shouldn't the program loop the while loop since there's a char w in the input?

#include<iostream>
#include<iomanip>
#include<limits>
using namespace std;

void option_1()
{
    int amount_input;

    cout<<"Enter the amount of cake"<<endl;
    cin>>amount_input;
    while(cin.fail())
    {
        cin.clear();
        cin.ignore(numeric_limits<streamsize>::max(), '\n'); 
        cout<<"Enter number only\n";
        cin>>amount_input;
    }
        cout<<amount_input;
}

void options(int input)
{
    bool fail;

    switch(input)
    {
        case 1:
            option_1();
            break;


        default:
            cout<<"Not an option"<<endl;
            cin>>input;
            while(cin.fail())
            {
                cin.clear();
                cin.ignore(numeric_limits<streamsize>::max(), '\n'); 
                cin>>input;
                break; 
            }
            options(input);

    }
}

void menu(){
    int user_input;

    cout<<"Enter 1 to print something\n";
    cin>>user_input;
    options(user_input);
}

int main(){
    menu();
}

回答1:


From your comments, it appears you want integer input exclusively and do not want to allow input of additional character after the integer like 1w, even though 1 would be converted to an int while leaving w unread to be removed by .ignore(...) after your call to .clear(). (as mentioned above, your use of .clear() and .ignore(...) are now correct.

If this is your intent, you need a way to check if there is anything else following the integer input by the user. To do that in a non-blocking way if nothing actually exists, you have a couple of options. (e.g. you can .peek() at the next character -- but you only get one, or you can use a line-oriented input approach) The line-oriented approach allows you to read the entire line of user input into a string and then extract the integer value and check whether there is anything else contained in the line of input.

The most straight forward way is to create a std::basic_stringstream from the line of data read with getline(). This approach, consumes the entire line of user input and provides you with all the tools you need to extract whatever information you may want from the line. It also does this in a way that does not effect any of your subsequent user inputs.

While I would recommend you combine your void menu() and void options(int input) functions so you simply have one function to handle input processing for your menu -- there is nothing wrong with breaking it in two other than the possibility of a few lines of code being duplicated. The following is just a suggestion on how to handle your menu() function to only allow integer input. You can adapt it to the remainder of your code.

You will need a couple of additional includes:

#include <sstream>
#include <string>

I would also #define the first and last acceptable menu entries so you have those constants available in your code in a place that can be easily changed as you add to your menu, e.g.

#define MENUFIRST 1     /* first valid entry */
#define MENULAST  1     /* last valid entry */

(note: that will allow only 1 be entered as a valid menu entry)

To limit you menu() function using the approach outlined above, you could do:

void menu(){

    int user_input = 0;
    string line, unwanted;

    for (;;) {  /* loop continually until valid input received */
        cout << "\nEnter 1 to print something: ";
        if (!getline (cin, line)) { /* read an entire line at a time */
            cerr << "(user canceled or unrecoverable stream error)\n";
            return;
        }
        stringstream ss (line);         /* create a stringstream from line */
        if (!(ss >> user_input)) {      /* test if valid integer read */
            /* test eof() or bad() */
            if (ss.eof() || ss.bad())   /* if not, did user cancel or err */
                cerr << "(empty-input or unreconverable error)\n";
            else if (ss.fail())         /* if failbit - wasn't an int */
                cerr << "error: invalid integer input.\n";
        }
        else if (ss >> unwanted) {      /* are there unwanted chars? */
            cerr << "error: additional characters following user_input.\n";
            user_input = 0;             /* reset user_input zero */
        }       /* was int outside MENUFIRST-to-MENULAST? */
        else if (user_input < MENUFIRST || MENULAST < user_input)
            cerr << "error: integer not a valid menu selection.\n";
        else    /* valid input!, break read loop */
            break;
    }

    options(user_input);
}

The comments should be self-explanatory given the discussion above, but let me know if you have questions. Using the function with the rest of your code (and commenting the unused // bool fail;), you can test whether it meets your requirements, e.g.

Example Use/Output

$ ./bin/menu_int

Enter 1 to print something: w1
error: invalid integer input.

Enter 1 to print something: $#%&^#&$ (cat steps on keyboard) !#$%%^%*()
error: invalid integer input.

Enter 1 to print something: 1w
error: additional characters following user_input.

Enter 1 to print something: -1
error: integer not a valid menu selection.

Enter 1 to print something: 24
error: integer not a valid menu selection.

Enter 1 to print something: 1
Enter the amount of cake
3
3

Also note, your menu() funciton will now properly trap a manual EOF generated by the user pressing Ctrl+d (or Ctrl+z on windows) to cancel input and exit gracefully.



来源:https://stackoverflow.com/questions/56140809/c-cin-fail-executes-and-moves-to-next-line-even-though-input-is-of-another-d

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!