I am working on C++, and using a basic authentication method using if statements, so what I have here, is when the the input is not the desired combination, it will say Access denied and ask the user if they want to try again or quit. I tried doing this using the goto variable, but it hasn't been working. Help please? (Full code: https://pastebin.com/49LdGgJX)
else {
cout << "Access denied..." << " Try again? (Y/N) >" << flush;
string ask;
cin >> ask;
if(ask == "N" || "n"){
cout << "Shutting down..." << endl;
goto end;
}
else if(ask == "Y" || "y"){
goto restart;
}
else {
cout << "Invalid option." << endl;
goto restart;
}
}
end:
return 0;
Your if statements are wrong as:
if(ask == "N" || "n")
always evaluates to true because the "n" operand always evaluates to true and you are using a logical OR operator. The string literal of "n" decays to const char* pointer whose value is not 0 thus evaluating to true. What you want is:
if(ask == "N" || ask == "n")
and:
else if(ask == "Y" || ask == "y")
That being said don't use goto.
One of the possible break-ups of that code structure into more procedural way (wouldn't dare to call this "object oriented").
You can use similar way to break up menu handling code into separate functions for each option, etc.
If this would be multi-user app, then you may want to store instead of simple true/false full credentials of the user authenticated, like having a structure containing name, code (password probably can be thrown away after authentication to not keep it in memory long, if you don't need it later).
// returns true if user wants to exit
// sets authenticated to true when the Drew user is detected
bool AuthenticateUser(bool & authenticated) {
cout << "Enter your username >" << flush;
...
if (name == "Drew" && ...) {
authenticated = true;
cout << "Access granted. Welcome, " << name << "." << endl;
cout << "Welcome to Database of Drew" << endl;
return false;
}
cout << "Access denied..." << " Try again? (Y/N) >" << flush;
...
return (ask == "N" || ask == "n"); // N = wants to exit
}
// returns true if user wants to exit
bool ProceedWithMenu() {
cout << "1.\tAdd new record." << endl;
cout << "2.\tDelete record." << endl;
...
if (1 == value) {
...
}
if (5 == value) {
cout << "Application quitting... " << endl;
}
return (5 == value);
}
void mainLoop {
bool authenticated = false;
bool exitApp = false;
do {
if (!authenticated) {
exitApp = AuthenticateUser(authenticated);
} else {
exitApp = ProceedWithMenu();
}
// repeat authentication / menu until user decides to quit app
} while (!exitApp);
}
This example is still quite crude and oversimplified, just trying to illustrate power of do {} while, return, and similar. Often also continue and break can be of great help to control the flow of code execution, without any goto and labels.
Related
I've been learning C++ and obviously before hitting loops, the tuition manual I've been reading provided me with this scenario:
Expand the password checking program from earlier in this chapter and make it take multiple usernames, each with their own password, and ensure that the right username is used for the right password. Provide the ability to prompt users again if the first login attempt failed. Think about how easy (or hard) it is to do this for a lot of usernames and passwords.
Without any knowledge yet of loops and whatnot that'll repeat any incorrect values prompting the user to input the correct information until they do so, I've attempted to complete the task and ended up with this code here:
#include < iostream >
using namespace std;
int main()
{
int inputCodeOne, inputCodeTwo = 0;
bool correctPassOne=false,correctPassTwo;
cout << "Please enter your first Code: ";
cin >> inputCodeOne;
if(inputCodeOne==1111||inputCodeOne==2222||inputCodeOne==3333)
{
correctPassOne = true;
}
if (correctPassOne)
{
cout << "Please enter your second Code: ";
cin >> inputCodeTwo;
if (inputCodeOne == 1111 && inputCodeTwo == 100)
{
cout << "Password Correct! Welcome back David";
return 0;
}
else if (inputCodeOne == 2222 && inputCodeTwo == 200)
{
cout << "Password Correct! Welcome back Darren";
return 0;
}
else if (inputCodeOne == 3333 && inputCodeTwo == 300)
{
cout << "Password Correct! Welcome back Jake";
return 0;
}
correctPassTwo = false;
if(!correctPassTwo)
{
cout << "Please re-enter your second Code: ";
cin >> inputCodeTwo;
if (inputCodeOne == 1111 && inputCodeTwo == 100)
{
cout << "Password Correct! Welcome back David";
return 0;
}
else if (inputCodeOne == 2222 && inputCodeTwo == 200)
{
cout << "Password Correct! Welcome back Darren";
return 0;
}
else if (inputCodeOne == 3333 && inputCodeTwo == 300)
{
cout << "Password Correct! Welcome back Jake";
return 0;
}
cout << "ACCESS DENIED";
return 0;
}
}
else
{
cout << "Please re-enter your first Code: ";
cin >> inputCodeOne;
if (inputCodeOne == 1111 && inputCodeTwo == 100)
{
cout << "Password Correct! Welcome back David";
return 0;
}
else if (inputCodeOne == 2222 && inputCodeTwo == 200)
{
cout << "Password Correct! Welcome back Darren";
return 0;
}
else if (inputCodeOne == 3333 && inputCodeTwo == 300)
{
cout << "Password Correct! Welcome back Jake";
return 0;
}
else
{
cout << "Please enter your second Code: ";
cin >> inputCodeTwo;
if (inputCodeOne == 1111 && inputCodeTwo == 100)
{
cout << "Password Correct! Welcome back David";
return 0;
}
else if (inputCodeOne == 2222 && inputCodeTwo == 200)
{
cout << "Password Correct! Welcome back Darren";
return 0;
}
else if (inputCodeOne == 3333 && inputCodeTwo == 300)
{
cout << "Password Correct! Welcome back Jake";
return 0;
}
correctPassTwo = false;
if (!correctPassTwo)
{
cout << "Please re-enter your second Code: ";
cin >> inputCodeTwo;
if (inputCodeOne == 1111 && inputCodeTwo == 100)
{
cout << "Password Correct! Welcome back David";
return 0;
}
else if (inputCodeOne == 2222 && inputCodeTwo == 200)
{
cout << "Password Correct! Welcome back Darren";
return 0;
}
else if (inputCodeOne == 3333 && inputCodeTwo == 300)
{
cout << "Password Correct! Welcome back Jake";
return 0;
}
else
{
cout << "ACCESS DENIED";
return 0;
}
}
}
}
}
Apologies for how messy the code probably is, but I wanted to know if there were any ways to make this more space efficient whilst providing the same result.
Optimization -- First Pass
Create indentifiers or named constants
This allows you to avoid duplication and making typos. Also allows for compiler to make more optimizations.
const int PASSWORD1a = 1111;
const int PASSWORD2a = 2222;
const int PASSWORD3a = 3333;
const int PASSWORD1b = 100;
const int PASSWORD2b = 200;
const int PASSWORD3b = 300;
Group passwords together.
Keeping the passwords paired together will make the process more generic.
You could use the existing std::pair or create your own:
struct Password_Entry
{
int first;
int second;
};
Next, create a table of valid password pairs:
const Password_Entry valid_passwords[] =
{
{PASSWORD1a, PASSWORD1b},
{PASSWORD2a, PASSWORD2b},
{PASSWORD3a, PASSWORD3b},
};
const size_t quantity_valid_passwords =
sizeof(valid_passwords) / sizeof(valid_passwords[0]);
Search the table for valid passwords
int inputCode1;
int inputCode2;
bool passwords_are_valid = false;
std::cout << "Enter first password: ";
std::cin >> inputCode1;
for (unsigned int index = 0; index < quantity_valid_passwords; ++index)
{
if (inputCode1 == valid_passwords[i].first)
{
std::cout << "Enter second password: ";
std::cin >> inputCode2;
if (inputCode2 == valid_passwords[i].second)
{
passwords_are_valid = true;
}
}
}
Summary
The above code is table driven. The code to search the table is generic and depends on the data in the table. The quantity of entries can be changed without having to modify the remaining code.
Pairing the first password with the second, in a structure, allows for more optimal data storage and code space.
Using named constants allows for the value to only be specified once. If you need to change the value, you only make one change. You don't make the risk of skipping past one or more when making the changes.
Optimization -- Second Pass
Person name
Printing of the person's name can be optimized by adding another field or member to the structure:
struct Password_Entry
{
int first;
int second;
char * name;
};
The table now becomes:
const Password_Entry valid_passwords[] =
{
{PASSWORD1a, PASSWORD1b, "David"},
{PASSWORD2a, PASSWORD2b, "Darren"},
{PASSWORD3a, PASSWORD3b, "Jake"},
};
const size_t quantity_valid_passwords =
sizeof(valid_passwords) / sizeof(valid_passwords[0]);
The search / validation code is changed as:
std::cout << "Enter second password: ";
std::cin >> inputCode2;
if (inputCode2 == valid_passwords[i].second)
{
passwords_are_valid = true;
std::cout << "Password Correct! Welcome Back "
<< valid_passwords[i].name
<< "!\n";
}
Optimization -- Third Pass
Consolidation of duplicate text
There is duplicate text, which means that more space can be squeezed out:
char const * const text_enter[] = "Enter ";
char const * const text_password[] = "password";
The code can be changed as:
std::cout << text_enter << "first " << text_password << ": ";
//...
std::cout << text_enter << "second " << text_password << ": ";
//...
std::cout << "Correct " << text_password << "! Welcome Back "
<< valid_passwords[index].name << "\n";
Block Writing
You may be able to squeeze some code space by block writing instead of using formatted writing. All the output is text, so there is no need to format; the text can be directly output. You'll have to compare assembly language listings of before this optimization and after to measure the space difference.
This technique may also show some speed improvements.
Use the following:
std::cout.write(text_enter, sizeof(text_enter) - 1U); // -1 so the terminating nul is not output.
std::cout.write(text_password, sizeof(text_password) - 1U);
std::cout.write(": ", 2);
Likewise, replace the other std::cout << with std::cout.write like the code above.
Optimization -- Fourth Pass
Don't use std::cout.
The std::cout may carry extra baggage with it. You can save some code space by using an alternative.
Replace std::cout with fwrite(stdout, /*...*/).
The fwrite function contains minimal code to write to the given stream. No extra code to format or convert. Plain and simple, write the data to the stream.
Some compilers may be lazy and insert a "one-size-fits-all" library rather than only the code for fwrite.
You may be able to squeeze out more code space by accessing the low level drivers of your operating system directly. Your program is small enough that it doesn't need buffering and some other overhead from the C++ and OS streams. At this point, the space savings may be negligible to the development time spent achieving this. Depends on your platform. On memory constrained systems, this may be a worthwhile endeavor. On most desktop systems, this is not worth your development time, as they have more memory than the constrained systems.
As Jesper commented, yes there are many ways. I'd encourage you to learn about loops first and then revisit this.
Loops just allow you to do the same thing again, anywhere from no times at all (loop doesn't run) to forever (infinite loop).
Anytime you find yourself typing the same thing again or copy-pasting, there is a very high chance that using a loop (or moving code into a function, a class, etc.) would be a better option.
Also, your code doesn't appear to behave as intended. If you initially type the incorrect input, correctPassOne is false, and you going into the first else and prompted to "re-enter your first code:".
The first set of if else statements will never pass. It's checking if inputCodeTwo is correct but it has never been given by the user at this point.
It seems like you recognized this and put the else statement after that which then prompts for inputCodeTwo.
The problem is now that if inputCodeOne was incorrect the second time, inputCodeTwo doesn't matter. So you'll prompt for inputCodeTwo with no chance of success.
On lines 103, 104 you type
correctPassTwo = false;
if(!correctPassTwo) { }
If 104 is ever reached, 103 will have been reached as well, so the assignment of false and the conditional are unnecessary.
Again if inputCodeOne was incorrect twice, this code block is unnecessary. You're now prompting the user to give the second code twice, with no chance of success occurring. Because inputCodeOne is still wrong.
With loops you could do something like this:
Ask for inputCodeOne as many times as you'd like, until it's correct or until the limit is reached.
If the limit is reached, print "ACCESS DENIED" and end program.
If the password is correct, you can go onto the inputCodeTwo.
Same thing, ask for inputCodeTwo as many times as you'd like.
If the two codes match, print your password correct message.
Otherwise, after a certain number of attempts, you can terminate the program.
You can do that with 25% of the initial code.
Please look at this code, and I will explain:
void GameOver()
{
cout << "\nWelp, you died. Want to try again?" << endl;
cin >> choice;
if (choice == "Yes" || "yes")
{
/*This is where I want the code. I want it to go back to the last
function that the player was on.*/
}
if (choice == "No" || "no")
{
cout << "Are you sure? The game will start over when you open it back up." << endl;
cin >> choice;
if (choice == "No" || "no")
{
cout << "Well, bye now!" << endl;
usleep(1000000);
exit (EXIT_FAILURE);
}
}
return;
}
I would like it so that when I choose "Yes" in the GameOver function, I want an if/else statement that says "if you came from this function, then you will go to that function", you see what I'm saying?
For example, let's say I am in the GameOver function and I came from a FightProcess function. I choose "Yes" then it will go to the Town function.
How would I code that?
First, a statement like this:
if (choice == "Yes" || "yes")
Is coded wrong, and will always evaluate as true. You need to use this instead:
if (choice == "Yes" || choice == "yes")
Or better, use a case-insensitive comparison function, like this:
if (strcmpi(choice.c_str(), "Yes") == 0)
Second, unless you add an input parameter, or use a global variable, GameOver() has no idea who is calling it. So what you want to do does not belong in GameOver() itself to begin with. It belongs in the calling function instead. GameOver() exits the game if the user chooses not to continue. That is all it should do. The calling function should decide how to retry if the game does not exit. For example:
void GameOver()
{
cout << "\nWelp, you died. Want to try again?" << endl;
cin >> choice;
//if (choice == "Yes" || choice == "yes")
if (strcmpi(choice.c_str(), "Yes") == 0)
return;
cout << "Are you sure? The game will start over when you open it back up." << endl;
cin >> choice;
//if (choice == "No" || choice == "no")
if (strcmpi(choice.c_str(), "No") == 0)
return;
cout << "Well, bye now!" << endl;
usleep(1000000);
exit (EXIT_FAILURE);
}
void FightProcess()
{
...
if (defeated)
{
GameOver();
Town();
return;
}
...
}
Or, if Town() is the function that called FightProcess():
void FightProcess()
{
...
if (defeated)
{
GameOver();
return;
}
...
}
void Town()
{
...
FightProcess();
...
}
Or, it might make more sense to have FightProcess() loop instead:
void FightProcess()
{
...
do
{
...
if (won)
break;
GameOver();
...
}
while (true);
...
}
See how things get more flexible when you don't put restrictive logic where it does not belong?
I would recommend using a parameter in the GameOver function. Then you could pass a different parameer each time you want to go somewhere else. For example, call GameOver(1) from function 1 and GameOver(2) from function 2.
This is assuming that returning from GameOver and executing different options in the calling function isn't an option.
Or you can choose to fire a event in FightProcess().
eg:-
void FightProcess(){
...
if( ...){
observer.send("FightProcess"); // or with more information.
//observer.send("FightProcess",Avatar::Killed);
GameOver();
}
}
And in the GameOver() you can query the observer to find what the last event was.
I tried to make a an introduction to a "game", and in its functions I made some Yes/No, 1/2/3, situations.
Im new to this however it wasn't that difficult, worked perfectly. The problem appeared when handling with invalid inputs. So this is what the code looks like by now:
#include "Introduction.h"
#include "GameConstants.h"
#include "PlayerCharacter.h"
#include <iostream>
#include <windows.h>
using namespace std;
Introduction::Introduction()
{
}
/////////Function N.1///////////
void Introduction::presentation()
{
char confirm;
string enteredName;
cout << constants.line() << "Welcome traveler! What is the name?" << endl;
getline(cin,enteredName);// Gets the WHOLE LINE as the name.
while (confirm != 'Y') //If the player doesn't confirm the name with 'Y' in will run again until it does.
{
cout << constants.xline() << "Your name is " << enteredName << " right? (Y/N)" << endl;
cin >> confirm; //The player's answer
cin.sync(); //Only takes the first character
confirm = toupper(confirm); //Turns player message into CAPS for easier detection in the "if" statements
if (confirm == 'N'){ //If not the correct name, gives another chance
cout << constants.xline() << "Please, tell me your name again..." << endl;
cin >> enteredName;
cin.sync();}
if ((confirm != 'Y')&&(confirm != 'N')){ //If an invalid input is entered, gives another chance. And insults you.
cout << constants.xline() << "Fool Go ahead, just enter your name again." << endl;
cin >> enteredName;
cin.sync();}
}
if (confirm == 'Y'){ //When the answer is yes ('Y') /* Uneeded line */
PC.setName(enteredName); //Saves the name
cout << constants.xline() << "Excellent! I have a few more questions for you " << PC.name() << "..." << endl;
}
}
//////////Function N.2///////////
void Introduction::difSelection(){
int selectedDif = 0; //Variable to store selected difficulty whitin this function.
Sleep(2500);
cout << constants.xline() << "What kind of adventure do you want to take part in?" << endl;
Sleep(2500); //Wait 2,5 s
cout << "\n1= Easy\n2= Normal\n3= Hard" << endl;
while(selectedDif != 1&&2&&3){ //Selected option must be 1/2/3 or will run again
cin >> selectedDif; //Sets the user selected difficulty
cin.sync(); //Gets only first character
if((selectedDif != 1||2||3)&&(!(selectedDif))){ //If the input isn't 1/2/3 AND is an invalid character, this will run. And it'll start again
cout << constants.xline() << "Criminal scum. Go again." << endl;
cin.clear();
cin.ignore();
}
if(selectedDif != 1&&2&&3){ //If selected option isn't 1/2/3, this will run and will loop again. However I know this conflicts with the previous statement since this will run anyways.
cout << constants.xline() << "Wrong input, please try again." << endl;
}
else if(selectedDif == 1){
constants.setDiff(1);
constants.setStatPoints(15);
} else if(selectedDif == 2){
constants.setDiff(2);
constants.setStatPoints(10);
} else if (selectedDif == 3){
constants.setDiff(3);
constants.setStatPoints(5);}
}
}
The first function works perfectly you can type "aaa" or "a a a" and will work. However I'd like to know if there's a simpler way to do it. (Understandable for a beginner, just started 3 days ago lol; if it includes some advanced or less known code prefer to stay like this by now).
Now, the second one, I really have no idea how to fix it. I need something that if the user's input was an invalid character type, throw certain message, and if it's an int type, but out of the range, another message. And of course, run again if it fails. Did a lot of search and couldn't find anything that meet this requirements.
To check if the user input is an int, you could use the good() function.
int val;
cin >> val;
if( cin.good() ) {
// user input was a valid int
} else {
// otherwise
}
As for the range check, the syntax is a bit different.
This returns true if the number is not equal to 1 nor 2 nor 3:
selectedDif != 1 && selectedDif != 2 && selectedDif != 3
Another shorter way would be to use:
selectedDif < 1 || selectedDif > 3
Another thing, in c++, there are two keywords break and continue which will allow to reduce the code in the loops.
I am using an if statement to get the users input with a bool value, if they enter 1 then the program continues to execute, if they enter 0 then I want the program to stop running completely. This is the code im using.
bool subscription;
cout << "Would you like to purchase a subscription to our newspaper?\n";
cout << "Enter 1 if yes, and 0 if no. ";
cin >> subscription;
if(subscription == false)
{
cout << "We're sorry you don't want our services.";
//this is where i want the program to stop, after it outputs that line.
}
else if(subscription == true)
{
cout << "\nPlease enter your first and last name. ";
}
I have tried using return 0; after the cout statement, but that didn't work, it would just output the statement and then continue on with the program.
I also tried exit(); and that did the exact same thing.
The problem is that instead of the comparison operator you are using the assignment operator
if(subscription = false)
{
cout << "We're sorry you don't want our services.";
//this is where i want the program to stop, after it outputs that line.
}
else if(subscription = true)
{
cout << "\nPlease enter your first and last name. ";
}
In thsi expression of the if statement
if(subscription = false)
you assigned false to subscription and the expression is also equal to false. As the result the compound statement of this if statement is not executed.
Change the code as
if(subscription == false)
{
cout << "We're sorry you don't want our services.";
//this is where i want the program to stop, after it outputs that line.
}
else if(subscription == true)
{
cout << "\nPlease enter your first and last name. ";
}
It would be even better if you would write
if( subscription )
{
cout << "\nPlease enter your first and last name. ";
}
else
{
cout << "We're sorry you don't want our services.";
// here you can place the return statement
}
#include <iostream>
using namespace std;
int main()
{
bool subscription;
cout << "Would you like to purchase a subscription to our newspaper?"<<endl;
cout << "Enter 1 if yes, and 0 if no. "<<endl;
cin >> subscription;
if(!subscription){
cout << "We're sorry you don't want our services."<<endl;
//this is where i want the program to stop, after it outputs that line.
return -1;
}
else{
cout << "\nPlease enter your first and last name. "<<endl;
return 0;
}
}
A couple of guidelines:
Do not use var = true or var = false (use double == for comparison)
Do not user boolean variables var == true in comparisons with true or false, just use them directly as boolean conditions
Put "<<"endl" when using streams for line breaks better than \n
Use return in main function will return in that place, thus finishing your program.
I'm making a small program that uses a if else statement, but instead of using numbers to control the flow i want to be able to make the control work with with yes and no;
for example:
cout << "would you like to continue?" << endl;
cout << "\nYES or NO" << endl;
int input =0;
cin >> input;
string Yes = "YES";
string No = "NO";
if (input == no)
{
cout << "testone" << endl;
}
if (input == yes)
{
cout << "test two" << endl;
//the rest of the program goes here i guess?
}
else
{
cout << "you entered the wrong thing, start again" << endl;
//maybe some type of loop structure to go back
}
but I can't seem to get any variations of this to work, i could make the user type a 0 or 1 instead but that seems really stupid, i'd rather it be as natural as possible, users don't speak numbers do they?
also i need to be able to simply add more words, for example "no NO No noo no n" all would have to mean no
hopefully that makes some sense
also i would love to make this using a window but i've only learned basic c++ so far not even that and i cant find any good resources online about basic windows programming.
You're not reading in a string, you're reading in an int.
Try this:
string input;
instead of
int input = 0;
Also, C++ is case-sensitive, so you can't define a variable called Yes and then try to use it as yes. They need to be in the same case.
btw, your second if statement should be an else if, otherwise if you type in "NO" then it will still go into that last else block.
First of all, input must be std::string, not int.
Also, you've written yes and no wrong:
v
if (input == No)
// ..
// v
else if (input == Yes)
^^^^
If you want your program to work with "no no no ..", you could use std::string::find:
if( std::string::npos != input.find( "no" ) )
// ..
The same with "Yes".
Also, you could do this to be almost case-insensitive - transform the input to upper-case letters (or lower, whatever ), and then use find.This way, yEs will be still a valid answer.
bool yesno(char const* prompt, bool default_yes=true) {
using namespace std;
if (prompt && cin.tie()) {
*cin.tie() << prompt << (default_yes ? " [Yn] " : " [yN] ");
}
string line;
if (!getline(cin, line)) {
throw std::runtime_error("yesno: unexpected input error");
}
else if (line.size() == 0) {
return default_yes;
}
else {
return line[0] == 'Y' || line[0] == 'y';
}
}
string input;
cin >> input;
if (input == "yes"){
}
else if (input == "no"{
}
else {
//blah
}