Thus far, we have discussed writing programs that communicate with the user and can do some basic math. In order to make our programs useful and interesting, they need to be able to make decisions. In this section we will discuss control flow which is how a program can alter which statements get executed and the order in which statements get executed based on specific conditions. Up until now, every program we have written has gone straight from top to bottom; this need not be the case.
As we discuss control flow, we will see that only two of these
control flow statements are actually needed. However, trying to write an
entire program with just if
and goto
is not
recommended. C provides a variety of different control flow structures
for different purposes. Often, you will find that there are several
different ways you could implement a particular piece of programming
logic. When deciding how to implement something there are a few things
you should consider:
Is it simple? The simplest solution will often be the fastest and the least prone to errors.
Is it easy to understand? If not, you are liable to make errors implementing it the first time and it will be harder to understand later.
Does it make conceptual sense? Many of the different control flow structures have a particular conceptual idea behind them. Trying to use control-flow structures in a way that is conceptually consistent is like good notation: it is easier to understand because it hints at what is really going on.
This simplest control structure in any programming language is the
if
statement. In the following piece of code, the
printf
statement only gets executed if the value of
x
is negative:
if (x < 0) {
printf("x = %d is negative!\n", x);
}
Notice the braces. When the computer encounters the if
statement, it first evaluates the expression in the parentheses, called
the condition. If the condition is nonzero, it executes
the code in the block associated with the if statement.
We can also make if statements more complex. Any if statement can be
followed by any number of else if
blocks and a possible
final else
block. For example, if we really wanted to check
x
thoroughly, we could write:
printf("x = %d", x);
if (x < 0) {
printf(" is negative!\n");
} else if (x > 0) {
printf(" is positive!\n");
} else {
printf(" is zero.);
}
First, regardless of the value of x
, it gets printed to
the screen. Then, if x
is negative, printf
is
called to complete the sentence with ” is negative!“. If the
x < 0
condition fails, it goes on to check the condition
x > 0
. If this condition succeeds, it makes a different
call to printf
. Finally, if neither x < 0
or x > 0
succeeds, it calls printf
with
is zero.
.
In an multi-part if statement like this, the conditions are evaluated
from top to bottom. If any if
(or else if
)
succeeds, the conditions for following else if
statements
are not even evaluated and the program skips to the bottom of the
combined if statement. An if statement does not need a terminating
else
block but it can have at most one and it must be the
final condition.
Another major type of control flow statement is a
loop. A loop is a block of code that runs multiple
times. Most programming languages, C included, have 3 basic types of
loops: while
, do-while
, and for
.
We’ll start with the while loop.
Consider the following code:
int i;
i = 0;
while (i < 10) {
printf("%d\n", i);
i = i + 1;
}
The while statement is like an if
statement only it
executes the statements in its associated block repeatedly until the
condition fails. In the above code, the variable i
starts
with a value of 0. Each time the loop runs, the value of i
is printed to the screen and then incremented by 1. The loop runs as
long the statement i < 10
holds true. On the 10th time
through the loop, the value of i
finally reaches 10 and the
loop does not execute an 11th time.
The loop above runs exactly 10 times every time it’s executed.
Usually while loops are used when you don’t know how many times the loop
will need to run. For example, if you were approximating an integral,
you might want to run the loop until the error is below a certain
threshold. In that case error < 0.001
might be a
realistic loop condition.
The do-while loop is basically the same as the while loop except that it checks the condition after the loop has been executed. This means that the statements in the do-while block will always get executed at least once regardless of the condition. The syntax for a do-while statement in C looks like this:
do {
/* Approximate the integral */
} while (error > 0.001);
The third type of loop is the for loop. The for loop, conceptually, is for looping through a set of things. For example, you could loop through all the numbers between 0 and 9 like we did above, or loop through some sort of a list and do something with each item. A for loop in C is of the form:
for (<expression>; <condition>; <expression>)
{
/* Loop Block */
}
Instead of simply having a single condition like a while loop, a for loop has two additional expressions. The first of expression is an initialization; it gets executed once before the loop begins. The second expression is a condition just like in a while loop. The third expression gets evaluated after everything in the block but before the condition. This allows you to make our first loop example much more compact:
int i;
for (i = 0; i < 10; i = i + 1) {
printf("%d\n", i);
}
All of the expressions in a for loop are optional. If no condition is given, it simply loops forever or until you manually break out of it.
C has four statements called jump statements. Jump
statements allow your code to jump from one place to another in its
execution. The first of these statements is the return
statement. We have seen return
before and will see it again
when we talk more about functions. Simply put, the return
statement causes your program to leave the current function without
executing any more statements.
For working with loops, C provides the break
and
continue
statements. The break
statement
causes the program to leave the current loop. Usually this is used if
you want to leave a loop early; for example, you could break out of a
for loop before it gets to the end. Suppose you wanted to add up all of
the numbers between 1 and 100 but stop if it gets above 150. You could
do that with a simple break
statement:
int i;
int sum;
sum = 0;
for (i = 1; i <= 100; i = i + 1) {
sum += i;
if (sum > 150) {
break;
}
}
You could also implement basically the same thing by changing the
condition to i <= 100 && sum <= 150
. Using
the break statement allows the for loop to conceptually mean “for every
i between 1 and 100” makes the sum being above 150 a conceptual “stop
condition”.
The continue
statement causes your program to skip the
rest of the statements in the loop block and go on to the next
iteration. The following code uses a continue
statement to
add up all of the numbers between 1 and 100 that are not divisible by
3:
int i;
int sum;
sum = 0;
for (i = 1; i <= 100; i = i + 1) {
/* If the number is divisible by 3, we don't care about it */
if (i % 3 == 0) {
continue;
}
/* This statement only gets executed for values of i that are not divisible by 3*/
sum += i;
}
The final jump statement is goto
. The goto
statement allows you to jump to an arbitrary location in your function.
Later in this course, we will see some good uses for goto
.
For now, I suggest you leave it alone as it usually just makes code a
mess. Whenever you are tempted to just use a goto
statement, you should probably ask yourself if there is a better way to
do it; there probably is.
switch
statementOne of the more strange control flow statements in C is
switch
. The switch
statement is best explained
by an example:
char unit;
float temperature, celsius;
switch(unit) {
case 'C':
celsius = temperature;
break;
case 'F':
celsius = (temperature - 32) * 5 / 9;
break;
case 'K':
celsius = temperature - 273.15;
break;
default:
celsius = 0;
/* This is an error */
break;
}
Instead of a condition, the switch
statement takes an
integer value. It then has a bunch of cases each labeled by a constant.
If the value given to switch
matches one of those cases, it
goes to that case. If the value given to switch
doesn’t
match any of the cases, it goes to default
. Technically,
the default
case is optional; if there is no default case,
it simply goes to the end of the switch statement in unmatched
cases.
Take note of the break
statements. Once the program
jumps to a case in the switch statement, it will continue to execute
statements until it reaches a break
. If you do not put a
break
as the last statement to a case, it will continue on
and execute the statements in the next case. Sometimes this is very
useful, but most of the time you should simply put a break
at the end of each case.
Let’s end this discussion of control flow with a more advanced version of our temperature conversion program:
/* temperature.c: A temperature conversion program */
#include <stdio.h>
int main(int argc, char **argv)
{
char units;
float celsius;
float input_temperature;
int items_read;
printf("Welcome to our temperature conversion program!\n");
while (1) {
printf("Please enter a temperature with units (Example: 32 C):");
items_read = scanf("%f %c", &input_temperature, &units);
if (items_read != 2) {
/* There was an error reading the temperature */
break;
}
switch (units) {
case 'c':
case 'C':
celsius = input_temperature;
break;
case 'f':
case 'F':
celsius = (input_temperature - 32) * 5 / 9;
break;
case 'k':
case 'K':
celsius = input_temperature - 273.15;
break;
default:
printf("Invalid units.\n");
continue;
}
printf("That temperature is:\n");
printf(" %.2f degrees Celsius\n", celsius);
printf(" %.2f degrees Farenheight\n", (celsius * 9) / 5 + 32);
printf(" %.2f Kelvin\n", celsius + 273.15);
}
}