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.
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
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 The If If it is 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.
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?
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.
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
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).
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.
{
There are two schools of thought on where to place the {
(open block) operator
orelse { answer = -b; double rad = b*b-4*a*c; if (rad > 0) answer += sqrt(rad); answer /= 2*a; }
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.
}
. It should line up with the start of its own if
(or
else
if it terminates an else clause). 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;}
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
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 }