Flow control(branching, looping) |
All useful programs manipulate DATA.
Data is stored and manipulated (modified) inside VARIABLES, which are TYPED in C++.
Common [data]types include bool, string, int, double.
Operators are symbols that are used along with vars, to create expressions and statements.
Expressions result in values.
Today, we'll study 'execution "flow"'.
So far we've looked at how literals and simple expressions can provide a value for a variable (such as i,j,k below):
int i=24, j=36;
int k = i+j; // i+j generates a value for k
Alternately, we can use a **function** to obtain a value (RMB-click to run - execute the code several times):
#include <iostream>
using namespace std;
int main() {
// rand() is a function that "returns"
// (outputs/generates/creates) a value
double r = rand();
cout << r << endl;
}
In the example we looked at, we always obtained 1.80429e+09 as the result of (output from) rand(), each time we re-ran the program. How can we get rand() to generate a DIFFERENT value each time we run the program? Also, how can we tell what the largest rand() value will be? Click to run:
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
// srand() "seeds" the random # generator;
// time(NULL) fetches the current time,
// which is used as a seed for srand()
srand(time(NULL));
double r = rand();
cout << r << endl;
cout << "The value of RAND_MAX is " << RAND_MAX << endl;
}
Notes on srand():
We can divide rand() by RAND_MAX, to get a nice 0 to 1 ("0..1") random value, like so [click]:
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
srand(time(NULL));
double r = ((double)rand())/RAND_MAX;
cout << r << endl;
}
Once we have a 0..1 value, we can scale and shift it to any other range of values (eg. -10 to 10). HOW?
srand() and rand() are FUNCTIONS (they can perform a useful operation, and often, "return" a value).
max(a,b) likewise is a function that returns the larger of a,b values that are 'input' to it. Functions can be composed (nested), eg. max(a,max(b,c)) returns the max of a,b and c. Use 'cout' to test this for yourself!
Now we can start talking about program 'flow'..
It is often convenient to visualize our code as being laid out in 2D like a pinball machine's base. Then, program execution (aka program flow, control flow..) can be pictured as a ball making its way through ONE of the many possible PATHS through the code.
Three kinds of flows exist (plus a fourth, 'function call' one, which is a 'jump'):
* sequential/linear: straight line flow
* branching: choice of alternate routes, ONE of the alternatives picked
* looping: repetition of a section
Just FYI - a language that lets us specify sequences, branching and looping is called a 'Turing complete' language - it means we can use it to express anything that can be computed.
This is where statements get executed IN ORDER, top to bottom. Eg:
double tempInCelsius = 32.4;
double tempInFahrenheit;
// calc
tempInFahrenheit = tempInCelsius*9/5.0 + 32;
cout << tempInFahrenheit << endl;
In the above, the four statements get executed once, top to bottom. This is called **sequential flow** of execution.
Continuing on, we first look at branching, then looping.
What if we're OPTIONALLY able to run a block of code? Pseudocode example:
Grocery store checkout: finalize purchase (generate total) IF club member, apply discount to items // optional 'branch' Payment: present total [possibly modified] amount to the user
The discount applying in the above happens, **only if** the user is a club member. We use an 'if statement' to code such a scenario (if this, then that)
At a restaurant: **if** the age of a child is/appears to be under 10, provide crayons+coloring book and a kids menu.
In an exam, multiple choice question: **if** the user picks the right answer, ++ their score.
The 'if ' statement looks like this:
if (condition) {
statement
}
Generate a 0..1 random #; **if** it is < 0.5, say "Lucky!" [otherwise do nothing]. Run the following example that shows this:
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
srand(time(NULL));
double r = ((double)rand())/RAND_MAX;
cout << r << endl;
if(r<0.5) {
cout << "Lucky!" << endl;
}
}
In the above, you'd see 'Lucky!' printed out about 50% of the time; how would you make it be printed out, 75% of the time (or 10%, or 82%..)?
A programmer is sent to the store, with these instructions from the spouse: "Pick up a loaf of bread. If they have eggs, get a dozen.". When the programmer returns with a dozen loaves of bread, the spouse asks why.. "They had eggs." :)
If the 'if' condition FAILS, we need a Plan B, which is specified using the 'else' clause (some other piece of code that can run, (only) when the 'if' condition is not true):
if (condition) {
statements
}
else {
alternate statements
}
Note that in the above, we don't have/need a condition for 'else'! The condition expression that is next to 'if' can either be true, or false - depending on this, the top {} or the bottom {} gets executed. Also, you can't have an else{} all by itself the way you can have an if(){} all by itself.
We can use srand(), rand() and if(){}else{} to flip a virtual coin as pictured here..
Run the following code which simulates the coin flipping:
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
srand(time(NULL));
if(((double)rand())/RAND_MAX < 0.5) {
cout << "HEADS" << endl;
}
else { // >= 0.5
cout << "TAILS" << endl;
}
}// main()
What if we need to 'chain' several conditions?
Analyze speech: if Japanese { translate Japanese to English }// Japanese else { if Chinese { translate Chinese to English }// Chinese else { if Vietnamese { translate Vietnamese to English }// Vietnamese else { say "Unable to translate!" }// not Vietnamese }// not Chinese }// not Japanese
We're pulling to the right (like a car with bad alignment!) in the above.. This is because each else{} gives rise to more conditions to test. Such 'nested if statements' become very hard for programmers to follow, in real-world examples. So we seek a way to make the code more readable.
A better way to specify multiple aternative clauses is to use the 'if, else-if' clause:
if (conditionA) {
}
else if (conditionB) {
}
else if (conditionC) {
}
else {
}
/*
The above is better (easier to follow) than this, especially when the {} blocks contain lots of code inside them:
if (conditionA) {
}
else { if (conditionB) {
}
else { if (conditionC) {
}
else {
}
}
}
*/
In the above, we leave out the {} for else conditions for conditionA and conditionB, since there is just a SINGLE statement for the else (however complex that statement might be). In other words, the above is not a new form of if-else - it's just a more human-readable form.
Generate a 0..1 random #, classify it into one of 5 'bins' (or 'buckets') [run this]:
#include <iostream >
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
srand(time(NULL));
double r = ((double)rand())/RAND_MAX;
cout << r << endl;
if(r<0.2) {
cout << "A" << endl;
}
else if (r<0.4) {
cout << "B" << endl;
}
else if (r<0.6) {
cout << "C" << endl;
}
else if (r<0.8) {
cout << "D" << endl;
}
else { // between 0.8 and 1.0
cout << "E" << endl;
}
}// main()
As you can see, the 'if .. else if .. else if .. else' usage makes the code easier to read: start with an if(){} block, include as many else if() {} blocks as necessary, finish with an else{} block.
If a variable's value come from a small set of values (eg. axis can be X or Y or Z, a color can only be one of the 7 rainbow colors, currency can only be dollar or euro or franc or pound), we can do branching based on the variable's value - this is essentially 'multi-way branching':
switch (var) {
case val1:
..
break;
case val2:
..
break;
...
}// end switch
The above 'switch statement' (aka 'case statement') can be read as: "if it is the case that var's value is val1 (eg axis="X"), do ..; else if it is the case that var's value is val2 instead, do ..".
Expressing logic this way lets us avoid verbose statements of the form: if (var==val1){ } else if (var==val2){ } else if..
Note that the switch statement is one of the few places in C++ where the colon (:) symbol is used (; is much more common).
Here is an example program showing how the 'switch' statement is used [run this]:
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
srand(time(NULL));
int r = rand() % 4; //0,1,2 or 3
switch(r) {
case 0:
cout << "r is 0" << endl;
break;
case 1:
cout << "r is 1" << endl;
break;
case 2:
cout << "r is 2" << endl;
break;
case 3:
cout << "r is 3" << endl;
break;
default:
cout << "Unknown value!" << endl;
}
}// main()
In the above, the default: case kicks in, when 'r' is not 0 or 1 or 2 or 3 - it is a 'final chance' block. Also, notice the 'break;' at the end of each case - these are what stop the execution from "punching through" to the next case underneath!! We DO expect just each case's code to run when that case condition is met, but C++ asks us to specify this explicitly via 'break;'. Why is it even there in the syntax, if it is the default? Because, based on some complex programming logic, we could CHOOSE to remove a break; between two cases (eg. if a customer purchases shipping insurance, also activate the expedited shipping option whose code is right below).
The ? (conditional, or ternary) operator is used as a shortcut, when assigning one of two different values to a var, based on a condition being true or false.
This is what we'd like to express, more compactly using '?' (see the next code block):
int toss;
double r = ((double)rand())/RAND_MAX;
if(r<0.5)
toss = 1;
else
toss = 0;
The above can be more compactly expressed using the ? operator, as follows:
double r = ((double)rand())/RAND_MAX;
// condition ? result_if_true : result_if_false
int toss = (r<0.5)?1:0; // r<0.5 being yes makes toss=1, else (:) toss=0
// note that we can write the above more compactly (becomes less readable):
// int toss = ((((double)rand())/RAND_MAX)<0.5)?1:0;
// read the above calculation expression "from in to out"
Here is a runnable version of the above.
Branching type of flow control in C/C++ can be in these five forms:
* if(){}
* if(){} else{}
* if() else if().. else if().. else{}
* switch(){}
* var = condition?true_val:false_val
What (code) would we write, to output the following?
Hello
C++
Hello
C++
Hello
C++
This will do it:
cout << "Hello" << endl;
cout << "C++" << endl;
cout << "Hello" << endl;
cout << "C++" << endl;
cout << "Hello" << endl;
cout << "C++" << endl;
How to output the pair of lines, a thousand (or billion) times??! Clearly, we can't hope to write two thousand (or two billion) cout statements! Likewise, we can't repeat code to update 1 BILLION Facebook users' profiles, place 1 million soldiers on a battlefield (LOTR), construct 100 windows for a building.. There has to be a PROGRAMMATIC way to repeat code.
To repeat (eg. 10 times) the execution of those two 'cout' stmts we just saw, we need to "wrap them" in a loop construct called the 'for' loop (run this):
for(int i=0;i<10;i++) {
cout << "Hello" << endl;
cout << "C++" << endl;
}
We employ a 'loop counter' variable, i, to keep track of the count.
We start with i=0, KEEP REPEATING as long as i<10 remains true, and do i++ at the bottom of each loop when we reach the closing } (ie. after the 2nd cout). The sequence of steps is:
initialize the loop counter (just ONCE, at the beginning) test the condition: true? loop, ie. execute all the code between { and }, then modify the counter false? stop looping, we're done
The idea is that loop can continue as long as the test passes, and the counter modification at each step will at some point make the test fail (if the test NEVER fails, the looping will happen forever!).
So the two cout stmts are repeated while i is 0, then 1, then 2,3,4,5,6,7,8,9; when i becomes 10, the looping is skipped (10<10 is FALSE) - we're **DONE** looping.
From http://slideplayer.com/slide/5167181/, here is a visualization:
The loop syntax again is:
for(starting_loopvar_val;termination_condition;loopvar_change) {
loop body
}
Eg. here is a way we can print 10,11,12,13..100 (run this):
#include <iostream>
using namespace std;
int main() {
for(int i=10;i<=100;i++) {
cout << i << endl;
}
}
How could we print 30,29,28...5? A solution: run the loop backwards!
#include <iostream >
using namespace std;
int main() {
for(int i=30;i>=5;i--){
cout << i << endl;
}
}// main()
Note: the loop variable type can be float or double, and the increment or decrement can even be fractional (eg. 0.01, eg. to increment an angle value).
How would we run a loop to output 1,3,5,7,9....99? We can increment the counter by 2, like so:
#include <iostream>
using namespace std;
int main() {
for(int i=1;i<100;i+=2) {
cout << i << endl;
}
}// main()
Note: we can also do -=, *= and /= for modifying the loop counter - any expression that CHANGES the value of the loop counter (hopefully steer it towards falsifying the test!) will do.
There is a different way to loop: only the (stopping) condition is specified at the top, and the loop counter change happens (NEEDS to happen) INSIDE the loop body:
while (condition_is_true) {
statements
UPDATE the loop counter!
}
For example [run this]:
int i=100; // init a loop counter
while(i>=0) {
cout << i << endl;
i--; // UPDATE loop counter!
}
Conceptually, a while() loop can be thought of as a for() where its three parts (init, test, update) are broken up:
init while(test) { (run body and) update }
Just like with for(), it is the update that eventually causes the test to fail, and therefore, the loop to terminate.
Exercise: read up, and practice coding, this alternate form of looping:
do {
} while(condition)
Looks like an upside down while() :) Such a loop will **run at least once** - why? Because the steps are:
init do { (run body and) update } while(test)
We'd find out that our test failed, only after getting past (running) the body, even if just once.
It is possible to terminate a ('while' or 'for') loop, even **before** the loop condition becomes false - we'd use a 'break' statement to do so. Here is an example (loop runs max 100 times):
#include <iostream>
using namespace std;
int main() {
srand(time(NULL));
for(int i=0;i<100;i++) {
double r = ((double)rand())/RAND_MAX;
if(r>0.75) break; // QUIT looping, even before i might reach 100!
// we didn't break out - do something with our 'r'
cout << r << endl;
}// next
cout << "Done looping" << endl;
}// main()
In some situations, we'd like to skip the rest of the loop (while we are in the middle of it), but go back and do the next iteration (ie. we don't want to quit looping entirely). We use a statement called 'continue' to achieve this. Run this to see 'continue' in action:
#include <iostream>
using namespace std;
int main() {
srand(time(NULL));
for(int i=0;i<100;i++) {
double r = ((double)rand())/RAND_MAX;
if(r>0.75) continue; // go back to top of for(), do the i++
// do something with our 'r'
cout << r << endl;
}// next
cout << "Done looping" << endl;
}// main()
Here is what we looked at:
* for(){} is the classic way to loop in C/C++
* while(){} is an alternate way
* do{}while() is another alternative
* 'break' is used to quit looping, early
* 'continue' is used to quit the rest of a loop, but keep looping
All complex logic in all programs in all programming languages involve the use of looping and branching! As we will see in future examples, these can be NESTED, ie. a loop can be inside a branch, a branch can be inside a loop, a loop can be inside a loop, a branch can be inside a branch - and this can happen multiple times (nested in arbitrarily deep "levels"), eg.:
loop branch true branch loop loop false branch branch true branch loop false branch sequence
The above is the A VERY BIG IDEA in programming! The most complex logic (solving fluid flow equations, flying a missile, playing chess, transfering money in a bank account....) can ALL be expressed in the form of nested loops and branches (and recursion, too - more on this later).
With practice, you will learn to express "program logic" [steps to solve a problem] using nested loops and branches. Note that too it's language-independent - the same logic can be coded up in dozens (or even 100+) programming languages. [roughly, just like a sunset scene can be painted using watercolor, oil, acrylic, color pencil, pen-and-ink..].
The following code uses the 'Mersenne Twister' RNG algorithm (https://www.cplusplus.com/reference/random/mt19937/, https://www.guyrutenberg.com/2014/05/03/c-mt19937-example/). Please use this everywhere you want random numbers, INSTEAD OF rand()!
#include <iostream> #include <random> #include <ctime> using namespace std; ///////////////////////////////////////////////// mt19937 rng(time(NULL)); //declared in <random> ///////////////////////////////////////////////// double random() { double r=((double)rng())/rng.max(); return r; // 0..1 }// random() int main() { cout << random() << endl; return 0; }//main()
Running the above every second (which increments time() by 1) produces well-distributed random values in 0..1, NOT a slowly increasing one!