Trying to learn about input validation loops

↘锁芯ラ 提交于 2020-03-03 14:03:25

问题


In this input validation while loop, cerr is being displayed after my console output is called. It displays as "Enter rate: $Invalid Rate." For example:

Enter rate: $750
Enter rate: $Invalid Rate. 

It does the same with the do-while loop. Any advice or help?

#include <iostream>

using namespace std;
#define max_rate 50
#define max_hours 80

int main() {

   // input validation loop (loops until input is valid)
   double rate, hours;

   cout << "enter rate: $";
   cin >> rate;
   cin.ignore();

   while (rate > max_rate){
       if (rate > max_rate){
           cerr << "Invalid Rate." << endl;
       }
       cout << "enter rate: $";
       cin >> rate;
   }

   do {
       if (hours > max_hours) {
           cerr << "Invalid hours." << endl;
       }
       cout << "enter hours: ";
       cin >> hours;

       }
   while (hours > max_hours);


   double pay = rate * hours;
   cout << "$" << pay << endl;
   return 0;

回答1:


The answer is, read first, check second, claim third. That is,

for (;;) { // the same as while(true)
    cin >> rate;
    if (rate is valid)
        break;
    cout << "Invalid rate\n";
}

And don’t mix cout and cerr without a reason. cout delays output, accumulating data in an internal buffer, while cerr prints immediately. (but cout flushes the buffer on cin use; it is also possible to call cout.flush [or cout << flush] explicitly).




回答2:


Validating user-input is all about checking the stream error state after each input. The stream error state is composed of a bitmask four primary states goodbit (no error), badbit (unrecoverable error), failbit (bad conversion/extraction), and eofbit (end-of-file). Each stream state may be checked with the corresponding member function .good(), .bad(), .fail() and .eof() or though reading and checking the std::basic_ios::rdstate directly.

Both .eof() and .bad() are unrecoverable, .fail() requires that you remove any offending characters causing the failure from the input stream before attempting your next input (or the same input causing the failure will be read again), and then clearing the stream error state with the .clear() member function.

When taking user-input, a robust approach will loop continually requiring the user to provide the correct input issuing sufficient diagnostics to inform the user of what went wrong and handling clearing any failbit from the stream state as well as any offending or extraneous characters from the input stream before the next input. The approach is fairly simple, just enter a continuous loop, prompt, check the stream state, exit on unrecoverable error, .clear() the stream error on .fail(), validate the input is within the needed range and break the loop if all criteria are satisfied, and finally removing all characters to the end of line before beginning the next input iteration.

To read you rate, than can be put together similar to the following:

    for (;;) {          /* loop continually until valid input is received */
        std::cout << "\nenter rate: $ " << std::flush;  /* prompt/flush */

        if ( !(std::cin >> rate) ) {
            /* if eof() or bad() (unrecoverable) break read loop */
            if (std::cin.eof() || std::cin.bad()) {
                std::cerr << "  user canceled or unrecoverable stream error.\n";
                return 1;
            }
            else if (std::cin.fail()) {     /* if failbit */
                std::cerr << "  error: invalid double input.\n";
                std::cin.clear();           /* clear failbit */
            }
        }
        else if (rate > max_rate)   /* validate against max_rate */
            std::cerr << "  rate entered exceeds max_rate (" << max_rate << ")\n";
        else    /* all checks passed, good input */
            break;
        /* empty any extraneous character to end-of-line */
        std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
    }

The exact same thing can be done for hours. You can write a function that handles the prompting and validating to cut down on some of the repetition in the body of your code, but simply including the validation loops for both rate and hours is fine. The content and validation is the important part.

Adding a short main(), the complete example could be:

#include <iostream>
#include <limits>

#define max_rate 50     /* good -- if you need a constant, #define one (or more) */
#define max_hours 80

int main (void) {

    double rate, hours;

    for (;;) {          /* loop continually until valid input is received */
        std::cout << "\nenter rate: $ " << std::flush;  /* prompt/flush */

        if ( !(std::cin >> rate) ) {
            /* if eof() or bad() (unrecoverable) break read loop */
            if (std::cin.eof() || std::cin.bad()) {
                std::cerr << "  user canceled or unrecoverable stream error.\n";
                return 1;
            }
            else if (std::cin.fail()) {     /* if failbit */
                std::cerr << "  error: invalid double input.\n";
                std::cin.clear();           /* clear failbit */
            }
        }
        else if (rate > max_rate)   /* validate against max_rate */
            std::cerr << "  rate entered exceeds max_rate (" << max_rate << ")\n";
        else    /* all checks passed, good input */
            break;
        /* empty any extraneous character to end-of-line */
        std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
    }

    for (;;) {          /* loop continually until valid input is received */
        std::cout << "\nenter hours: " << std::flush;   /* prompt/flush */

        if ( !(std::cin >> hours) ) {
            /* if eof() or bad() (unrecoverable) break read loop */
            if (std::cin.eof() || std::cin.bad()) {
                std::cerr << "  user canceled or unrecoverable stream error.\n";
                return 1;
            }
            else if (std::cin.fail()) {     /* if failbit */
                std::cerr << "  error: invalid double input.\n";
                std::cin.clear();           /* clear failbit */
            }
        }
        else if (hours > max_hours) /* validate against max_hours */
            std::cerr << "  hours entered exceeds max_hours (" << max_hours << ")\n";
        else    /* all checks passed, good input */
            break;
        /* empty any extraneous character to end-of-line */
        std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
    }

    double pay = rate * hours;
    std::cout << "\n$" << pay << '\n';
}

Example Use/Output

Once you have written an input routine -- go try and break it. Intentionally input bad input and additional extraneous input after good values to make sure you are handling all aspects of the input. A minimal run-though could be:

$ ./bin/validate_rate_hours

enter rate: $ 56 dollars
  rate entered exceeds max_rate (50)

enter rate: $ I don't care I really want $50!!
  error: invalid double input.

enter rate: $ 50

enter hours: 120 hours and 59 minutes and 59 seconds
  hours entered exceeds max_hours (80)

enter hours: I really want 120:59:59!!
  error: invalid double input.

enter hours: 80

$4000

Understanding and checking the C++ stream error states is the key to writing robust input routines. Look things over and let me know if you have questions.



来源:https://stackoverflow.com/questions/60406754/trying-to-learn-about-input-validation-loops

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