Loops: while(), for() and do .. while()
Comments and questions to John Rowe.
In the previous lecture we learnt about logical statements that
determine whether or not code gets run. Here we learn about loops which allow sections of code to run
zero or more times, with a controlling logical expression.
The while() loop has the
form:
while ( expression ) {
/* Do something... */
}
The contents of the loop, which as always may be a single
statement or a { ... } block, are executed
for as long as the controlling expression is true.
The while() loop repeats as long as the
condition is true (non-zero).
If the condition is false the body of the loop never executes
at all.
This may sound slightly unintuitive (if there's nothing to do,
why have a loop?) but when writing a program we often don't know
if there will always be something to loop over and it would be
really inconvenient to have to wrap every loop inside an if()
statement.
A trivial example
while (x > 1)
x = x / 2.0;
In this rather contrived example, x is halved every
time the loop is executed until at last the test condition is
false and the loop quits. If x had started with a
value of less than or equal to one the loop would not have
executed even once.
If the condition is false the body of the loop
never executes at all.
Mini-exercise
NB: if you have problems with this see the min-exercise
in the section "Debugging loops".
- Write a simple while() loop that reads in an integer.
- To demonstrate a while() loop.
- Create a new on-line program in a new window, with a suitable title and opening comment.
- Declare an int variable and initialise it to zero.
- Write a simple while() whose test is that the variable
is greater than or equal to zero and whose body:
- Prints a simple message asking for an integer
- Then has a call to
scanf() that reads the value of your variable from the keyboard.
(Of the form scanf("%d", &varname) )
- Note 1: as there is more than one statement you will need to use
{ and }
- Note 2: when you run a program that uses scanf()
from within the on-line compiler the console window will have
two sections: type your input into the lower,
darker section.
- Build & run, enter a few positive values into the lower part of
the console window, pressing <return> between each one.
- Now type a negative number and press <return>: your
program should finish.
- Now modify your program so that the initial value of your
variable is -1 (minus one). Do you think your program will ask you for
any input?
- Build & run. Check the output is correct..
In one sense while()
loops are a bit more complicated and subtle than they first
appear. Whilst our initial pseudo-example looks simple, it's
clear that the controlling expression can't go on being true
forever or else the loop would never stop. To
avoid accidental "infinite loops" that never stop
the loop
must do something to change the value of the controlling
expression. We shall refer to this as
the increment.
In the above example the increment is the sole statement inside
the loop x = x / 2.0; which
clearly affects the controlling expression.
Another example of an increment might be a
function call (covered in the next lecture) that returns a value
to say if the loop should continue, or a call to scanf()
(for example a loop that reads in an integer and loops until
it's within a certain allowed range of values).
Although it's very unusual, it's possible for the
controlling expression to contain its own increment although
whether this is wise is another matter altogether.
Understanding the increment is critical to
understanding the loop.
Example: a power function
We can use the while loop to write a "poor man's
power function" which calculates value to the power of exponent
where exponent is a positive integer. We show the core
loop of program first, with the complete program following
below:
double result, base = 2.01;
int exponent = 2;
result = 1.0;
while ( exponent > 0 ) {
result = result * base;
exponent = exponent - 1;
}
Step through this code
Here the increment is the line
exponent = exponent - 1;
- Step through the above "Key example".
- To observe the behavious of the increment.
- Step through the above "Key example" in a new window.
- Step through the code observing the change in
the value of exponent.
- Before each evaluation of the test expression
exponent > 0 try to predict whether
the loop will continue or not.
- If you don't get it right first time, refresh the page and
try again until you do.
Although most control expressions are fairly simple we are, of
course, allowed to use all the logical operators we can use with
the if() expression:
while (test1 && test2) {
// ...
}
while (test3 || test4) {
// ...
}
The word bug means an
error in the program and debugging refers to fixing problems
with the code.
Loops can easily go wrong with the most common problems being:
- Loops that never start.
- Loops that never finish.
- Loops that repeat the wrong number of times.
Check how a loop starts
When examining any loop it's good to ask
"what would happen if
the condition were false the very first time it was evaluated?".
In this case we are talking about exponent == 0
in which case the loop would never execute. Thus result
would be 1.0, the correct answer. If exponent == 1
the loop will execute once, making result equal to value,
again the correct answer.
Mentally check how a loop starts
Check how a loop increments and finishes.
As we mentioned above, if exponent == 1 the
loop will execute once, making result equal to value,
again the correct answer. If exponent == 2
the loop will execute twice, making result equal to value
squared, etc.
Mentally check how a loop increments and
finishes.
As we discussed in the previous section, the loop above
contains an "increment", i.e. a statement that does something
that will affect the test the next time round (in this case it's
actually a decrement, "increment" is just a generic term):
exponent = exponent - 1;
If in doubt, print it out
If we get the increment wrong the loop may never end, we will
have entered an "infinite loop". The program will start and just
never end or get on to the next statement. For example we have
have mistakenly written:
exponent = exponent + 1; // Wrong!
If this happens, an easy test is to put a printf()
statement inside the loop, such as:
printf("Exponent: %i result so far: %g\n", exponent, result);
Notice how we have printed out:
- The value of the controlling variable (exponent)
and the progress of
the loop (result).
- A description of what it is: simply printing out anonymous
numbers is extremely confusing.
If your program starts and seems to do nothing you
may have entered an infinite loop. Try putting a printf()
statement in the loop and see what happens.
Another extremely useful tactic is to print out the "input"
values at the start of the loop and the result at the end,
- Add some debugging output to your previous loop
- To demonstrate some debugging.
- After your call to scanf() in the loop add a
call to printf() to print out the value you have just
read in. Make sure the printf() statement also has some
helpful text so it's not just an anonymous number.
A complete program
Here is a short complete program incorporating this loop where
we have highlighted the test and the increment:
#include <stdio.h>
int main() {
double base = 1.414;
int exponent = 4;
double result;
printf("%f to the power %d is ", base, exponent);
result = 1.0;
while ( exponent > 0 ) {
result = result * base;
exponent = exponent - 1;
}
printf("%f\n", result);
return 0;
}
Step through this code
The result is:
1.414000 to the power 4 is 3.997584
The above example has the effect that the value of the original
variable "exponent" is lost (and we have somewhat
artificially written the code to handle this). Often however, we
need to preserve the original value and so we take a temporary
copy of it. This gives the following code, where we have
highlighted the temporary copy and
three lines that control how many times the loop
is executed:
#include <stdio.h>
int main() {
double base = 1.414;
int exponent = 4, tmpexp;
double result;
result = 1.0;
tmpexp = exponent;
while ( tmpexp > 0 ) {
result = result * base;
tmpexp = tmpexp - 1;
}
printf("%f to the power %d is %f\n", base, exponent, result);
return 0;
}
Step through this code
Although this works, it's rather crude and
confusing. Fortunately there is a better way.
A common pattern
If we look at the parts of the above program that control the
execution of the loop we see the following:
- Initialise: ( tmpexp =
exponent; )
- Loop:
- Test (tmpexp > 0
) and if false quit the loop.
- Execute the body of the calculation.
- Increment. ( tmpexp =
tmpexp - 1; )
In our case the loop goes:
Initialise .. test-loop-increment .. test-loop-increment ..
test-loop-increment .. test-loop-increment ..
test & quit
Notice that the initialisation happens only once for the whole
loop.
This Initialise, Test, Increment loop is so common that it has
a loop especially for it, the for() loop:
The for() loop rolls all
these three into one. It has the following format:
for (Initialise; Test; Increment ) {
... /* Loop body */
}
Note that the three expressions are separated by semicolons, not commas, and that they occur
in the same order as they are used.
for (Initialise; Test; Increment ) separated by semicolons, not commas
The steps in the for() loop are done in exactly the
same order as in the equivalent while() loop:
for(Initialise; Test; Increment)
- Initialise
- Loop:
- Test and if false quit the
loop.
- Execute the body.
- Increment.
Just as in the while() loop, if the for() loop test is initially false
the body never executes.
Using the for() loop the program now looks like this:
#include <stdio.h>
int main() {
double base = 1.414;
int exponent = 4, tmpexp;
double result;
result = 1.0;
for ( tmpexp = exponent; tmpexp > 0; tmpexp = tmpexp - 1 )
result = result * base;
printf("%f to the power %d is %f\n", base, exponent, result);
return 0;
}
Step through this code
Here we have chosen to removes the braces { } as
there is only one statement within the loop.
The for() loop could be written a little more
naturally as:
for (tmpexp=1; tmpexp <= exponent; tmpexp = tmpexp + 1)
In this case they both yield the same result, although in
general this is not always the case of course.
- Step through the above "Key example".
- To observe a for() loop.
- Step through the above "Key example" in a new window.
- Step through the program, particularly noting:
- The change in the value of tmpexp
- The successive application of the test
tmpexp > 0
- Check that you can successfully predict whether the test
will be true or false. If not, refresh the page and repeat
until you can.
In the loops we have written so far we have often had steps
which have the same variable on both sides of the equals sign
such as:
j = j -1;
tmpexp = tmpexp + 1
factorial = factorial * n;
In the wages calculation in the preface we had a
step:
- Add that day's pay to the running-total
This sort of phrase comes naturally to us and C provides a way
of doing it directly with the +=
operator:
running_total += days_pay;
which means "add the value of days_pay
to running_total". Notice there is no space between
the + and the =.
When we are at the supermarket the till is
constantly doing calculations of the type "add the cost of this
item to the current value of the bill".
Just about any operator can have "=" after it:
x += y; // add y to x
x -= y; // subtract y from x
x *= y; // multiply x by y
x /= y; // divide x by y
running_total += days_pay; means "add the
value of days_pay to running_total"
The code fragments:
j += 1;
k -= 1;
can be further abbreviated to:
++j; /* add 1 to j */
--k; /* subtract 1 from k */
These last two are used quite often
(particularly ++j) so try to use them whenever
possible.
++j; means "add 1 to j"
Warning: j++ vs. ++j
There are two slightly different forms of the "++"
operator. Both have the same effect when in a statement on
their own but are different when used in an expression, such
as an assignment.
The statement
k = ++j;
means "add one to j and then set set k to
the (new) value of j".
k = j++;
means "set k to the (old) value of j and
then add one to j".
Exactly the same principle applies to the "--"
operator.
Remember the mega-principle and avoid chances to
go wrong!
I suggest you don't use "++" inside
expressions: it just gives us the chance to go wrong.
Notice how we have taken the opportunity to use a
descriptive variable name for our array index.
The for() (and only the for()) has a
further ability: the initialiser can be replaced by a variable
declaration which has effect only in that loop, including the
loop body. We can therefore simplify the above example slightly:
#include <stdio.h>
int main() {
double base = 1.414;
int exponent = 4;
double result;
result = 1.0;
for ( int tmpexp = exponent; tmpexp > 0; --tmpexp )
result *= base;
printf("%f to the power %d is %f\n", base, exponent, result);
return 0;
}
Step through this code
Here we had no need to use tmpexp inside the body of
the for() loop, but we could have if we had wanted to.
See the extended prime number test
example.
- Step through the above "Key example".
- To see the two new features in action and to
cement our understanding of the for() loop.
- Step through the above "Key example" in a new window.
- Start the above code.
- Note how a new variable tmpexp springs into
existence (with the value 4) when the loop starts.
- Step through the code noting how both the control
variable tmpexp and result
change in value.
- Note how tmpexp disappears when the loop finishes.
- Make sure you understand the test-loop-increment
sequence and that you can correctly predict what the loop will
do next.
- As always, Refresh the page and go through it again until
you can confidently get it right.
The Initialiser of a for() loop can declare a temporary
variable which exists only as long as the loop runs.
- Write a simple for() loop.
- To practice writing a for() loop.
- Create a new on-line program in a new window, with a suitable title and opening comment.
- Write a simple for() loop to print out the squares
of the numbers zero to ten inclusive.
- Your code should have an increment that increases the value
of your control variable by one each time.
- Build & run. Check the output is correct..
- If your code doesn't work use the etchniques in the
"Debugging loops" section.
- Now convert your code so that it prints out the values
in reverse order (from ten squared down to zero squared) using
a negative increment that decreases the value
of your control variable by one each time.
The final, and least used, of C's loops, the do { ... } while();,
behaves in exactly the same way as the more widely used while() { ... }
loop except that the body of the loop is executed before the logical test, meaning that the
loop always executes at least once.
The basic format is:
do {
....
} while ( logical-test );
Notice the semi-colon after the while().
Example: successive halving.
The chances are you will never have to use this loop
in this module so don't worry too much about it.
In the last lecture we met the break statement which
breaks out of a switch() statement. The break
statement can also break out of a loop (but not an if()).
The break statement breaks
out of the innermost switch() statement or loop (but
not an if()).
The continue statement
The continue statement goes to the next
iteration of the innermost loop (but not an if()).
That is to say, for the while() and do { ... }
while(); loops the continue statement goes
straight to the next evaluation of the control statement and
quits the loop if it evaluates to zero. For a for() it
evaluates the increment and then the control statement and quits
the loop if that evaluates to zero.
The break statement gives us another way to quit a
loop and in some circumstances it may be most convenient to
deliberately create a loop whose control expression will never
be zero and to have a break statement inside it. The
following example continues until the user enters a negative
number.
#include <stdio.h>
#include <math.h>
int main() {
while ( 1 ) {
double x;
printf("Please enter a positive number, or negative to quit\n");
scanf("%lg", &x);
if ( x < 0 )
break;
printf("The square root of %g is %g\n", x, sqrt(x));
}
return 0;
}
Step through this code
Other popular ways to create an infinite loop include:
while ( 1 == 1 ) {
...
}
and
for( ;; ) {
...
}
Note we have still got the two semi-colons inside the for(;;).
This works because in a for() loop if the test is
absent it is assumed to be true. The apparent weakness of this
construction (it looks strange!) is also its strength: it look
so strange it's hard to mistake it for anything else.
In general we always want to check that input is valid before
proceeding. Even quitting with a helpful message is better
than carrying on with invalid data. For an interactive program
it's also nice to give the user another chance.
The previous example of
an infinite loop
with a break statement can easily be
adapted to provide a good way to do this, with
an error emssage that can be different for each possible mistake.
For example: suppose we wish to read in a minimum and maximum
possible mass subject to the reasonable conditions that both masses should
be positive and the maximum at least as great as the minimum.
Our code might look like this:
#include <stdio.h>
int main() {
double minMass, maxMass;
while(1 ) {
printf("Please enter the minimum and maximum mass\n");
scanf("%lg %lg", &minMass, &maxMass);
if ( minMass <= 0 || maxMass <= 0 )
printf("Sorry, the masses must be greater than zero\n");
else if ( minMass > maxMass )
printf("Sorry, the maximum must not be less than the minimum\n");
else
break;
}
printf("Mass range is %g to %g\n", minMass, maxMass);
}
Step through this code
The body of the loop reads in the data and then checks
for each possible error in turn, printing a helpful error message if either
error is true. If, and only if, all the error checks are false then
the else comes into effect which simply breaks out
of the loop. (We will expand on this later in the course to deal with
a few more obscure ways the user can go wrong entering numbers.)
The key is to notice that this loop cannot finish with invalid data.
The text of each key point is a link to the place in the web page.