Control flow statements

edit

Usually a program is not a linear sequence of instructions. It may repeat code or take decisions for a given path-goal relation. Most programming languages have control flow statements (constructs) which provide some sort of control structures that serve to specify order to what has to be done to perform our program that allow variations in this sequential order:

  • statements may only be obeyed under certain conditions (conditionals),
  • statements may be obeyed repeatedly under certain conditions (loops),
  • a group of remote statements may be obeyed (subroutines).
Logical Expressions as conditions
Logical expressions can use logical operators in loops and conditional statements as part of the conditions to be met.

Exceptional and unstructured control flow

edit

Some instructions have no particular structure but will have an exceptional usefulness in shaping how other control flow statements are structured, a special care must be taken to prevent unstructured and confusing programming.

break

edit

A break will force the exiting of the present loop iteration into the next statement outside of the loop. It has no usefulness outside of a loop structure except for the switch control statement.

continue

edit

The continue instruction is used inside loops where it will stop the current loop iteration, initiating the next one.

The goto keyword is discouraged as it makes it difficult to follow the program logic, this way inducing to errors. The goto statement causes the current thread of execution to jump to the specified label.

Syntax
label:
  statement(s);

goto label;

In some rare cases, the goto statement allows to write uncluttered code, for example, when handling multiple exit points leading to the cleanup code at a function exit (and neither exception handling or object destructors are better options). Except in those rare cases, the use of unconditional jumps is a frequent symptom of a complicated design, as the presence of many levels of nested statements.

In exceptional cases, like heavy optimization, a programmer may need more control over code behavior; a goto allows the programmer to specify that execution flow jumps directly and unconditionally to a desired label. A label is the name given to a label statement elsewhere in the function.

Note:
There is a classic paper in software engineering by W. A. Wulf called "A case against the GOTO", presented in the 25th ACM National Conference in October 1972, a time when the debate about goto statements was reaching its peak. In this paper Wulf defends that goto statements should be regarded as dangerous. Wulf is also known by one of his comments regarding efficiency: "More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason -- including blind stupidity.".

A goto can, for example, be used to break out of two nested loops. This example breaks after replacing the first encountered non-zero element with zero.

for (int i = 0; i < 30; ++i) {
  for (int j = 0; j < 30; ++j) {
    if (a[i][j] != 0) {
       a[i][j] = 0;
       goto done;
     }
  }
}
done:
/* rest of program */

Although simple, they quickly lead to illegible and unmaintainable code.

// snarled mess of gotos

int i = 0;
  goto test_it;
body:
  a[i++] = 0;
test_it:
  if (a[i]) 
    goto body;
/* rest of program */

is much less understandable than the equivalent:

for (int i = 0; a[i]; ++i) {
  a[i] = 0;
}
/* rest of program */

Gotos are typically used in functions where performance is critical or in the output of machine-generated code (like a parser generated by yacc.)

The goto statement should almost always be avoided, but there are rare cases where it enhances the readability of code. One such case is an "error section".

Example

#include <new>
#include <iostream>

...

int *my_allocated_1 = NULL;
char *my_allocated_2 = NULL, *my_allocated_3 = NULL;
my_allocated_1 = new (std::nothrow) int[500];

if (my_allocated_1 == NULL)
{  
  std::cerr << "error in allocated_1" << std::endl;
  goto error;
}

my_allocated_2 = new (std::nothrow) char[1000];

if (my_allocated_2 == NULL)
{  
  std::cerr << "error in allocated_2" << std::endl;
  goto error;
}
    
my_allocated_3 = new (std::nothrow) char[1000];

if (my_allocated_3 == NULL)
{  
  std::cerr << "error in allocated_3" <<std::endl;
  goto error;
}
return 0;
    
error:
  delete [] my_allocated_1;
  delete [] my_allocated_2;
  delete [] my_allocated_3;
  return 1;

This construct avoids hassling with the origin of the error and is cleaner than an equivalent construct with control structures. It is thus less error prone.

Note:
While the above example shows a reasonable use of gotos, it is uncommon in practice. Exceptions handle such cases in a clearer, more effective and more organized way. This will be discussed in "Exception Handling" in detail. Using RAII to manage resources such as memory also avoids the need for most of the explicit cleanup code that is shown above.


abort(), exit() and atexit()

edit

As we will see later the Standard C Library that is included in C++ also supplies some useful functions that can alter the flow control. Some will permit you to terminate the execution of a program, enabling you to set up a return value or initiate special tasks upon the termination request. You will have to jump ahead into the abort() - exit() - atexit() sections for more information.

Conditionals

edit

There is likely no meaningful program written in which a computer does not demonstrate basic decision-making skills based upon certain set conditions. It can actually be argued that there is no meaningful human activity in which no decision-making, instinctual or otherwise, takes place. For example, when driving a car and approaching a traffic light, one does not think, "I will continue driving through the intersection." Rather, one thinks, "I will stop if the light is red, go if the light is green, and if yellow go only if I am traveling at a certain speed a certain distance from the intersection." These kinds of processes can be simulated using conditionals.

A conditional is a statement that instructs the computer to execute a certain block of code or alter certain data only if a specific condition has been met.

The most common conditional is the if-else statement, with conditional expressions and switch-case statements typically used as more shorthanded methods.

if (Fork branching)

edit

The if-statement allows one possible path choice depending on the specified conditions.

Syntax

if (condition)
{
  statement;
}

Semantic

First, the condition is evaluated:

  • if condition is true, statement is executed before continuing with the body.
  • if condition is false, the program skips statement and continues with the rest of the program.

Note:
The condition in an if statement can be any code that resolves in any expression that will evaluate to either a boolean, or a null/non-null value; you can declare variables, nest statements, etc. This is true to other flow control conditionals (ie: while), but is generally regarded as bad style, since it only benefit is ease of typing by making the code less readable.

This characteristic can easily lead simple errors, like tipping a=b (assign a value) in place of a a==b (condition). This has resulted in the adoption of a coding practice that would automatically put the errors in evidence, by inverting the expression (or using constant variables) the compiler will generate an error.

Recent compilers support the detection of such events and generate compilation warnings.

Example

if(condition)
{
  int x; // Valid code
  for(x = 0; x < 10; ++x) // Also valid.
    {
      statement;
    }
}
 
flowchart from the example

Note:
If you wish to avoid typing std::cout, std::cin, or std::endl; all the time, you may include using namespace std at the beginning of your program since cout, cin, and endl are members of the std namespace.

Sometimes the program needs to choose one of two possible paths depending on a condition. For this we can use the if-else statement.

if (user_age < 18)
{
    std::cout << "People under the age of 18 are not allowed." << std::endl;
}
else
{
    std::cout << "Welcome to Caesar's Casino!" << std::endl;
}

Here we display a message if the user is under 18. Otherwise, we let the user in. The if part is executed only if 'user_age' is less than 18. In other cases (when 'user_age' is greater than or equal to 18), the else part is executed.

if conditional statements may be chained together to make for more complex condition branching. In this example we expand the previous example by also checking if the user is above 64 and display another message if so.

if (user_age < 18)
{
  std::cout << "People under the age of 18 are not allowed." << std::endl;
}
else if (user_age > 64)
{
  std::cout << "Welcome to Caesar's Casino! Senior Citizens get 50% off." << std::endl;
}
else
{
  std::cout << "Welcome to Caesar's Casino!" << std::endl;
}
 
flowchart from the example

Note:

  • break and continue do not have any relevance to an if or else.
  • Although you can use multiple else if statements, when handling many related conditions it is recommended that you use the switch statement, which we will be discussing next.

switch (Multiple branching)

edit

The switch statement branches based on specific integer values.

switch (integer expression) {
    case label1:
         statement(s)
         break;
    case label2:
         statement(s)
         break;
    /* ... */
    default:
         statement(s)
}

As you can see in the above scheme the case and default have a "break;" statement at the end of block. This expression will cause the program to exit from the switch, if break is not added the program will continue execute the code in other cases even when the integer expression is not equal to that case. This can be exploited in some cases as seen in the next example.

We want to separate an input from digit to other characters.

 char ch = cin.get(); //get the character
 switch (ch) {
     case '0': 
          // do nothing fall into case 1
     case '1': 
         // do nothing fall into case 2
     case '2': 
        // do nothing fall into case 3
     /* ... */
     case '8': 
        // do nothing fall into case 9
     case '9':  
          std::cout << "Digit" << endl; //print into stream out
          break;
     default:
          std::cout << "Non digit" << endl; //print into stream out
 }

In this small piece of code for each digit below '9' it will propagate through the cases until it will reach case '9' and print "digit".

If not it will go straight to the default case there it will print "Non digit"

Note:

  • Be sure to use break commands unless you want multiple conditions to have the same action. Otherwise, it will "fall through" to the next set of commands.
  • break can only break out of the innermost level. If for example you are inside a switch and need to break out of a enclosing for loop you might well consider adding a boolean as a flag, and check the flag after the switch block instead of the alternatives available. (Though even then, refactoring the code into a separate function and returning from that function might be cleaner depending on the situation, and with inline functions and/or smart compilers there need not be any runtime overhead from doing so.)
  • continue is not relevant to switch block. Calling continue within a switch block will lead to the "continue" of the loop which wraps the switch block.

Loops (iterations)

edit

A loop (also referred to as an iteration or repetition) is a sequence of statements which is specified once but which may be carried out several times in succession. The code "inside" the loop (the body of the loop) is obeyed a specified number of times, or once for each of a collection of items, or until some condition is met.

Iteration is the repetition of a process, typically within a computer program. Confusingly, it can be used both as a general term, synonymous with repetition, and to describe a specific form of repetition with a mutable state.

When used in the first sense, recursion is an example of iteration.

However, when used in the second (more restricted) sense, iteration describes the style of programming used in imperative programming languages. This contrasts with recursion, which has a more declarative approach.

Due to the nature of C++ there may lead to an even bigger problems when differentiating the use of the word, so to simplify things use "loops" to refer to simple recursions as described in this section and use iteration or iterator (the "one" that performs an iteration) to class iterator (or in relation to objects/classes) as used in the STL.

Infinite Loops

Sometimes it is desirable for a program to loop forever, or until an exceptional condition such as an error arises. For instance, an event-driven program may be intended to loop forever handling events as they occur, only stopping when the process is killed by the operator.

More often, an infinite loop is due to a programming error in a condition-controlled loop, wherein the loop condition is never changed within the loop.

// as we will see, these are infinite loops...
while (1) { }

// or

for (;;) { }


Note:
When the compiler optimizes the source code, all statement after the detected infinite loop (that will never run), will be ignored. A compiler warning is generally given on detecting such cases.

Condition-controlled loops

Most programming languages have constructions for repeating a loop until some condition changes.

Condition-controlled loops are divided into two categories Preconditional or Entry-Condition that place the test at the start of the loop, and Postconditional or Exit-Condition iteration that have the test at the end of the loop. In the former case the body may be skipped completely, while in the latter case the body is always executed at least once.

In the condition controlled loops, the keywords break and continue take significance. The break keyword causes an exit from the loop, proceeding with the rest of the program. The continue keyword terminates the current iteration of the loop, the loop proceeds to the next iteration.

while (Preconditional loop)

edit

Syntax

while (''condition'') ''statement''; ''statement2'';

Semantic First, the condition is evaluated:

  1. if condition is true, statement is executed and condition is evaluated again.
  2. if condition is false continues with statement2

Remark: statement can be a block of code { ... } with several instructions.

What makes 'while' statements different from the 'if' is the fact that once the body (referred to as statement above) is executed, it will go back to 'while' and check the condition again. If it is true, it is executed again. In fact, it will execute as many times as it has to until the expression is false.

Example 1

#include <iostream>
using namespace std;
 
int main() 
{
  int i=0;
  while (i<10) {
    cout << "The value of i is " << i << endl;
    i++;
  }
  cout << "The final value of i is : " << i << endl;
  return 0;
}

Execution

 The value of i is 0
 The value of i is 1
 The value of i is 2
 The value of i is 3
 The value of i is 4
 The value of i is 5
 The value of i is 6
 The value of i is 7
 The value of i is 8
 The value of i is 9
 The final value of i is 10

Example 2

// validation of an input
#include <iostream>
using namespace std;
 
int main() 
{
  int a;
  bool ok=false;
  while (!ok) {
    cout << "Type an integer from 0 to 20 : ";
    cin >> a;
    ok = ((a>=0) && (a<=20));
    if (!ok) cout << "ERROR - ";
  }
  return 0;
}

Execution

 Type an integer from 0 to 20 : 30
 ERROR - Type an integer from 0 to 20 : 40
 ERROR - Type an integer from 0 to 20 : -6
 ERROR - Type an integer from 0 to 20 : 14

do-while (Postconditional loop)

edit

Syntax

do {
  statement(s)
} while (condition);
 
statement2;

Semantic

  1. statement(s) are executed.
  2. condition is evaluated.
  3. if condition is true goes to 1).
  4. if condition is false continues with statement2

The do - while loop is similar in syntax and purpose to the while loop. The construct moves the test that continues condition of the loop to the end of the code block so that the code block is executed at least once before any evaluation.

Example

#include <iostream>

using namespace std;
 
int main() 
{
  int i=0;

  do {
    cout << "The value of i is " << i << endl;
    i++;
  } while (i<10);

  cout << "The final value of i is : " << i << endl;
  return 0;
}

Execution

The value of i is 0
The value of i is 1
The value of i is 2
The value of i is 3
The value of i is 4
The value of i is 5
The value of i is 6
The value of i is 7
The value of i is 8
The value of i is 9
The final value of i is 10

for (Preconditional and counter-controlled loop)

edit

The for keyword is used as special case of a pre-conditional loop that supports constructors for repeating a loop only a certain number of times in the form of a step-expression that can be tested and used to set a step size (the rate of change) by incrementing or decrementing it in each loop.

Syntax
for (initialization ; condition; step-expression)
  statement(s);

The for construct is a general looping mechanism consisting of 4 parts:

  1. . the initialization, which consists of 0 or more comma-delimited variable initialization statements
  2. . the test-condition, which is evaluated to determine if the execution of the for loop will continue
  3. . the increment, which consists of 0 or more comma-delimited statements that increment variables
  4. . and the statement-list, which consists of 0 or more statements that will be executed each time the loop is executed.

Note:
Variables declared and initialized in the loop initialization (or body) are only valid in the scope of the loop itself.

The for loop is equivalent to next while loop:

 initialization
 while( condition )
 {
   statement(s);
   step-expression;
 }


Note:

Each step of the loop (initialization, condition, and step-expression) can have more than one command, separated by a , (comma operator). initialization,condition, and step expression are all optional arguments. In C++ the comma is very rarely used as an operator. It is mostly used as a separator (ie. int x, y; ).

Example 1

// a unbounded loop structure
for (;;)
{
  statement(s);
  if( statement(s) )
    break;
}

Example 2

// calls doSomethingWith() for 0,1,2,..9
for (int i = 0; i != 10; ++i)
{                  
  doSomethingWith(i); 
}

can be rewritten as:

// calls doSomethingWith() for 0,1,2,..9
int i = 0;
while(i != 10)
{
  doSomethingWith(i);
  ++i;
}

The for loop is a very general construct, which can run unbounded loops (Example 1) and does not need to follow the rigid iteration model enforced by similarly named constructs in a number of more formal languages. C++ (just as modern C) allows variables (Example 2) to be declared in the initialization part of the for loop, and it is often considered good form to use that ability to declare objects only when they can be initialized, and to do so in the smallest scope possible. Essentially, the for and while loops are equivalent. Most for statements can also be rewritten as while statements.

In C++11, an additional form of the for loop was added. This loops over every element in a range (usually a string or container).

Syntax
for (variable-declaration : range-expression)
  statement(s);

Example 2

std::string s = "Hello, world";
for (char c : s) 
{
  std::cout << c << ' ';
}

will print

H e l l o ,   w o r l d

.