Module 4: Conditional control flow

  • what is control flow?
  • what are the syntax and semantics of an if statement?
  • how is a condition evaluated?

Flowcharts and control flow

So far in this course, we have been writing functions that behave like the code snippet below: first one statement is executed, then the next, then the next, etc., until we reach either a return statement or the end of the function. This is an example of sequential control flow: things happening one after another in a sequence through the course of the program. We can depict this straightforward control flow as C++ code, as shown below and to the left, or as a flowchart, shown below and to the right.

Flowchart of sequential statement execution
double speed(double distance, double time)
{
	distance = distance / 1000;  // convert meters to kms.
	time = time / 3600;          // convert secs. to hrs.
	return distance/time;
}

These representations both show how our simple function is purely sequential: there is an uninterrupted flow from the top of the function to the bottom with no interruptions or diversions. This sort of control flow allows us to write functions to perform mathematical calculations or some other simple tasks, but those functions are a bit boring. Important, but definitely not exciting.

Flowchart of promotion decisions in Term 3

Most of the power of algorithms and programming comes from the ability to represent more interesting control flow. We do this in two ways:

  • we can choose to execute one bit of code or another, based on some condition (conditional control flow) and
  • we can execute a bit of code over and over until a condition is met (looping).

Flowcharts are a natural way of representing control flow in programming. A flowchart uses rectangles to represent process steps and diamond shapes to represent decisions: arrows flow in when a decision needs to be taken and flow out based on the possible outcomes of that decision. For example, the flowchart to the right depicts an algorithm that should be of great interest to you: the (simplified) algorithm for determining a student’s promotion from Term 3 of an Engineering program. As we trace our way through the flowchart from “Complete term” to “Pass”, we encounter decisions that must be made (yellow diamonds) that determine where control will flow next based on a condition (true or false value).

In programming languages, conditional control flow is represented by the if statement (the subject of this module) and by loops (the subjects of future modules).

The if statement

In almost all programming languages, the if statement is used to choose between two possible paths of control flow, like decision boxes in a flowchart. At its very simplest, a C++ if statement can allow us to choose whether or not to execute one statement. Here is an example of just such a statement within a larger block of code:

Flowchart representation of simple single-statement if statement
double x = getNumberFromUser();

if (isnan(x))
{
	cout << "the user did not enter a number!\n";
}

double y = 3 * x + 1;

We can also choose between two entirely different control-flow paths, as in this example:

Flowchart representation of a simple if-else statement
double x = getNumberFromUser();

if (isnan(x))
{
	cout << "the user did not enter a number!\n";
}
else
{
	cout << "the user entered: " << x << endl;
}

double y = 3 * x + 1;

We can also have multiple statements in either the “then” block or the “else” block:

Flowchart representation of an if statement with multi-statement blocks
double x = getNumberFromUser();

if (isnan(x))
{
	cout << "the user did not enter a number!\n";
	x = 0.0;
}
else
{
	cout << "the user entered: " << x << endl;
}

double y = 3 * x + 1;

As is often the case in programming, we need to consider the if statement from the perspectives of both syntax and semantics.

Syntax

Syntactically, the example above shows us three key elements of an if statement:

  1. the keyword if (and sometimes else),
  2. a condition in parentheses and
  3. a block of code (a substatement) to be executed if the condition is true (or for the else substatement, if the condition is false).

Condition

The condition is an expression that can evaluate to the Boolean values true or false. In the example above, the condition is isnan(x): we call the isnan function (which tells us whether a floating-point number is the “not a number” value), passing x in as an argument, and whatever value is returned is what we use to decide which control-flow path to take.

The condition can be any expression that evaluates to true or false. For now, we’ll use explicit Boolean values (as in the example above, since isnan() returns a value of type bool) and comparison operators like < (less than), > (greater than) and == (equality).

Note: we have previously seen the single equals sign = used to indicate assignment rather than *equality. If we want to check whether or not two values are equal, we use a double-equals sign ==. Neither of these symbols means the same thing as mathematical equality (“these things are equal”): one means, “assign this to that” and the other means, “are these two things equal?”

We will come back to conditions a little later when we talk about logical expressions.

Substatement(s)

After an if statement’s condition, there is a block of statements called the first substatement. If the if statement also contains an else keyword, there will be a second substatement as well. These substatements are blocks of code that can contain multiple statements to be executed if the condition is (or is not) true. These substatements are usually indented by one more level than the surrounding code, making it easy to identify which statements are logically related to each other. For example, compare the following:

if (x > 0)
{
cout << "x is positive." << endl;
if (z > 0)
{
z = 2 * x + y;
}
else
{
y = 2 * x - z;
}
}
else
{
cout << "x is negative." << endl;
if (z > 0)
{
z = -2 * x + y;
}
else
{
y = -2 * x - z;
}
}
if (x > 0)
{
    cout << "x is positive." << endl;
    if (z > 0)
    {
        z = 2 * x + y;
    }
    else
    {
        y = 2 * x - z;
    }
}
else
{
    cout << "x is negative." << endl;
    if (z > 0)
    {
        z = -2 * x + y;
    }
    else
    {
        y = -2 * x - z;
    }
}

Which is easier to read and understand?

Aside

If there is only one statement in a substatement, the braces around that substatement can be omitted:

if (isnan(x))
	cout << "the user did not enter a number!\n";

However, this is not recommended. It is all too easy to inadvertently change the above to something like:

if (isnan(x))
	cout << "the user did not enter a number!\n";
	x = 0.0;

in which we meant for both statements to be executed only if isnan(x) returns true, but that’s not happens! People reading this code see, because of the logic of the program and the indenting, that x = 0.0 is only supposed to execute if x is nan. However, the compiler doesn’t look at indenting, so it sees the x = 0.0 statement as outside the if statement. The net effect is that x will always be re-assigned the value 0.0, no matter what the user originally entered! This kind of mistake is very easy to make — if you don’t believe me, go ask Apple’s security team (and see section 3.3 of the linked article!). To avoid this sort of mistake, I suggest that you always put braces around substatements.

Semantics

Flowchart representation of a simple if-else statement

The semantics of an if statement are actually quite simple. When we encounter an if statement, the condition is evaluated to a value of true or false. If it is true, the first substatement is executed. Otherwise, the second substatement is executed (if there is one). After that, we move on to the next statement to execute.

Application

Quadratic roots

Remember the accelTime function from assignment 1? It relied on the quadratic formula as applied to the kinematic equation:

$$ \begin{align} a x^2 + b x + c = 0 & & \frac{1}{2}a t^2 + vt = d \end{align} $$

$$ \begin{align} \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} & & \frac{-v \pm \sqrt{v^2 + 2ad}}{a} \end{align} $$

To find the larger of these two roots when $v^2 \geq 2ad$, my solution used the following function definition:

double accelTime(double a, double v, double d)
{
	// NOTE: we don't handle the case of a=0 just yet... we ought to make
	//       this a precondition
	return (-v + sqrt(v*v + 2*a*d)) / a;
}

Note the problem here. If I asked this function to calculate the time required to travel 1m when travelling at 1m/s with an acceleration of 0, it doesn’t return 1s but rather nan! This is because the return statement used an expression that divides by a, but a could be zero if I didn’t say so otherwise in the function contract. Now that we know about conditional flow control, however, we can do something much better:

double accelTime(double a, double v, double d)
{
	if (a == 0)
	{
		return d / v;
	}
	else
	{
		return (-v + sqrt(v*v + 2*a*d)) / a;
	}
}

Marks to letter grades

Given that letter grades at Memorial are assigned according to this chart in the Calendar, let’s implement the following function:

/**
 * Convert a numeric markinto a letter grade such as `A` or `B`.
 *
 * @param   mark       a number between 0 and 100 (inclusive)
 *
 * @returns `A`, `B`, `C`, `D` or `F`
 */
char letterGrade(int mark);

(this will be done in class)

Style issues

The else-if construct

In the previous application example, we ended up with a structure of an if inside an else of an if inside an else of an if inside a … you get the idea. This occurs often enough that there is a common stylistic improvement to be made: when we have a second substatement (a.k.a., an “else clause”) that contains nothing but another if statement, we can collapse the braces together. The change looks like this:

if (condition1)
{
    // ...
}
else
{
    if (condition2)
    {
        // ...
    }
    else
    {
        if (condition3)
        {
            // ...
        }
        else
        {
            if (condition4)
            {
                // ...
            }
            else
            {
                // ...
            }
        }
    }
}
if (condition1)
{
    // ...
}
else if (condition2)
{
    // ...
}
else if (condition3)
{
    // ...
}
else if (condition4)
{
    // ...
}
else
{
    // ...
}

The code example on the right has exactly the same meaning as the one on the left, one, but it is clearer and easier to read.

Placement of the {

There are two primary schools of thought on where to place the { (open block) symbol. The first style is derived from historic C code as written by Brian Kernighan and Dennis Ritchie. Ritchie created the C programming language, so he has a certain amount of programming street cred. K&R wrote if statements and then put the opening brace on the same line, like this:

if (condition) {
	statement1;
	statement2;
}

Another style was popularized by Eric Allman, another writer of historically-important C code and also a nice guy. He popularized the Allman style (although someone else named it after him later), which puts the opening brace on the line after the if keyword:

if (condition)
{
	statement1;
	statement2;
}

I personally prefer this style for two reasons: it makes it easier to find things that are related to each other (if keyword, opening brace and closing brace) and it innoculates against certain kinds of accidental syntax error (e.g., commenting out a condition and also losing the opening brace). This is, therefore, the style I use in my code examples. However, as in all matters of style, which style you choose is less important than being consistent. When you complete assignments, work on examples and (for some of you) eventually write code for money, pick a style (or accept what your employer has chosen for you) and stick with it.

Exercises

Top song

Write a function with one parameter, a year from 1958 onwards, that prints out the top song of that year’s decade. You may find this list helpful, but of course the important thing isn’t which song you choose but that you practice writing code!

The middle number

Given three integers, x, y and z, we would like to know which one is the middle value. Write a function to return the middle value. In the event that two or more are the same, return one of the equal values.

Marks to letter grades

After a day or two, try re-implementing the letterGrade function demonstrated in lecture by yourself:

/**
 * Convert a numeric markinto a letter grade such as `A` or `B`.
 *
 * @param   mark       a number between 0 and 100 (inclusive)
 *
 * @returns `A`, `B`, `C`, `D` or `F`
 */
char letterGrade(int mark);

Letter comparison

Write a function that will compare two characters (char parameters) in a case-insensitive way. For example, sameLetter('A', 'A') should evaluate to true, but so should sameLetter('A', 'a').

Hint:
recall that a char is an integer value used to represent a character according to a standard coding scheme, so you can perform arithmetic operations on characters (e.g., 'Q' - 'A')
License: CC BY-NC-SA

(c) 2009–2016 Michael Bruce-Lockhart, Theo Norvell, Dennis Peters and Jonathan Anderson. Licensed under a Creative Commons Attribution–Noncommercial–Share-Alike 2.5 Canada License. Permissions beyond the scope of this license may be available at theteachingmachine.org.