问题
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