Show 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 examplewhile (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-exerciseNB: if you have problems with this see the min-exercise in the section "Debugging loops".
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 functionWe 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 codeHere the increment is the line exponent = exponent - 1;
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:
Check how a loop startsWhen 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 outIf 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:
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,
A complete programHere 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 codeThe result is: 1.414000 to the power 4 is 3.997584The 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 codeAlthough this works, it's rather crude and confusing. Fortunately there is a better way. A common patternIf we look at the parts of the above program that control the execution of the loop we see the following:
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)
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 codeHere 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.
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:
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 yrunning_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"
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 codeHere 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.
The Initialiser of a for() loop can declare a temporary variable which exists only as long as the loop runs.
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 statementThe 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 codeOther 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 codeThe 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. SummaryThe text of each key point is a link to the place in the web page.
|