Safely prompt for yes/no with cin
I'm in an intro to C++ class and I was wondering of a better method of checking if input was the desired type.
Is this a good way of doing this? I come from a PHP/PERL background which makes me rather apprehensive of using while loops.
char type;
while (true) {
cout << "Were you admitted? [y/n]" << endl;
cin >开发者_如何学Go;> type;
if ((type == 'y') || (type == 'n')) {
break;
}
}
Is this a safe way of doing this or am I opening myself up to a world of hurt, which I suspect? What would be a better way of making sure I get the input I want before continuing?
Personally I'd go with:
do
{
cout << "Were you admitted? [y/n]" << endl;
cin >> type;
}
while( !cin.fail() && type!='y' && type!='n' );
Personally I'd make the prompt a separate function, this makes it putting the prompt output and reading a response a logical expression to put in a while loop.
Testing whether the read was successful is critical to the correct functioning of the code.
I'd also prefer to use std::getline
to get a line at a time as it helps reduce errors caused by reading the rest of a half read line that was the result of a partial read to earlier user responses.
bool PromptForChar( const char* prompt, char& readch )
{
std::string tmp;
std::cout << prompt << std::endl;
if (std::getline(std::cin, tmp))
{
// Only accept single character input
if (tmp.length() == 1)
{
readch = tmp[0];
}
else
{
// For most input, char zero is an appropriate sentinel
readch = '\0';
}
return true;
}
return false;
}
void f()
{
char type = '\0';
while( PromptForChar( "Were you admitted? [y/n]", type ) )
{
if (type == 'y' || type == 'n')
{
// Process response
break;
}
}
}
Use can use
do {
program;
} while (condition_to_repeat);
if the algorithm is similar to your example. Otherwise the example is "safe", but I am not sure about the readablity.
Why not do it this way?
do
{
cout << "Were you admitted? [y/n]" << endl;
cin >> type;
}while( type !='y' && type !='n');
That's fine. If you want it to time out after a number of failures, you could use the i<10
but its not worth it.
And don't forget to make your potential user's life easier explaining each step and even provide the case insensitive input.
#include <iostream>
#define MAX_USER_INPUT_ATTEMPTS 3
int _tmain(int argc, _TCHAR* argv[])
{
char input_value = ' ';
int current_attempt = 1;
while(true)
{
std::cout << "Please confirm your choice (press y[es] or n[o] and Enter): ";
std::cin >> input_value;
input_value = tolower( input_value );
if(input_value=='y' || input_value=='n')
{
break;
}
else
{
std::cout << "You have used " << current_attempt << " of " << MAX_USER_INPUT_ATTEMPTS << " attempts" << std::endl;
++current_attempt;
}
if( current_attempt > MAX_USER_INPUT_ATTEMPTS )
{
std::cout << "Warning: Maximum number of attempts reached." << std::endl;
break;
}
}
return 0;
}
Line-based input does not have to be verbose, you can make it succinct, with a single function you write once, that still handles corner cases:
bool yesno_repeat(char const* prompt) {
using namespace std;
while (true) {
cout << prompt << " [yn] ";
string line;
if (!getline(cin, line)) {
throw std::runtime_error("unexpected input error");
}
else if (line.size() == 1 and line.find_first_of("YyNn") != line.npos) {
return line == "Y" || line == "y";
}
}
}
int main() try {
if (yesno_repeat("Blow up?")) {
take_off_every<Zig>(); // in the future, a zig is a nuclear missile...
}
return 0;
}
catch (std::exception& e) {
std::cerr << e.what() << '\n';
return 1;
}
Here is a shorter way
char type;
while (type != 'y')
{
cout << "Were you admitted? [y/n]" << endl;
cin >> type;
}
I've never coded in PERL/PHP before but based off of your question and the example here's a simple solution.
char c;
while(true){
cout << "Were you admitted? [y/n]" << endl;
cin >> c;
if(c == 'y')
break;
}
cin.get(c);
return 0;
You will continue to be prompted until you enter a 'y' after entering a 'y' this simple program will exit.
The do...while construct is sort of made for this, but you can also do all the work in the loop condition:
while (std::cout << "Were you admitted [y/n]\n" && std::cin >> answer && !(answer == 'y' || answer == 'n'));
:)
And if you don't want to test the success of std::cin >> answer
(e.g want to loop infinitely with Ctrl+Z), you can replace &&
for comma :)
Not entirely serious, even though methinks putting the prompt in the condition can sometimes enhance the logic of such loops (avoid break).
I see two "problems" here. First one is the use of while( true ). I don't think that's a good practice (though probably this is a matter of taste for many people). Breaking inside a loop is however interesting for searching:
for(i = 0; i < MAX; ++i) {
if ( v[ i ] == searchedElement ) {
break;
}
}
This is self-explanatory: you run until the end of the vector, though if the element is found, you break before reaching it. It is justifiable.
About getting information from the console, you can run into a lot of trouble reading directly from cin, for example, being returned the contents only until the first space if the input has one. getline() is an utility function that reads to a string, which is (nearly) always safe. getline() is defined in the utility header. You do also need the string header. And cstdio if you want to use EOF.
int main()
{
int ch;
std::string type;
do {
getline( std::cin, type );
if ( cin.fail() ) {
ch = EOF;
break;
}
ch = tolower( type[ 0 ] );
} while( ch != 'y' && ch != 'n' );
// interesting things here...
return 0;
}
Modifying what @McAden said, try this fix the bug where if you enter multiple characters, the test only checks the first letter.
char type;
char buffer[128];
do
{
cout << "Were you admitted? [y/n]" << endl;
cin >> buffer;
type = buffer[0];
cout << type << "\n";
}while( !cin.fail() && type!='y' && type!='n' );
Another way to prompt for y/n . "Do" this "while" you don't get what you want. Do whatever else you like with it; it just works.
#include "stdafx.h"
#include <iostream>
int main()
{
bool accepted;
char answer;
do
{ // Ask for 'y' or 'n' at least once
std::cout << "Accepted? [y/n] -> ";
std::cin >> answer;
if (std::cin.fail())
{ // not a valid character?
std::cin.clear();
std::cin.ignore(1024, '\n');
}
if (answer == 'n')
{ // character is 'n'?
accepted = false;
}
else
{ // character is 'y'?
accepted = true;
}
// not valid |or| not 'n' |or| not 'y'
} while (std::cin.fail() || !(answer == 'n') || !(answer == 'y'));
}
This also comes with no adverse after effects!
Here's another way to prompt for y/n. "While" you don't get what you want, "switch" on your input. Do whatever else you like with it; it just works.
#include "stdafx.h"
#include <iostream>
int main()
{
bool play = true; // Initialize play to true.
char choice;
while (play) // While play is true, do the following:
{
std::cout << "Play again? [y/n] -> ";
std::cin >> choice;
switch (choice)
{
case 'n': // We can fall through here because we
play = false; // don't do anything with 'y' anyway.
case 'y': // Remember; play is already true unless
break; // we changed it to false with 'n'.
default: // We'll simply assume anything else is a fail!
std::cin.clear();
std::cin.ignore(1024, '\n');
break;
}
// Break out here and check the "while" condition again...
}
// When play becomes false, we exit the while statement.
}
This comes with no adverse after effects!
char yn;
bool choice;
do
{
cout<<"Would you like try again?\n";
cout<<"Y for yes N for no: ";
cin>>yn;
while (yn != 'n'&& yn != 'N'&&yn != 'y'&&yn != 'Y')
{
cout<<"Y or N please: ";
cin>>yn;
}
if (yn == 'n'||yn == 'N')
choice = false;
if(yn == 'y'||yn == 'Y')
choice = true;
} while (choice == true);
精彩评论