Maybe you've seen a tee shirt like this

This is what is called a flow-chart, a tool we don't use anymore. It traces the flow of control through a program while it is running, which is a fancy way of saying where the program goes after it finishes a step.

The rectangles are process steps and the diamonds are decision boxes, representing tests that have a yes-no (true-false) answer.

Programs actually used to be designed like this once. The problem is, they were hard to build and harder to debug.

The lines are too uncontrolled, they go all over the place, like a plate of spaghetti. In fact, this style of coding came to be called spaghetti code.

Its hard to build spaghetti code in modern programming languages because flow of control (where you go next) is very carefully structured.

Really, only two types of control are allowed—decisions and loops. In this topic we study if statements, the primary decision mechanism. Then we'll look at loops in the next topic.

If statements are used to switch the flow of control between alternate paths. There are two forms.

The If Statement

Here is a simple if statement.

if (x > 0) cout << "x is positive.\n";

if is a keyword. The expression inside the parentheses is often called a condition. The condition must

  1. appear inside parentheses
  2. evaluate to a bool (or something that can be converted to a bool).

The single statement after the condition is known as the then clause. When the program runs, the if statement executes by evaluating the condition and, only if it is true, executing the then clause. If the condition is false the then clause is skipped over.

Here are some equivalent ways of writing the same statement. Unless the then clause is very short, it is usually better style to put it on the line following the if, indented to the right (most code editors will do the indent automatically).

if (x > 0)
   cout << "x is positive.\n";

It is also possible to put multiple statements in a then clause by forming a compound statement by using the block operators { and }, like so

if (x > 0) {
   cout << "x is positive.";
   cout << endl;
}

Now both statement in the block are treated as if they were a unit by the if statement. If the condition is true, the entire block is executed, if it is false, the entire block is skipped.

The control flow looks like this.

The b in the decision block represents the condition in the if statement

The then clause represents either a single statement or a block of statements.

If b is true the then clause is executed

If it is false, it is bypassed

In either case, the flows of control come back together and the same following statement is executed. If you look at our tee shirt you'll see that's not the case.

Let's use the if-then statement to determine the letter grade that should be assigned to a particular numeric mark.


Code Notes

1. We don't bother to initialize grade because we know that data will be entered into it before it will be used.

2. When checking whether a mark is between boundaries, the perfectly well-formed mathematical condition 50<= mark < 55 is not legal as a C++ expression and must be reformulated as two separate boolean conditions joined by an and

if (mark >= 50 && mark < 55 )

3. Note the use of the if statement in main to determine whether to say "a B" or "an F"


Here's how our program looks diagrammatically.

Competent C++ programmers would complain about the program.

The larger decision diamonds were made bigger to hold the bigger expressions

They demonstrate graphically that each of these require two tests to be made

Note that a lot of the tests are redundant

if the result of mark>=80 is false then we know it must be <80. Why retest it?


 

The if-else Statement

It is often useful to pick between two alternatives.

if (x >= 0)
   cout << "x is positive.\n";
else
   cout << "x is negative.\n";

Again, we can use { and } to create compund then and else clauses.

if (x >= 0){
   cout << "x is positive.";
   cout << endl;
} else {
   cout << "x is negative.";
   cout << endl;
}

Of course it would be more efficient to write the second version this way (and in doing so show another variation)

if (x >= 0){
   cout << "x is positive.";
} else {
   cout << "x is negative.";
}
cout << endl;

Since cout <<endl; is common to both clauses, it has been moved to after the if statement where it will be executed no matter which clause is chosen. Notice, however, the block operators were left in even though there is only one statement in each clause. This is perfectly acceptable and many programmers will always use { and } with then and else clauses even when they are unnecessary. You may do it either way, but must recognize both ways as being valid.

Here's the control flow:

if the logical expression b is true the then clause is executed

if it's false the else clause is executed

Let's see how we can use the if-then-else form of the if statement to improve our first example.

Here, by using the else clause, and by nesting each subsequent if statement in its preceding statement's else clause, we have eliminated the double testing

The strategy is actually very simple. In essence what we're doing is

if the mark is greater than or equal to 80

then the grade is an A

otherwise go on to further testing

Here's what the control flow looks like

If you look back at our first example you will see we used five decision blocks, three of which were "big" ones—i.e., contained two separate tests

That's a total of eight tests!

Here we only use four, single-test, decision blocks

Before we tested mark to see if it was <48, then turned around and tested it to see if it was >=48

Here we use else clause to take advantage of the fact that if it is not <48, it must be >=48


But haven't we generated spaghetti code? Not really. The flow of control is highly constrained and so very orderly

In this diagram the hatched area is actually the else clause of the first if statement.

All the rest of the if statements are wholly embedded within that else clause.

The power of control structures really stems from our ability to nest them, one inside the other, to build almost arbitrarily complex structures.

Of course, we don't want arbitrarily complex, just complex enough to do the job (and not one jot more).

One of the most basic design skills good programmers have is an ability to get their control structures right.


A Complicated Example

Let's look at the problem of computing the roots of a quadratic equation. We'd like to create a function to do that but we have a problem. Quadratic equations have two roots and so far we have only learned how to return one value from a function.

OK. Let's calculate the maximum root. That still leaves a problem. The roots could be complex and how do we deal with that. Again, we have no way to do so yet.

Problem Statement

Write a function to calculate the largest root of the standard quadratic equation, ax2 + bx + c, given that the roots are real (that is that b2 >= 4ac)

Let's break this down into steps

  1. answer = -b
  2. if b2-4ac > 0 answer = answer + sqrt(b2-4ac)
  3. answer = answer/2a

This might look a little strange but I think like a computer programmer. I've computed a partial answer, then tested the expression under the radical

If it's positive I calculate its square root then add that to the answer.

If its negative my client has violated the condition of the design contract (the precondition). I don't want to call sqrt with a negative number because it will generate an error, so I just leave things as they are. The answer is wrong, but that's ok. If the contract is violated, I am under no obligation to provide the correct answer.

Note that if it is zero there's no point in invoking the sqrt function because I know sqrt(0) is 0.

Whichever is the case, I divide the answer by 2a and that should do it.

Or does it? There's an error. Can you spot it?

What happens if a is 0. Dividing by 0 is going to cause problems. There's nothing in our preconditions that says that is illegal so we have to deal with it.

What we need is something like this

if a is 0

if b is 0 equation is invalid

else answer = -c/b

else

answer = -b

if b2-4ac > 0 answer = answer + sqrt(b2-4ac)

answer = answer/2a

The above, which is partly written in English, is called pseudo-code. It utilizes the if-then-else structure to allow us to plan out our algorithm without having to fuss about coding details. It is also language independent as virtually all computer languages have an if-then-else in some form.

As this example shows, getting the right program structure can be tricky, so its nice to be able to do it in pseudo-code before investing a lot of time in real code.

Here is the real code

Note that we've slipped something new into the code. Up to now we've said that the then clause and if clause consisted of a single statement. Sometimes, however, we want to put more than one statement into the clause (which is why we called it a clause).

Statement Blocks

Again, w e can always replace a single statement in a clause with a block of statements by simply enclosing them in block operators. Thus the last else clause consists of three statements enclosed in block operators.


	else {
        answer = -b;
        double rad = b*b-4*a*c;
        if (rad > 0)
           answer += sqrt(rad);
        answer /= 2*a;
    } 

It might look to you as if there are four statements but in fact the line answer += sqrt(rad); is actually part of the preceding if statement.

Style Issues

Placement of the {

There are two schools of thought on where to place the { (open block) operator


else {
    answer = -b;
    double rad = b*b-4*a*c;
    if (rad > 0)
        answer += sqrt(rad);
    answer /= 2*a;
} 
or

else
{
    answer = -b;
    double rad = b*b-4*a*c;
    if (rad > 0)
        answer += sqrt(rad);
    answer /= 2*a;
} 

The second one makes it easy to line up the } with its own {.

The first one is more compact, taking one less line vertically.

We accept either in this course but you must be consistent. Whichever one you decide on use it always. Again, you must also be able to recognize either style as valid code, because other people will make style decisions different from yours.

Other aspects are consistent.

  1. indent statements inside the block (most program editors try to automate this anyway—set the indent to 4 spaces)
  2. Don't indent the }. It should line up with the start of its own if (or else if it terminates an else clause).

To Block or Not to Block

The code above our else clause is

	if (a == 0.0)
		if (b == 0.0)
			answer = -1.0;
		else
			answer = -c/a;
  

This could equally well have been written

	if (a == 0.0){
		if (b == 0.0){
			answer = -1.0;
       }
		else {
			answer = -c/a;
       }
 } else {
     etc....

Some programming shops routinely insist upon the second—that is always using block operators even on single statements. Others prefer the second as being more compact. We will accept either.

It is also acceptable to write an entire if statement on a single line:

if (b == 0.0) answer = -1.0;

or even

if (b == 0.0){ answer = -1.0;}

The else if Construct

Remember this fragment of code taken from our second example?

All if statements subsequent to the first one are wholly embedded in the preceding else clause. In fact, as we showed above, they are the else clause.

Some languages actually have a special elseif keyword.

There's no need of that, but many experienced programmers would simulate that as below.

Because of the regularity of this structure, the normal indenting has been supressed. This is unusual and should only be done in a long sequence of else if

Exercises

  1. Given a complex number (Re, Im), we would print Re if Im were zero; otherwise, we would only print it if it were not zero. Write a function to tell us whether we should print it.
  2. Given three integers, A, B & C we would like to know which one is the middle value. Write a function to return the middle value. In the event two or more are the same, return one of the equal values.
  3. Oh my, Poppy has a problem! The government has fouled up his pension check but still wants his taxes, so he needs to borrow money from one of his children. Delilah, his oldest, is always good for up to $500, no matter how often he asks. George, his second child will go up to $1000 while Tracy, his youngest, will stretch to $2000; however, George and Tracy will only do it once in a while so he doesn't like to ask them unless he has to. Given Poppy owes D dollars, where D is between $0 and $2000, he wants a function to tell him who to ask. It should return 1 for his oldest, 2 for his middle kid and 3 for the youngest, and 0 if he shouldn't ask at all.

Solutions

bool shouldPrintRe(double re, double im){
// Note: we don't need else here because if any
// condition is true, we return from the function.
// Only get to the nest test if previos one was false.
    if (im == 0.) return true;
    if (re == 0.) return false;
    return true; 
}
int whoToAsk(double amount){
    if (amount <= 0.) return 0;
	// To get here poppy actually owes some tax
    if (amount <= 500.) return 1; // ask Delilah
 // Poppy owes more than $500
    if (amount <= 1000.) return 2; // ask George
 // More than $1000!
    return 3; // ask Tracy
}

Examples Shown in Full