Each program written this far has followed sequential instructions that execute one after another. Control structures break up this sequential code, allowing for code to run conditionally or repeatedly. A control structure is a programming construct that determines the flow of execution of instructions in a program. Selection is a type of control structure that allows a program to choose between different paths of execution based on whether a condition is true
or false
. To make selections computers use boolean expressions with selection control structures to conditionally run code.
There are 5 types of selection in C++: one-way selection, two-way selection, multiple selection (also called n-way selection), switch statements, and ternary statements.
One-way selection is the simplest form of selection and is a single block of code that executes only if a condition is true
. In general, one-way selection takes the form:
if (booleanExpression) { // code to run when booleanExpression is true } // code to run after the selection (in both true/false cases)
When the booleanExpression
is true
it causes the block of code that follows to be run. Otherwise the block is skipped over. In both cases the code eventually meets up back in the same spot just after the block of code, unless the block of code has instructions to not run the code after the block (which would cause the true
case to end somewhere else -- more on this as examples develop).
For example, the following program outputs “even number”
when an even number is entered, then regardless of if the value is even or not outputs how many times that number is divisible by 2:
#include <iostream> using namespace std; int main() { // variables int num = 0; // get num from user cout << "Enter a number: "; cin >> num; // output if num is even if (num % 2 == 0) { cout << "even number\n"; } // output how many times 2 goes into num cout << "2 goes into " << num << " " << num / 2.0 << " times.\n"; return 0; }
When a number is mod by 2 the only results possible are 0 when the number is even (no remainder when 2 can divide perfectly) and 1 when the number is odd (remainder when 2 can’t divide perfectly). So when the value received from num % 2
is 0 then the value is even and the even number message is shown, otherwise the block of code is skipped over. In both cases the program continues on after the selection to output how many times 2 goes into the value entered.
Consider the following program:
#include <iostream> using namespace std; int main() { // variables int num = 0; // get num from user cout << "Enter a number: "; cin >> num; // output if num is even cout << “You entered: “ << num << endl; return 0; }
Validate the value entered to
num
is a valid integer.
If this was a string input, anything the user inputs would be able to be held since strings hold 0 or more characters (which all keyboard values are). For numerical inputs this is not the case. Numerical inputs can only hold numbers, but what if a user enters a value such as ”error”
? It turns out that the program just fails to place the input into the num
variable and leaves it in the input stream. The program then continues with its execution:
alex ~ % ./a.out Enter a number: Hello You entered: 0
The program receives the value ”Hello”
into the input stream:
// SHOW STRING IN INPUT STREAM
But when the program tries to place the value into the num
variable it fails. Since the value can not be placed into the variable it is left in the input stream:
// SHOW STRING FAILED TO GO IN VARIABLE AND STUCK IN INPUT STREAM
This is called input failure, or when the program tries to read input but the input does not match the expected type or format. The cin
statement can be checked for input failure using:
cin.fail()
Which can be checked following a single input:
// get num from user int num = 0; cout << "Enter a number: "; cin >> num; // output if input failed cout << cin.fail() << endl; }
This will cause 0
(false
) to be output if a number was entered (even if it was a floating-point as implicit typecasting gets rid of the decimal), or 1
(true
) to be output if a string were to be entered. This can be incorporated into the program by checking for input failure after num
is entered in a one-way selection. If input failure happens on the num
then the program can output that an error occurred and quit the program:
#include <iostream> using namespace std; int main() { // variables int num = 0; // get num from user cout << "Enter a number: "; cin >> num; // check for input failure if (cin.fail()) { cout << “invalid number\n”; return 0; } // output if num is even cout << “You entered: “ << num << endl; return 0; }
If input failure occurs then the one-way selection is true
causing the block to code. The program will output invalid number\n
and terminate the program early with the return 0
. Since the block of code terminates the program at the end there is no issue that it may output the entered result. If there were no return 0
at the end of the block this would be an issue that would have to be addressed with further selections.
If on the other hand there is no input failure the one-way selection will be false
, causing the block of code to be skipped over and the program to not be terminated early. Now the program will output You entered:
followed by the value entered, then terminate the program at the end of the code.
The reason why failed input terminates the program early rather than recover from the failed input is because repetition would be needed. Repetition has not been gone over yet and will be gone over later. At that time the return 0
will be removed and more code added to recover from the failed input.
Consider the following program:
#include <iostream> using namespace std; int main() { // variables int num1 = 0, num2 = 0; // get num1 from user cout << "Enter a number: "; cin >> num1; // get num2 from user cout << "Enter another number: "; cin >> num2; // check for input failure if (cin.fail()) { cout << “invalid number\n”; return 0; } // output the 2 numbers added together cout << num1 << " + " << num2 << " = " << num1 + num2 << endl; return 0; }
If input failure happens on the second input (num2
) the check will succeed:
alex ~ % ./a.out Enter a number: 20 Enter another number: error invalid number
But if the failure happens on the first input (`num1) the check will fail:
alex ~ % ./a.out Enter a number: error Enter another number: invalid number
The second input does not allow the user to enter anything and the invalid number
message is on the same line as the prompt for the second input. The input is skipped because the first input failed to place the error
value into the num1
variable, so error
was left in the input stream. When the second input statement is hit it does not need to get input from the user since it already has error
in the input stream. So the program tries to put error
into num2
, fails, then outputsinvalid number
. The failure message is on the same line as the prompt because there was no user to hit enter
on their keyboard after the input to put the newline there.
To fix this behavior, every numerical input should have input failure checking directly after the input:
#include <iostream> using namespace std; int main() { // variables int num1 = 0, num2 = 0; // get num1 from user cout << "Enter a number: "; cin >> num1; // check for input failure on the previous input if (cin.fail()) { cout << “invalid number\n”; return 0; } // get num2 from user cout << "Enter another number: "; cin >> num2; // check for input failure on the previous input if (cin.fail()) { cout << “invalid number\n”; return 0; } // output the 2 numbers added together cout << num1 << " + " << num2 << " = " << num1 + num2 << endl; return 0; }
Now, if input failure occurs on the second input it will work properly as it did previously. But, if input failure occurs on the first input it will output the error message and terminate the program immediately after:
alex ~ % ./a.out Enter a number: error invalid number
Two-way selection is a control structure that chooses between two possible paths of execution, depending on whether a condition is true
or false
. In general, two-way selection takes the form:
if (booleanExpression) { // code to run when booleanExpression is true } else { // code to run when booleanExpression is false }
Like one-way selection, when the booleanExpression
is true
it causes the first block of code following the if
to be run. Otherwise, when the booleanExpression
is false
the first block of code is skipped over and the second block of code following the else
is run.
For example, the following program outputs “even number”
when an even number is entered, and ”odd number”
when an odd number is entered. The, regardless of if the value is even or not outputs how many times that number is divisible by 2:
#include <iostream> using namespace std; int main() { // variables int num = 0; // get num from user cout << "Enter a number: "; cin >> num; // output if num is even if (num % 2 == 0) { cout << "even number\n"; } else { cout << "odd number\n"; } // output how many times 2 goes into num cout << "2 goes into " << num << " " << num / 2.0 << " times.\n"; return 0; }
Like the previous example in the one-way selection section, if num % 2
is 0 this means the value is even as 2 can divide any even number perfectly. Otherwise, the only other result is 1 which means the value is odd.
So when num % 2 == 0
in the if
is true
this means the number is even, and the true
triggers the code after the if
to run which outputs "even number\n"
. Otherwise, the expression is false
which causes the first block of code to be skipped over and the second block of code after the else
to be run, which outputs "odd number\n"
.
To slightly clean up this check the == 0
can be removed and blocks of code swapped:
#include <iostream> using namespace std; int main() { // variables int num = 0; // get num from user cout << "Enter a number: "; cin >> num; // output if num is even if (num % 2) { cout << "odd number\n"; } else { cout << "even number\n"; } // output how many times 2 goes into num cout << "2 goes into " << num << " " << num / 2.0 << " times.\n"; return 0; }
Remember, 1 is true
and 0 is false
. And num % 2
can only return two values, 0 when the value is even and 1 when the value is odd. So the even case being 0 correlates to false
and the odd case being 1 correlates to true
. So when the result of num % 2
is 1 (true
/odd) the value can be used as the boolean value passed to the if
statement as the 1 is just implicitly typecast to true
, causing the first block of code to be run which outputs cout << "odd number\n"
. Otherwise, the result will be 0 (false
/even) causing the second block of code to be run which outputs cout << "even number\n"
.
Write a program that reads in a username and password, checks if the username is
”Ronald”
and the password is”McDonald”
, and if the credentials are correct outputs”logged in”
otherwise outputs”invalid credentials”
.
First, the username and password must be read into string variables:
#include <iostream> using namespace std; int main() { // variables string username = “”, password = “”; // get username cout << “Username: “; getline(cin, username); // get password cout << “Password: “; getline(cin, password); return 0; }
The getline()
instruction is used as the username
and password
may have spacing in them, and if cin >>
was used that spacing or anything after it would not be read in. No input failure needs to be added after each input as strings are being used for the variable types so anything from the keyboard can be held in a string. Once the username
and password
are read in they can be checked for validity:
#include <iostream> using namespace std; int main() { // variables/constants string username = “”, password = “”; const string REQ_USERNAME = “Ronald”, REQ_PASSWORD = “McDonald”; // get username cout << “Username: “; getline(cin, username); // get password cout << “Password: “; getline(cin, password); // validate credentials if (username == REQ_USERNAME && password == REQ_PASSWORD) { cout << “logged in\n”; } else { cout << “invalid credentials\n”; } return 0; }
Constants for REQ_USERNAME
and REQ_PASSWORD
are added and checked against the variables holding the entered username
and password
. Both the username
must be equivalent to the REQ_USERNAME
and password
equivalent to the REQ_PASSWORD
at the same time for the credentials to be valid, thus the logical AND is used as both sides of the logical AND must be true
for the first block of code after the if
to run. If the credentials are valid “logged in\n”
is output from the if
case, otherwise the credentials are invalid and “invalid credentials\n”
is output from the else
case.
To the Logging In example program add that the
username
must have between 3 and 12 characters (inclusive) and thepassword
between 6 and 18 characters.
Since the username
and password
are both strings their length can be checked using:
string.length()
Where string
is any string variable. Thus, after each input is received, its length can be checked against the required ranges and an error output if either is out of range:
#include <iostream> using namespace std; int main() { // variables/constants string username = “”, password = “”; const string REQ_USERNAME = “Ronald”, REQ_PASSWORD = “McDonald”; // get username cout << “Username: “; getline(cin, username); // make sure username is valid length if (username.length() < 3 || username.length() > 12) { cout << “invalid username (must be: 3 <= length <= 12)\n”; return 0; } // get password cout << “Password: “; getline(cin, password); // make sure password is valid length if (password.length() < 6 || password.length() > 18) { cout << “invalid password (must be: 6 <= length <= 18)\n”; return 0; } // validate credentials if (username == REQ_USERNAME && password == REQ_PASSWORD) { cout << “logged in\n”; } else { cout << “invalid credentials\n”; } return 0; }
After the username
is read it is checked if it is either too small or too large, and if either case is true
it triggers the block of code that follows to run outputting “invalid password (must be: 6 <= length <= 18)\n”
and terminating the program. Otherwise if both checks are false
then the block of code is skipped and the program moves on to reading in the password
. The password
is then validated using the same logic as the username
.
Often a selection may have several cases, such as in video games when multiple spells are selected through. In these instances n-way (multiple) selection--a control structure that allows a program to choose between more than 2 possible execution paths--can be employed. Multiple selection allows any amount of instruction sets to be blocked off with an expression that must be true
in order to run. In general multiple selection takes the form:
if (booleanExpression1) { // code to run when booleanExpression1 is true } else if (booleanExpression2) { // code to run when booleanExpression2 is true } ... // as many else if’s as needed else { statements; }
Just like one-way and two-way selection, when booleanExpression1
is true
the first block of code runs and all the else if
and else
in the selection are skipped over, otherwise it moves on to the else
. This time the else
has a if
attached to it. The if
just allows a condition to be checked, so when attached to an else it just means to run that else case the condition attached must be true
. So booleanExpression2
is tested and if is true
then the second block of code is run, otherwise the selections can stop here or another else
can be attached. That else
can or can not have an else depending on the selection, and this can go on for as many cases as the selection needs.
For example, to select what recipe to show, the user would first be asked for the name of a recipe, then as many cases as recipes can be added as needed using else if
:
#include <iostream> using namespace std; int main() { // variables string recipe = “”; // get username cout << “What do you want to cook? “; getline(cin, recipe); // mom’s spaghetti if (recipe == “spaghetti”) { cout << “The recipe for mom’s spaghetti is...\n"; } // pizza else if (recipe == “pizza”) { cout << “The recipe for pizza is...\n"; } // invalid recipe else { cout << “Invalid recipe\n"; } return 0; }
When the user enters spaghetti
then recipe == “spaghetti”
is true
causing the first block of code to run which outputs “The recipe for mom’s spaghetti is...\n"
, skipping the next 2 blocks of code. If the user enters pizza
then the first check is false
and recipe == “pizza”
is true
causing the second block of code to run which outputs “The recipe for pizza is...\n"
, skipping the first and final blocks of code. If the user enters anything else then both of the checks will be false, which causes the first two blocks of code to be skipped and the final else
case which outputs “Invalid recipe\n"
to be run by default.
To the String Bounds example add outputting separate too small/large messages for the lengths of the username/ password.
Recall the checks currently are in the form:
if (length < lowBound || length > upBound) { cout << “invalid”; return 0; }
Currently if either side of the logical OR is true
(too small or too big) the the statement is true
and the error message is output. To output separate messages the logical OR needs to be split into two cases that output different messages depending if the case is true:
if (length < lowBound) { cout << “too small”; return 0; } else if (length > upBound) { cout << “too large”; return 0; }
This logic can be applied to the program from the String Bounds example for each input that is being error checked:
#include <iostream> using namespace std; int main() { // variables/constants string username = “”, password = “”; const string REQ_USERNAME = “Ronald”, REQ_PASSWORD = “McDonald”; // get username cout << “Username: “; getline(cin, username); // make sure username is big enough if (username.length() < 3) { cout << “invalid username (must be: length >= 3)\n”; return 0; } // make sure username is small enough else if (username.length() > 12) { cout << “invalid username (must be: length <= 12)\n”; return 0; } // get password cout << “Password: “; getline(cin, password); // make sure password is big enough if (password.length() < 6) { cout << “invalid password (must be: length >= 6)\n”; return 0; } // make sure password is small enough else if (password.length() > 18) { cout << “invalid password (must be: length <= 18)\n”; return 0; } // validate credentials if (username == REQ_USERNAME && password == REQ_PASSWORD) { cout << “logged in\n”; } else { cout << “invalid credentials\n”; } return 0; }
Now if the username is too small then username.length() < 3
will be true
which will cause its block of code to be run which outputs “invalid username (must be: length >= 3)\n”
and terminates the program. Otherwise the first block of code is skipped over and the else is run, which has a condition to run. If the username is too large then username.length() > 12
will be true
which will cause its block of code to be run which outputs “invalid username (must be: length <= 12)\n”
and terminates the program. If the lengths are valid, then the program moves on to the password, which has error checking that follows the same logic with different bounds and error messages.
However, this is not the best use case of multiple selection as the program is being terminated inside of each block of code. Thus, the program gets terminated before it ever has the chance to move on to skipping the else if
so the else
is not necessary:
#include <iostream> using namespace std; int main() { // variables/constants string username = “”, password = “”; const string REQ_USERNAME = “Ronald”, REQ_PASSWORD = “McDonald”; // get username cout << “Username: “; getline(cin, username); // make sure username is big enough if (username.length() < 3) { cout << “invalid username (must be: length >= 3)\n”; return 0; } // make sure username is small enough if (username.length() > 12) { cout << “invalid username (must be: length <= 12)\n”; return 0; } // get password cout << “Password: “; getline(cin, password); // make sure password is big enough if (password.length() < 6) { cout << “invalid password (must be: length >= 6)\n”; return 0; } // make sure password is small enough if (password.length() > 18) { cout << “invalid password (must be: length <= 18)\n”; return 0; } // validate credentials if (username == REQ_USERNAME && password == REQ_PASSWORD) { cout << “logged in\n”; } else { cout << “invalid credentials\n”; } return 0; }
When there is error for too small of a value in either the username
or password
inputs then the program is terminated, there is no worry for the program continuing on to further code it should not. If there is no error for too small of a value the block of code is automatically skipped over and the next independent selection is done to see if an error/termination needs to happen for the value being too big. Otherwise if there are no errors in the input then the program has valid values and can continue on with execution.
A better use of multiple selection would be having the user make a selection between multiple options.
Write a program that reads in the user’s magic level (integer between 3 and 99), then asks for a spell to cast between fire bolt, fire blast, and fire wave and reads the spell name into a string, then outputs the name of the spell entered or that the selection is invalid.
First, the program needs to read in the user’s magic level and guarantee it is between 3 and 99:
#include <iostream> using namespace std; int main() { // variables int magicLvl = 0; // get magic level cout << “Magic Level: “; cin >> magicLvl; // make sure magic level is in range if (magicLvl < 3 || magicLvl > 99) { cout << “invalid magic level (must be 3 <= level <= 99)\n”; return 0; } return 0; }
Like the examples before the range needs to be checked and the program terminated if the input goes out of range. This time a numerical value is being read in though so input failure can happen and needs to be checked for:
#include <iostream> using namespace std; int main() { // variables int magicLvl = 0; // get magic level cout << “Magic Level: “; cin >> magicLvl; // make sure magic level is in range if (cin.fail() || magicLvl < 3 || magicLvl > 99) { cout << “invalid magic level (must be 3 <= level <= 99)\n”; return 0; } return 0; }
Once a valid value in range is guaranteed to have been read in the in then the program can ask to choose between fire bolt, fire blast, and fire wave:
#include <iostream> using namespace std; int main() { // variables int magicLvl = 0; string spell = “”; // get magic level cout << “Magic Level: “; cin >> magicLvl; // make sure magic level is in range if (cin.fail() || magicLvl < 3 || magicLvl > 99) { cout << “invalid magic level (must be 3 <= level <= 99)\n”; return 0; } // get spell selection cout << “Which spell:\n fire bolt\n fire blast\n fire wave\nSelection: “; getline(cin, spell); return 0; }
The spell
is a string so it does not need to be error checked for input failure. It also does not need to be directly checked if a valid spell was entered because checking if it is a valid spell will be handled by multiple selection. There is one problem though, the user will not be able to enter anything for the spell
. As written the program will currently produce the following interaction:
alex ~ % ./a.out Magic Level: 99 Which spell: fire bolt fire blast fire wave Selection: alex ~ %
The user will be able to enter a value for the magicLvl
but the spell
input will be skipped over because recall from when input was first discussed, the stream extraction operator leaves the ’\n’
in the input stream and when a new stream extraction operator is called it automatically removes it. This is not the case for getline()
as getline()
removes the ’\n’
from the input stream, so when a new getline()
is called it does not remove anything prior to reading. Thus if anything is in the input stream, such as the ’\n’
from cin >> magicLvl
then it must be ignored:
#include <iostream> using namespace std; int main() { // variables int magicLvl = 0; string spell = “”; // get magic level cout << “Magic Level: “; cin >> magicLvl; cin.ignore(256, '\n'); // make sure magic level is in range if (cin.fail() || magicLvl < 3 || magicLvl > 99) { cout << “invalid magic level (must be 3 <= level <= 99)\n”; return 0; } // get spell selection cout << “Which spell:\n fire bolt\n fire blast\n fire wave\nSelection: “; getline(cin, spell); return 0; }
Now, the spell
will be able to be read in. To start with the spell selection add 4 separate independent selections after selecting the spell, one for each of the spells and one for an invalid selection:
#include <iostream> using namespace std; int main() { // variables int magicLvl = 0; string spell = “”; // get magic level cout << “Magic Level: “; cin >> magicLvl; cin.ignore(256, '\n'); // make sure magic level is in range if (cin.fail() || magicLvl < 3 || magicLvl > 99) { cout << “invalid magic level (must be 3 <= level <= 99)\n”; return 0; } // get spell selection cout << “Which spell:\n fire bolt\n fire blast\n fire wave\nSelection: “; getline(cin, spell); // fire bolt if (spell == “fire bolt”) { // code for fire bolt } // fire blast if (spell == “fire blast”) { // code for fire blast } // fire wave if (spell == “fire wave”) { // code for fire wave } // invalid selection if (/*what would go here?*/) { // code for invalid selection } return 0; }
For each of the spells there would be no issue, the program would look at the spell
, and if it sees one that matches one of the first 3 spell checks then it will run the code that is eventually put there for the spell cast. But what about the invalid selection? What boolean expression would go where the comment /*what would go here?*/
is? It would be an infinitely long boolean expression saying every permutation of keystrokes the user could possibly enter with the keyboard all logical ORed together.
A much simpler way to say this would just be to say if it’s not one of the previous 3 then just output that it’s invalid. This is what multiple selection is for. Since the else
attaches a block of code to the false
case of an if
the 4 blocks can just be strung together with else
with extra conditions on them as needed. So rather than have 4 separate independent selections, have one multiple selection:
#include <iostream> using namespace std; int main() { // variables int magicLvl = 0; string spell = “”; // get magic level cout << “Magic Level: “; cin >> magicLvl; cin.ignore(256, '\n'); // make sure magic level is in range if (cin.fail() || magicLvl < 3 || magicLvl > 99) { cout << “invalid magic level (must be 3 <= level <= 99)\n”; return 0; } // get spell selection cout << “Which spell:\n fire bolt\n fire blast\n fire wave\nSelection: “; getline(cin, spell); // fire bolt if (spell == “fire bolt”) { // code for fire bolt } // fire blast else if (spell == “fire blast”) { // code for fire blast } // fire wave else if (spell == “fire wave”) { // code for fire wave } // invalid selection else { // code for invalid selection } return 0; }
Now the program will only go on to the later checks and potentially run their blocks of code once the respective earlier checks are run, otherwise they will be skipped over. And in the final case of invalid selection no boolean expression is needed as if it's not one of the 3 then it's invalid.
To the example on Selecting a Spell to Cast add calculating and outputting the max hit for the spell selected based on the formula . For baseDmg use 12 for fire bolt, 16 for fire blast, and 20 for fire wave.
In the program each of the 3 blocks of code for the spells could simply calculate the max hit based on the base damage for the spell then output it:
#include <iostream> using namespace std; int main() { // variables int magicLvl = 0, maxDamage = 0; string spell = “”; // get magic level cout << “Magic Level: “; cin >> magicLvl; cin.ignore(256, '\n'); // make sure magic level is in range if (cin.fail() || magicLvl < 3 || magicLvl > 99) { cout << “invalid magic level (must be 3 <= level <= 99)\n”; return 0; } // get spell selection cout << “Which spell:\n fire bolt\n fire blast\n fire wave\nSelection: “; getline(cin, spell); // fire bolt if (spell == “fire bolt”) { maxDamage = 12 * (1 + magicLvl / 100.0); cout << spell << “ max damage is “ << maxDamage << endl; } // fire blast else if (spell == “fire blast”) { maxDamage = 16 * (1 + magicLvl / 100.0); cout << spell << “ max damage is “ << maxDamage << endl; } // fire wave else if (spell == “fire wave”) { maxDamage = 20 * (1 + magicLvl / 100.0); cout << spell << “ max damage is “ << maxDamage << endl; } // invalid selection else { cout << “invalid spell\n”; } return 0; }
This works but it can be simplified. The only thing changing between the 3 blocks of code for the spells is the constant being multiplied in the formula at the end. Besides that the calculation and output are the same. Rather than repeat the same code over and over and over a variable can just be set to the value needed to be multiplied in the formula, then after the selection the formula can be calculated and results output:
#include <iostream> using namespace std; int main() { // variables int magicLvl = 0, maxDamage = 0, baseDmg = 0; string spell = “”; // get magic level cout << “Magic Level: “; cin >> magicLvl; cin.ignore(256, '\n'); // make sure magic level is in range if (cin.fail() || magicLvl < 3 || magicLvl > 99) { cout << “invalid magic level (must be 3 <= level <= 99)\n”; return 0; } // get spell selection cout << “Which spell:\n fire bolt\n fire blast\n fire wave\nSelection: “; getline(cin, spell); // fire bolt if (spell == “fire bolt”) { baseDmg = 12; } // fire blast else if (spell == “fire blast”) { baseDmg = 16; } // fire wave else if (spell == “fire wave”) { baseDmg = 20; } // invalid selection else { cout << “invalid spell\n”; } maxDamage = baseDmg * (1 + magicLvl / 100.0); cout << spell << “ max damage is “ << maxDamage << endl; return 0; }
Now the selection is just used to set the baseDmg
equivalent to that of the spell that the user selected. The baseDmg
is then used after the selection to calculate the maxDamage for the selected spell, and then output the results.
But what happens in the invalid selection case? If the user enters a string that is not one of the 3 spells the first 3 checks will be false
and all of their blocks of code skipped over, causing the final else
to be run. The else
outputs an error message, but once it is done it continues executing the program, which will calculate the maxDamage as 0 since the initial value of baseDmg
is 0 and anything multiplied by 0 is 0. That value will then be output as the result. To fix this the program needs to be terminated when the error happens:
#include <iostream> using namespace std; int main() { // variables int magicLvl = 0, maxDamage = 0, baseDmg = 0; string spell = “”; // get magic level cout << “Magic Level: “; cin >> magicLvl; cin.ignore(256, '\n'); // make sure magic level is in range if (cin.fail() || magicLvl < 3 || magicLvl > 99) { cout << “invalid magic level (must be 3 <= level <= 99)\n”; return 0; } // get spell selection cout << “Which spell:\n fire bolt\n fire blast\n fire wave\nSelection: “; getline(cin, spell); // fire bolt if (spell == “fire bolt”) { baseDmg = 12; } // fire blast else if (spell == “fire blast”) { baseDmg = 16; } // fire wave else if (spell == “fire wave”) { baseDmg = 20; } // invalid selection else { cout << “invalid spell\n”; return 0; } maxDamage = baseDmg * (1 + magicLvl / 100.0); cout << spell << “ max damage is “ << maxDamage << endl; return 0; }
Now when an invalid spell name is entered the program is terminated so it does not have a chance to go onto calculating or outputting any invalid results.
Combine the final programs from the examples to Login and Cast a Spell into a single program. The program should have the user login prior to casting a spell.
Recall the final state of the program to login:
#include <iostream> using namespace std; int main() { // variables/constants string username = “”, password = “”; const string REQ_USERNAME = “Ronald”, REQ_PASSWORD = “McDonald”; // get username cout << “Username: “; getline(cin, username); // make sure username is big enough if (username.length() < 3) { cout << “invalid username (must be: length >= 3)\n”; return 0; } // make sure username is small enough if (username.length() > 12) { cout << “invalid username (must be: length <= 12)\n”; return 0; } // get password cout << “Password: “; getline(cin, password); // make sure password is big enough if (password.length() < 6) { cout << “invalid password (must be: length >= 6)\n”; return 0; } // make sure password is small enough if (password.length() > 18) { cout << “invalid password (must be: length <= 18)\n”; return 0; } // validate credentials if (username == REQ_USERNAME && password == REQ_PASSWORD) { cout << “logged in\n”; } else { cout << “invalid credentials\n”; } return 0; }
Which ends with checking username == REQ_USERNAME && password == REQ_PASSWORD
to see if the entered credentials are correct, and if they are this statement is true
which causes ”logged in\n”
to be output. Otherwise ”invalid credentials”
is output. But in an actual system something will actually happen after the login, and as written if code was written after the final selection then even in the invalid credentials case the code would be run. To easily fix this the De Morgan’s Law can be applied to the expression so invalid credentials make the expression true
, the ”invalid code”
message can then be output and the program terminated from the first block in the selection. Then there is no need to add a second block for the valid credentials case because the code that runs after the selection is the valid credentials case:
#include <iostream> using namespace std; int main() { // variables/constants string username = “”, password = “”; const string REQ_USERNAME = “Ronald”, REQ_PASSWORD = “McDonald”; // get username cout << “Username: “; getline(cin, username); // make sure username is big enough if (username.length() < 3) { cout << “invalid username (must be: length >= 3)\n”; return 0; } // make sure username is small enough if (username.length() > 12) { cout << “invalid username (must be: length <= 12)\n”; return 0; } // get password cout << “Password: “; getline(cin, password); // make sure password is big enough if (password.length() < 6) { cout << “invalid password (must be: length >= 6)\n”; return 0; } // make sure password is small enough if (password.length() > 18) { cout << “invalid password (must be: length <= 18)\n”; return 0; } // validate credentials if (username != REQ_USERNAME || password != REQ_PASSWORD) { cout << “invalid credentials\n”; return 0; } // code for when valid credentials are entered return 0; }
When either or both the username
or password
are incorrect, then the program is terminated in the block of code so no further code will be executed. Then the code for when valid credentials are entered can be placed where the // code for when valid credentials are entered
comment is, which is the code for the solution to the Casting a Spell example:
#include <iostream> using namespace std; int main() { // variables/constants int magicLvl = 0, maxDamage = 0, baseDmg = 0; string username = “”, password = “”, spell = “”; const string REQ_USERNAME = “Ronald”, REQ_PASSWORD = “McDonald”; // get username cout << “Username: “; getline(cin, username); // make sure username is big enough if (username.length() < 3) { cout << “invalid username (must be: length >= 3)\n”; return 0; } // make sure username is small enough if (username.length() > 12) { cout << “invalid username (must be: length <= 12)\n”; return 0; } // get password cout << “Password: “; getline(cin, password); // make sure password is big enough if (password.length() < 6) { cout << “invalid password (must be: length >= 6)\n”; return 0; } // make sure password is small enough if (password.length() > 18) { cout << “invalid password (must be: length <= 18)\n”; return 0; } // validate credentials if (username != REQ_USERNAME || password != REQ_PASSWORD) { cout << “invalid credentials\n”; return 0; } // get magic level cout << “Magic Level: “; cin >> magicLvl; cin.ignore(256, '\n'); // make sure magic level is in range if (cin.fail() || magicLvl < 3 || magicLvl > 99) { cout << “invalid magic level (must be 3 <= level <= 99)\n”; return 0; } // get spell selection cout << “Which spell:\n fire bolt\n fire blast\n fire wave\nSelection: “; getline(cin, spell); // fire bolt if (spell == “fire bolt”) { baseDmg = 12; } // fire blast else if (spell == “fire blast”) { baseDmg = 16; } // fire wave else if (spell == “fire wave”) { baseDmg = 20; } // invalid selection else { cout << “invalid spell\n”; return 0; } maxDamage = baseDmg * (1 + magicLvl / 100.0); cout << spell << “ max damage is “ << maxDamage << endl; return 0; }
When valid credentials are entered the program then enters into the Casting a Spell part of the example where the code simply needs to be copy/pasted without edits. Since the program terminates when invalid credentials are entered it is not necessary to guard off the code with any further selections which makes the code simpler than had more selections been added.
A final successful interaction with the program follows:
Username: Ronald Password: McDonald Magic Level: 99 Which spell: fire bolt fire blast fire wave Selection: fire bolt fire bolt max damage is 23
When the user enters the valid credentials Ronald
and McDonald
then the program continues onto the Casting a Spell part of the example where 99
and fire bolt
are entered which produces the output fire bolt max damage is 23
.
Switch statements are a control structure that chooses one case from many possible cases based on the value of an integral expression. An integral expression is an expression that evaluates to an integral value, which means it can evaluate to the types:
int
, short
, long long
and their unsigned versions.
char
bool
In general, switch statements take the form:
switch (integralExpression) { case 1: // case instructions break; case 2: // case instructions break; ... case n: // case instructions break; default: // case instructions }
The integralExpression
evaluates to an integral value which can match to a case
. Each value 1
, 2
, ..., n
in each respective case
needs to be changed to values of the type that the integralExpression
will evaluate to based on what values need to be handled for the use case. Then when the integralExpression
is evaluated whatever it resolves to may match up with a case
causing the code for the case
to run. If no case
matches then the default
case allows for code to be run. The default
case can be used or left out depending on the use case.
For example, consider the following snippet of code:
int x = 1; switch (x) { case 1: cout << "Case 1 executed\n"; break; default: cout << "Default executed\n"; }
The variable x
is an integer which is an integral variable that can be used in a switch statement as the statement’s expression. Since x = 1
the expression matches to case 1
which causes "Case 1 executed\n"
to be output followed by the switch being broken out of using the break
statement. The break
statement tells the program to stop executing the switch
statement which causes the program to continue execution after the block of code for the switch
. If however x = 50
or any value other than 1
then there would be no case for the expression to match except the default
which causes "Default executed\n"
to be output. The default
does not need a break
after it because the next thing that comes after it is the end of the block of code for the switch
.
But what happens if the break is left out:
int x = 1; switch (x) { case 1: cout << "Case 1 executed\n"; default: cout << "Default executed\n"; }
When run this snippet will produce the output:
Case 1 executed Default executed
Both messages are output! This is because the break
is what causes the switch
to stop being executed at the end of each case. If a case
has no break
then the switch
falls through to the next case
until a break
is hit, known as fall-through. This can be very useful in selections where the same code has to be run for multiple cases, but in this use case it is important to not forget the break
otherwise the program will output unintended results.
Since boolean values are integral they can be used in switch statements:
bool b = true; switch (b) { case true: cout << "b was true\n"; break; default: cout << "b was false\n"; }
But when this is done there are only 2 cases which can run: true
or false
so a switch statement is overkill. This would be the perfect use case for 2 way selection:
bool b = true; if (b) { cout << "b was true\n"; } else { cout << "b was false\n"; }
In general it is best to use 1/2/n-way selection as most selections are based on boolean expressions. When the selection should be converted over to a switch statement is when there are many integral values checks that are being logical ORed together to trigger a block of code to be run. In this case fall-through can be used rather than the logical ORs to make the code cleaner.
Use a switch statement to calculate/output area or perimeter of a square based on a user’s selection.
First, the side length of the square needs to be read and error checked:
#include <iostream> using namespace std; int main() { // variables double side = 0.0; // read side length cout << “Side length: “; cin >> side; // error checks if (cin.fail() || side <= 0) { cout << “Invalid side length\n”; return 0; } return 0; }
If the side length read in is not a number or is not positive, as a side must have some length to be a square, then it is not a valid side length. The user’s selection for area or perimeter can then be read:
#include <iostream> using namespace std; int main() { // variables double side = 0.0; char selection = 0; // read side length cout << “Side length: “; cin >> side; // error checks if (cin.fail() || side <= 0) { cout << “Invalid side length\n”; return 0; } // read calculation selection cout << “(A/a)rea -or- (P/p)erimeter: “; cin >> selection; return 0; }
Since the selection
is a character which is an integral data type, its value can be switched on calculating area if a
or A
is entered and perimeter if p
or P
is entered:
#include <iostream> using namespace std; int main() { // variables double side = 0.0; char selection = 0; // read side length cout << “Side length: “; cin >> side; // error checks if (cin.fail() || side <= 0) { cout << “Invalid side length\n”; return 0; } // read calculation selection cout << “(A/a)rea -or- (P/p)erimeter: “; cin >> selection; switch (selection) { case 'a': area = side * side; cout << "area = " << area << endl; break; case 'A': area = side * side; cout << "area = " << area << endl; break; case 'p': perimeter = side * 4; cout << "perimeter = " << perimeter << endl; break; case 'P': perimeter = side * 4; cout << "perimeter = " << perimeter << endl; break; default: cout << "Invalid selection\n"; } return 0; }
If the user enters a
, A
, p
, or P
the proper value will be calculated and its result output. Otherwise if anything else is entered "Invalid selection\n"
will be output.
But this can be simplified. Consider the following:
switch (selection) { case 'a': area = side * side; cout << "area = " << area << endl; case 'A': area = side * side; cout << "area = " << area << endl; break; }
If selection
is a
then the area will be calculated and output twice since there is no break
. So if the code from case ‘a’
was completely removed:
switch (selection) { case 'a': case 'A': area = side * side; cout << "area = " << area << endl; break; }
When selection
is a
it will just run the code for case ‘A’
, calculating the area and outputting it only once in the case that selection
is a
. This logic can be used for the perimeter as well:
#include <iostream> using namespace std; int main() { // variables double side = 0.0; char selection = 0; // read side length cout << “Side length: “; cin >> side; // error checks if (cin.fail() || side <= 0) { cout << “Invalid side length\n”; return 0; } // read calculation selection cout << “(A/a)rea -or- (P/p)erimeter: “; cin >> selection; switch (selection) { case 'a': case 'A': area = side * side; cout << "area = " << area << endl; break; case 'p': case 'P': perimeter = side * 4; cout << "perimeter = " << perimeter << endl; break; default: cout << "Invalid selection\n"; } return 0; }
Now, if the user enters a
or A
the area will be calculated and output from case ‘A’
, and if they enter p
or P
the perimeter will be calculated and output from case ‘P’
. If any other value is entered then "Invalid selection\n"
will be output.
Nesting is when one control structure is placed inside of another control structure. In general, selections can be nested inside of one another in any arrangement:
if (expression1) { if (expression2) { statement; } ... // can have else if/else switch(expression3) { ... } ... } ... // can have else if/else with nesting
For example, to be able to gamble a person must be 21:
age = 20; if (if age >= 21) { cout << “Allowed to gamble\n”; }
But on people’s 21st birthday the casino wants the user to be greeted with a happy birthday message, followed by an ad to sign up for a player’s card. Inside of the block of code it is already known that the person is at least 21 years of age, but another check can be nested inside to check if the user is exactly 21:
age = 21; if (age >= 21) { if (age == 21) { cout << “Happy birthday!\n” << “Sign up for a player’s card at the club desk!\n” << “Get a free meal at the buffet today!\n”; } cout << “Allowed to gamble\n”; }
Now if the user is 21 or older it will be displayed they are allowed to gamble, but in the case that they are 21 they will be advertised to go sign up for a player’s club card.
Control Structure - A programming construct that determines the flow of execution of instructions in a program
Selection - A type of control structure that allows a program to choose between different paths of execution based on whether a condition is true
or false
One-Way Selection - A single block of code that executes only if a condition is true
.
Input Failure - When the program tries to read input but the input does not match the expected type or format.
Two-Way Selection - A control structure that chooses between two possible paths of execution, depending on whether a condition is true
or false
.
N-Way (Multiple) Selection - A control structure that allows a program to choose between more than 2 possible execution paths.
Switch Statement - A control structure that chooses one case from many possible cases based on the value of an integral expression.
Integral Expression - An expression that evaluates to an integral value.
Fall-Through - When a case
has no break
and a switch
falls through to the next case
until a break
is hit.
Nesting - When one control structure is placed inside of another control structure.
List the five kinds of selection mentioned.
When does a one-way selection run its block?
After a one-way selection finishes (true or false), where does execution continue?
What does two-way selection add beyond one-way selection?
What is the difference between 2-way and multiple selection?
In what order are expressions evaluated in multiple selection?
What is an “integral expression”?
What are the allowed types of a switch
expression?
What does default
do in a switch
, and is it required?
What is “fall-through”?
Why are break
s needed in switch
cases?
When are break
s needed in switch
cases?
Why is switch
usually a poor fit for boolean conditions?
What does “nesting selections” mean?
Give one reason to nest an if
inside another if
.
When would you prefer multiple selection over a switch statement?
When would you prefer a switch statement over an multiple selection?
How do you guarantee that exactly one branch executes among many mutually exclusive conditions?
What kinds of bugs can overlapping conditions create in separate if
statements? Give a brief example scenario.
When several branches share the same final computation, where should the shared code live to avoid duplication?
What are the pros and cons of using an early return on error versus wrapping the rest of your code in an else
block?
Why can’t switch
use floating-point or std::string
conditions, and what alternatives should you use?
If you need to branch on a std::string
like "fire wave"
, what selection structure should you use and why?
When is intentional fall-through appropriate?
Where can the default
label appear in a switch
, and does its position affect behavior?
What does break
do inside a switch?
After cin >> n
followed by getline(cin, s)
, why might s
be empty, and how do you fix it?
After a failed numeric extraction, what is the state of cin
, and what happens if you keep reading without clearing it?
Compare return 0;
in an error branch to wrapping the rest of the code in an else
. Which is easier to maintain and why?
What is input failure?
How is input failure checked?
Why validate immediately after I/O instead of collecting several inputs first and checking at the end?
MORE TO BE ADDED SOON