The C++ Programming Language

Part 2: Controlling the flow of our code

Dealing with data means we must do a lot of operations on them. This means we have to repeat some process, take different paths, and alter the flow of the code to achieve our goal. There are endless examples of everyday life where we go through data to solve some trivial problem.

Imagine the process of calculating your average score in the exams, or finding the longest of some sticks, or even harder finding the path from your home to your job.

The logical process to solve a problem is called algorithm. Algorithms are complex processes and can only be completed if we have the ability to modify the flow of our though. Straightforward approach is only useful when we add two numbers. The harder the problem the more flexible our thought process must be. The same rule applies to computer programs as well.

Branching, or taking a different path

We are starting with branching our code to different paths. The first example that comes to mind is how we could notify a user whether he passed the exams or not.

if - else

In our mind we look at his grade and if it is greater than or equal to a certain value we tell he passed, otherwise we tell him he failed. This is actually the first algorithm we create to solve a problem. Here is he code for this sample:

#include <iostream>

int main() {
    double a;                                   // declare our variables
    std::cout << "tell us your grade (1-20):";   // give the user a hint
    std::cin >> a;              // read the value of a from the keyboard

    if (a >= 10) {
        std::cout << "pass\n";
    }
    else {
        std::cout << "fail\n";
    }
    std::cout << "thank you\n";
}

Now we will go through the code and analyze the syntax of this statement. As expected we have the if keyword followed by a comparison operation in the parenthesis. Then we have a statement in curly brackets, the else keyword and finally another statement in curly brackets. Let us analyze these five blocks one by one.

The first (if) is the basic keyword to signal that we want to perform a branching statement. As in real life it is followed by the rule we have to check. In programming terms this is a conditional. It is an expression that results either in true or false. For a computer anything that equals to zero (0) is false, and everything else is true. After the conditional comes the compound statement that has the code to be executed if the conditional is true. In the general case we can have another path to follow in case the conditional is false. That path is the compound statement that follows the else statement. When everything is done the code resumes at the statement following the last compound statement.

Here is the official syntax of the if statement:

if (condition is true)
   compound statement
else
   compound statement
statement(s)

or if we omit the else part, which depends on the logic we want to implement:

Conditions

Before we continue with any other branching method the language offers, this is a good opportunity to see these conditionals from a little closer and understand them because they are used in many cases in C++ and in programming in general.

As we mentioned before a conditional is any expression that can be evaluated as either true or false. The most profound expressions are those where we check for equality between values. In our example we check whether the user’s mark is greater or equal to 10. These equality check operators are called relational operators, and we saw them in part1. Here they are:

OperatorDescriptionSyntax
==Checks if two operands are equal or not, if they are returns true, otherwise falsea == b
!=Checks if two operands are equal or not, if they are NOT returning true, otherwise falsea != b
>Checks if the value on the left is greater than the value on the righta > b
<Checks if the value on the left is less than the value on the righta < b
>=Checks if the value on the left is greater than or equal to the value on the righta >= b
<=Checks if the value on the left is less than or equal to the value on the righta <= b

C++ has introduced a new type of data called bool. This can hold only two values: true and false.

Apart from real Boolean values though our conditionals can have any variable or function call. Traditionally any non-zero value is considered as true, and a zero value is false. Here is an example:

// a function returning always 0
int zero_return() {
    return 0;
}
// and a function returning always true
bool is_valid() {
    return true;
}

The code calling these functions would be like:

// since the function returns 0, this is always false
if (zero_return()) {
    std::cout << "if you see this message something went wrong\n";
}
// this is always true
if (is_valid()) {
    std::cout << "this is a really valid result\n";
}

We can combine conditions using the logical operators we saw in part 1:

OperatorDescriptionSyntax
&&AND operator, returns TRUE if both operands are TRUEa && b
||OR operator, return TRUE if either of the operands is TRUEa || b
!NOT operator, negates logical status of expression!a

The combined logical result follows the rules of Boolean Algebra. The order of precedence for the logical operators is from left to right. Depending on the logical operator it is possible that the compiler will generate code that does not execute the second conditional if the first is sufficient. Look at these examples:

// for AND both must be true
// this executes both if the first is true
// and only the first if the first fails
if (value > 10 && value < 20) {}

// for OR only one needs to be true
// this may execute only the first and if it fails
// it executes the second
if (value > 10 || value < 20) {}

You should keep this in mind and never rely on conditional to execute any code but only perform logical checks.

switch - case

The switch statement in C++ is a control statement that allows you to execute different blocks of code based on the value of an expression. It is often used as an alternative to a series of if … else if statements, making the code cleaner and easier to read.

Here is the syntax for this statement:

switch (expression) {
case constant1:
    // code to be executed if expression equals constant1
    break;
case constant2:
    // code to be executed if expression equals constant2
    break;
    // you can have any number of case statements
default:
    // code to be executed if expression does not match any case
    break;
}

So, this code:

// user menu option processing
if (menu_selection == 1) {
    std::cout << "one\n";
    // execute respective code
}
else if (menu_selection == 2) {
    std::cout << "two\n";
    // execute respective code
}
else if (menu_selection == 3) {
    std::cout << "three\n";
    // execute respective code
}
else {
    std::cout << "selection out of bounds\n";
    // execute respective code
}

Can be rewritten like this:

// user menu option processing
switch (menu_selection) {
case 1:
    std::cout << "one\n";
    // execute respective code
    break;
case 2:
    std::cout << "two\n";
    // execute respective code
    break;
case 3:
    std::cout << "three\n";
    // execute respective code
    break;
default:
    std::cout << "selection out of bounds\n";
    // execute respective code
    break;
}

It is easier to follow this code because we know that all the cases are testing the save expression against the given values, where in the first implementation we need to check them one by one.

With this command we introduce the break statement. When encountered, this statement transfers control to the first statement after the compound statement, it appears, in this case after the switch. If we do not issue the break in some case, the code will continue executing the next case. Try commenting out one break and see the results. This feature in the design of the command gives us some flexibility when designing code, but it comes with the cost of extra care we have to put when designing and writing our code.

Loops, performing repetitive tasks

Many times, in our programs we have to do the same thing repeatedly. Rewriting the same code though is not very productive and in general it is error prone. It is not practical either limiting us to a small number of options.

For this reason, programming languages incorporate loops in their syntax. Loops are especially useful for several reasons.

while

The while loop in C++ executes a block of code repeatedly as long as a logical condition is true. Here is the typical syntax of the loop:

while (condition) {
    // code block to be executed
}

The code within the loop block starts executing if the condition is true and stops executing when the condition is false. It is important to remember that the code in the loop might never run if the condition is false.

In practice we have some code before the loop to do some initialization and some code in the loop that might change the value of the condition. Here is the pdated version of the loop:

initialize_condition;
while (condition)
{
    statement(s);
    step_condition;
}

Here is how we can use the while loop to calculate the sun of the numbers from 1 to 5:

// calculate the sum of 1->5 without a loop
// this is a fixed calculation
int sum = 1 + 2 + 3 + 4 + 5;

// do the same with the while loop
sum = 0;  // reset the sum
int i = 1; // initialize the condition
// while i < 6(not included) means from 1 to 5
while (i < 6) {   // check the condition
    sum += i;
    i += 1;    // move to the next number
}

The first calculation is a fixed addition of five numbers. If we look at the loop we can see that we can make it more flexible. We can modify the conditional and instead of going up to 5 we can use a variable and go as far as we want. Likewise, we can modify the starting value as well as the step ending up in a function that can actually calculate any sum we want:

int calc_sum(int start, int finish, int step) {
    // do the same with the while loop
    int sum = 0;  // initialize the sum
    int i = start;    // initialize the condition
    // while i < finish(included) means from 1 to 5
    while (i <= finish) {   // check the condition
        sum += i;
        i += step;    // move to the next number
    }
    return sum;
}

The fixed summation of the values just does not work.

We can make the loop run forever if we put in the conditional the value 1. Endless loops are used in programs we do not know how many times we will have to repeat the loop. For example, wait until the user chooses to terminate the program, and in any other case do some processing.

for

The while loop is not our only option in C++. A similar loop with while is the so called for loop. We usually visit the for loop after the while loop because it adds some things to its syntax, but otherwise it is almost the same. We take a look at its syntax and then we will break it down:

for (initialization_statement; condition; step_statement)
    compound_statement

As we can see it is the same as the updated version of the while loop we developed. The two loops differ only in syntax. Otherwise, they are completely the same. Especially if we keep in mind that we can omit the initialization_statement and the step_statement. In that case the loop will be:

for (; condition;)
    compound_statement

Notice that we have left the semicolons. We can leave out the conditional as well, NOT the semicolons, and the loop will run forever.

The generated code for this loop starts with the initialization_statement. Then it checks for the condition. If it is false the loop will not run. At the end it executes the step_statement. After that it goes back to check the condition, and it is true it runs the loop or if it is false it continues with the first statement that comes after the loop.

Like the while loop, this loop also checks for the condition at the beginning.

do ... while

Finally, we have one more loop. This is the do…while loop. Here is its syntax:

do
    compound_statement
while (condition);

Our sum calculation function using this loop will become:

int calc_sum_with_do(int start, int finish, int step) {
    int sum = 0;  // initialize the sum

    int i = start;
    do {
        sum += i;
        i += step;
    } while (i <= finish);
    return sum;
}

There is a significant difference between this and the previous two loops. In this statement the check for the condition is done at the end of the loop. This means that the code in the loop will run at least once. We may either have a variable we initialize before the loop to control it, but the most common case is terminating the loop by checking one or more variables that are set within the loop:

void wait_for_input() {
    int value;  // just declare the variable
    do {
        std::cout << "input a number between 1 and 10:";
        std::cin >> value;
    } while (value < 1 || value >20);  // as long as we are out of bounds, repeat
    std::cout << "value:" << value << "\n";
}

Getting out of the loop

To add flexibility in loops C++, much like any language that respects itself, gives us the ability to modify the flow of the code in a loop. This can be achieved with the use of two statements: break and continue. Here is a more detailed presentation of the two.

break

This statement terminates the loop immediately and transfers control to the first statement after the loop. Needless to say, it skips any remaining code that comes after it within the loop. It usually comes within a branching statement. It is clear that issuing a break command without a check if it is appropriate is not a particularly good practice.

Being able to terminate the loop should something unexpected happen, gives us the flexibility to add one more condition to check while iterating.

We will use the while loop to present the syntax of the command, and for the example code. Once we understand its behavior and syntax, it is obvious that it is exactly the same no matter what looping statement we use.

while ( condition )
{
    statement(s)
    if (we_should_break)
    {
        break;
    }
    [optional statement(s)] 
}
code_comes_here_after_break

As we said, although we have the main condition to control the loop we can also have one more control point where we check and terminate the loop, skipping some code.

We will modify our calc_sum function to take an extra parameter, the maximum allowed value for sum. Then our function would be like:

int calc_sum(int start, int finish, int step, int max_limit) {
    int sum = 0;  // initialize the sum
    int i = start;    // initialize the condition
    while (i <= finish) {   // check the condition
        sum += i;
        if (sum > max_limit) { // limit was passed?
            sum -= i;   // need to go back
            break;   // and terminate the loop
        }
        i += step;    // move to the next number
    }
    return sum;
}

continue

There are cases though, where we do not want to break the loop but rather go back to the beginning, skipping some of the code. In that case we can use the continue statement. This statement does just that.

while ( condition )
{
    statement(s)
    if (we_should_skip)
    {
        [optional statement(s)]
        continue;
    }
    [optional statement(s)] 
}

Depending on the loop and the way we write our code, there might be some unexpected and nasty results. Take a look at this image and note where the control is transferred after continue.

continue

In the case of for loop, the control goes to the step statement, which allows the loop to continue its operation normally. Although the second loop is supposed to do the same thing, continue transfers control to the conditional check, the variable is not incremented, and the loop continues forever. The same would have happened if we had omitted the stepping statement in the for loop as well.

This means that we must be twice as careful when we use the continue statement, because it may lead to really bad situations, which may be extremely hard to find if our code is long and complex.

goto

This is one of the simplest commands in any language that supports it. It issues a jump instruction to the CPU transferring control to a certain point in the code, defined by the label following the command. The label could be before the goto command, resulting in some kind of loop, or after the goto and skip some code. In the early days of computing when languages did not have so advanced features, our programs were small, and we were struggling for execution speed, this command was really useful.

As our programs grow both in size and complexity, using goto may lead to code that is hard to understand and of course to maintain. We have to follow the code, find the label, and try to guess what it is all about.

We must keep in mind that the most expensive part of software development is maintenance, not initial development.

The years have passed and now our languages have evolved, our programs have grown enormously, and the computers are fast enough to handle the most complicated tasks. The new features of programming languages have made features like goto almost obsolete. So far I have encountered only one (1) case where we could not implement an algorithm without the use of a goto.

Comparing a for loop in C++ with a piece of code that does the same thing using goto will make this statement clear. Here is the code using the for loop:

for (init; condition; step) {
    code;
}

Perfectly clear for anyone reading it. Here is the same loop using goto:

init;  // perform initialization statement
start_of_loop:  // mark the beginning of the loop
if (!condition) // check condition
    goto end_of_loop; // if it fails (!condition), abort loop

code;  // execute the code in the loop

continue_goes_here:  // continue jumps here (with a goto as well)
step;   // perform the step
goto start_of_loop;  // and go back to the start
end_of_loop: // break jumps here with goto

Technically this is the kind of code the compiler generates. But this code is rebuilt every time we change the C++ above, and not fixed or maintained, operations that may lead to errors.

return

This command is used in functions when we want to return to the caller. It can appear anywhere within the function, and it immediately returns control to the point the function was called. In the case of functions that have a return type other than void we also have to specify a value to return.

We usually find it at the end of a function returning some result after the function has manipulated the data, or nothing in case of void functions. Being able to place it anywhere within the function can simplify the function code significantly.

Consider a function that calculates the square root of a number. It expects a positive number, we leave imaginary numbers out of the picture. The algorithm to calculate the square root will fail with unexpected results. We have two options to make our code robust. One is to put as many checks as possible to avoid nasty behavior, or check the number passed and if it is negative return some predefined error value, and if it is positive process with simpler code since we operate on valid data. Here is some fiction code that implements square root:

double  calc_sqrt(double num) {
    if (num < 0)  // no square root for negative 
        return 0; // stop processing and return
    double sq_rt = 0;
    // do the algorithm to calculate square root
    // store it in sq_rt, and return it
    return sq_rt;
}

Summary

The C++ Programming Language