C++ user input restriction with proper retry without “goto”

前端 未结 5 687
Happy的楠姐
Happy的楠姐 2021-01-15 14:01

I have the following code:

qstn:
  cout << \"Input customer\'s lastname: \";
  getline(cin, lname);

  if (lname.find_first_not_of(\"abcdefghijklmnopqr         


        
相关标签:
5条回答
  • 2021-01-15 14:14

    It seems to me that your code suffers from lack of clarity of purpose.

    You apparently don't want the string that's entered to include a leading space, nor multiple consecutive spaces. Other than that, only alphabetic characters should be accepted.

    If the user does enter multiple consecutive spaces, I'd probably just ignore all but the first. I'd probably write the code something like this:

    #include <string>
    #include <iostream>
    #include <algorithm>
    #include <cctype>
    #include <sstream>
    
    bool non_alpha(std::string const &s) {
        return !std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isalpha(c) || std::isspace(c); });
    }
    
    std::string get_name(std::string const &prompt) {
        std::string result;
        std::string line;
    
        do {
            std::cout << prompt;
            std::getline(std::cin, line);
        } while (non_alpha(line));
    
        std::istringstream words(line);
        std::string word;
        while (words >> word)
            result += word + ' ';
        return result;
    }
    
    int main() {
        auto res = get_name("Please enter last name, alpha-only\n");
        if (res.empty())
            std::cout << "Oh well, maybe some other time";
        else
            std::cout << "Thanks Mr(s). " << res << "\n";
    }
    

    I'd be tempted to consider doing roughly the same for non-alphabetic characters--rather than asking the user to re-enter from the beginning, assume it's a mistake and just ignore it.

    0 讨论(0)
  • 2021-01-15 14:18

    A While loop looks like your best bet. You can redo the loop with the continue keyword.

    int incorrect = 0;
    while(!incorrect) {
    cout<<"Input customer's lastname: ";
            getline(cin,lname);
    
            if(lname.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ")!=string::npos)
                {
                cout<<"You can only input alpha here!\n";
                cin.clear();
                continue;
                }
            else if(lname.empty())
            {
                cout<<"Please enter your firstname!\n";
                cin.clear();
                continue;
            }
            int lnamel=lname.length();
            int strl=str.length();
            int is=0;
            for(int i=1; i<strl;)
            {
               i++;
               is++;
               if(lname[i]==lname[is]&&lname[i]==' '||lname[0]==' ')
               {
                   cin.clear();
                   cout<<"Please input your lastname properly!\n";
                   continue;
               }
               incorrect = 1;
            }
    
    0 讨论(0)
  • 2021-01-15 14:26

    Here's an idiom I like to use:

    int i;
    
    if (std::cin >> prompt("enter an integer: ", i))
    {
        std::cout << "Read user input: " << i << "\n";
    } else {
        std::cout << "Input failed (too many attempts). Eof? " << std::boolalpha << std::cin.eof() << "\n";
    }
    

    Here, prompt is a smart input manipulator, that takes care of handling parse errors or stream failures and retrying.

    It's quite generic so actually do many things, but you don't need to indicate all the options. When the manipulator is inserted into the stream it relays to the do_manip member:

    template <typename Char, typename CharT>
    friend std::basic_istream<Char, CharT>& operator>>(std::basic_istream<Char, CharT>& is, checked_input<T, Prompter>& manip) {
        return manip.do_manip(is);
    }
    

    The do_manip handles all the logic without any gotos :) :

    std::istream& do_manip(std::istream& is) {
        auto attempt = [this] { return infinite() || retries_ > 0; };
    
        while (attempt()) {
            if (!infinite())
                retries_ -= 1;
    
            prompter_(out_);
    
            if (is >> value_) {
                if (!run_validators(out_))
                    is.setstate(is.rdstate() | std::ios::failbit);
                else
                    break;
            } else {
                out_.get() << format_error_ << "\n";
            }
    
            if (attempt()) {
                is.clear();
                if (flush_on_error_)
                    is.ignore(1024, '\n');
            }
        }
    
        return is;
    }
    

    You can see that there is a possibility to have validations run before accepting the input.

    Here's a somewhat full-blown demo:

    Live On Coliru

    int main() {
        using namespace inputmagic;
    
        int i;
    
        if (std::cin >> prompt("enter an integer: ", i)
                .retries(3)
                .flush_on_error(false)
                .format_error("I couldn't read that (Numbers look like 123)")
                .output(std::cerr)
                .validate([](int v) { return v > 3 && v < 88; }, "value not in range (3,88)")
                .validate([](int v) { return 0 == v % 2; })
                .validate([](int v) { return v != 42; }, "The Answer Is Forbidden")
                .multiple_diagnostics())
        {
            std::cout << "Read user input: " << i << "\n";
        } else {
            std::cout << "Input failed (too many attempts). Eof? " << std::boolalpha << std::cin.eof() << "\n";
        }
    }
    

    You can see it will only accept valid integers

    • that are >3 and <88,
    • that are even
    • except 42 (forbidden number)

    When entering the numbers 21, 42 and 10 on subsequent retries, you get: live

    enter an integer: 21
    Value not valid
    enter an integer: 42
    The Answer Is Forbidden
    enter an integer: 10
    Read user input: 10
    

    However, if you enter 1 all the time you get this: live

    enter an integer: 1
    value not in range (3,88)
    Value not valid
    enter an integer: 1
    value not in range (3,88)
    Value not valid
    enter an integer: 1
    value not in range (3,88)
    Value not valid
    Input failed (too many attempts). Eof? false
    

    Or if you read from a single line file: live

    enter an integer: value not in range (3,88)
    Value not valid
    enter an integer: I couldn't read that (Numbers look like 123)
    enter an integer: I couldn't read that (Numbers look like 123)
    Input failed (too many attempts). Eof? true
    
    0 讨论(0)
  • 2021-01-15 14:30

    Use a function:

    bool getLastName(string & lname,
                     string & str)
    {
        cout << "Input customer's lastname: ";
        getline(cin, lname);
    
        if (lname.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ")
                != string::npos)
        {
            cout << "You can only input alpha here!\n";
            cin.clear();
            return false;
        }
        else if (lname.empty())
        {
            cout << "Please enter your firstname!\n";
            cin.clear();
            return false;
        }
        int lnamel = lname.length();
        int strl = str.length();
        int is = 0;
        for (int i = 1; i < strl;)
        {
            i++;
            is++;
            if (lname[i] == lname[is] && lname[i] == ' ' || lname[0] == ' ')
            {
                cin.clear();
                cout << "Please input your lastname properly!\n";
                return false;
            }
        }
        return true;
    }
    

    All I've done here is replace the gotos with return false. If the program makes it to the end of the function, return true. Make the function call in a while loop:

    while (!getLastName(lname, str))
    {
        // do nothing
    }
    

    Not only does this de-spaghettify the code, but it breaks it up into nice, small, easy to manage pieces. This is called procedural programming.

    0 讨论(0)
  • 2021-01-15 14:30

    You need to use some do while loops for instance this:

    qstn:
                cout<<"Input customer's lastname: ";
                getline(cin,lname);
    
                if(lname.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ")!=string::npos)
                    {
                    cout<<"You can only input alpha here!\n";
                    cin.clear();
                    goto qstn;
                    }
                else if(lname.empty())
                {
                    cout<<"Please enter your firstname!\n";
                    cin.clear();
                    goto qstn;
                }
    

    could be re-written as this:

    int flag;
    do{
        flag = 1;
        cout<<"Input customer's lastname: ";
        getline(cin,lname);
        if(lname.find_first_not_of( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ")!=string::npos)
        {
            flag = 0;
            cout<<"You can only input alpha here!\n";
        }
        else if(lname.empty())
        {
            flag = 0;
            cout<<"Please enter your firstname!\n";
        }
        cin.clear();
    } while( flag !=1 );
    

    feel free to use a boolean type flag, it doesn't really matter

    0 讨论(0)
提交回复
热议问题