Lesson #8

Compound Conditions and for Loops

Overview

In this lesson I will cover the following topics

  1. Compound conditions using the &&, ||, and ! operators.

  2. The for loop.

  3. Off by one errors when looping.

Body

Compound conditions

In the last lesson I showed you how you could test a condition and use the result of that test to execute different parts of your program. Often you will want to create fairly complicated conditions. Sometimes you can use nested if statements to get the effect that you want. For example, supposed you wanted to print "Hello, World!" only if the value of x was 10 and the value of y was 20. You could do something like this

if (x == 10) {
  if (y == 20) {
    printf("Hello, World!\n");
  }
}

Here the body of the outer if statement executes only if x == 10 is true. What is in that body is another if statement. If y == 20 is also true then there is output. If either condition is false, the innermost printf gets skipped.

While this works, it is awkward and potentially hard to read. A much nicer option is to use the && operator. When you see &&, say "and".

if (x == 10 && y == 20) {
  printf("Hello, World!\n");
}

This says "if x equals 10 AND y equals 20 then print." Check the precedence chart. The condition above will only work if the precedence of == is higher than that of &&.

To go along with "and" there is also an "or" operator. Here is how that looks.

if (x == 10 || y == 20) {
  printf("Hello, World!\n");
}

Here the output is printed if either x == 10 is true OR y == 20 is true. If both are true the overall condition is still true. The || operator is called "inclusive" OR because of this. ("Exclusive" OR would be false if both conditions were true as in: "You can have cookies or you can have ice cream." The || operator in C does not work that way). Note that the || operator is typed with two vertical bars with no spaces between them. The vertical bar character is on the same key as the backslash character.

In addition to && and ||, C also has a NOT operator. For example

if (!(x == 10)) {
  printf("Hello, World!\n");
}

This causes "Hello, World" to be printed if it is not the case that x equals 10. Of course I could have written that as just

if (x != 10) {
  printf("Hello, World!\n");
}

and that would have been more straightforward. However, the ! operator is sometimes useful in complicated conditions.

Let me show you a more realistic example of how these operators might be used. Consider the problem of testing for leap years.

#include <stdio.h>

int main(void)
{
  int year;

  // Get the year from the user.
  printf("What year is it? ");
  scanf("%d", &year);

  // Is it a leap year? If so, print a message.
  if (year % 4 == 0) {
    printf("It's a leap year!\n");
  }

  return 0;
}

In this simple program I'm checking to see if the year is evenly divisible by four (no remainder after division). Such years are leap years. But wait! That rule isn't quite right. It turns out that years that are evenly divisible by 100 aren't leap years despite the first rule. Maybe I need to say

if (year % 4 == 0 && !(year % 100 == 0)) {
  printf("It's a leap year!\n");
}

This says, "if the year is evenly divisible by four AND it is NOT the case that the year is evenly divisible by 100 then print." I think using the ! operator here actually makes it easier to read the condition than switching the == to != would.

Actually this rule isn't quite right either. Years that are evenly divisible by 400 are leap years anyway, despite rule #2. In other words, a year is a leap year "if the year is evenly divisible by 4 AND NOT evenly divisible by 100 OR if the year is evenly divisible by 400."

if (year % 4 == 0 && !(year % 100 == 0) || year % 400 == 0) {
  printf("It's a leap year!\n");
}

I didn't put in any extra parentheses here to control which is done first, the && or the || operator. If you consult the precedence chart you will see that && is done first. However, this is an obscure fact that many programmers don't know. In a case like this I would just add some more parentheses to make the condition easier to understand.

if ((year % 4 == 0 && !(year % 100 == 0)) || year % 400 == 0) {
  printf("It's a leap year!\n");
}

It might be surprising to know that quite a few programs do not compute leap years properly.

Getting compound conditional statements right is tricky. Typically people depend on how it sounds when read in English. That works pretty well, but English is often ambiguous and that can cause problems. For example, the word "or" is used in both an inclusive and exclusive way. However, the || operator is only inclusive. I often make mistakes getting compound conditions right. As a result I am always extra careful when I write them to make sure they are correct. After a while you will get to know your own strengths and weaknesses and take them into account as you program.

I find it useful to know the following rules:

!(a && b)   is the same as   !a || !b
!(a || b)   is the same as   !a && !b

These are called "DeMorgan's Theorems" and they are very handy. If you think about them, they make perfect sense.

if (!(a == b && c == d)) { ...

if (a != b || c != d) { ...

The two conditions above are identical. The first says that it is not the case that both of the smaller conditions are true. In that case, one of the smaller conditions must be false. In other words either the first one is false or the second one is false (or maybe both are false). Now maybe you find my explaination confusing. Frankly, I do too. That's why I often make mistakes writing compound conditionals and why I find it handy to have an algebraic formula that I can use on them. On the other hand, if algegra is not your speed that's fine too. You can live without it in this case.

The for loop

Computers are dumb. But because they are dumb they don't mind doing the same thing over and over again. They never get bored and they never loose their concentration. This is good. That way we can get computers to do those repetative tasks that we would rather not do. Many programs exist exactly for this reason.

Consequently every programming language has a way of executing the same instructions over and over again. Such activity is called "looping". In C there are actually three different looping statements. We will look at the first and most important here. We will look at the other two in the next lesson.

Here is a program that prints out the numbers from 1 to 10.

#include <stdio.h>

int main(void)
{
  int i;

  // Print out the numbers from 1 to 10.
  for (i = 1; i <= 10; i++) {
    printf("%d\n", i);
  }

  return 0;
}

The for loop probably looks a bit intimidating. Let's take a closer look.

   for (i = 1;                  i <= 10;                i++) {
        ^ Initialization        ^ Loop condition        ^ End expression

The stuff inside the parentheses of the for statement defines the loop. Here is how it works.

  1. The initialization expression is done first and only once.

  2. The loop condition is checked. If true, the body of the loop executes (one time). If false, the loop ends and the program continues after the closing brace of the loop.

  3. The end expression is computed.

  4. Go back to step 2.

In this example this is what happens.

a) The value of 1 is assigned to i.
b) Is i <= 10? Yes. The loop body executes and the value of i is printed.
c) The value of i is incremented. Now it is 2.
d) Is i <= 10? Yes. The loop body executes and the value of i is printed.
e) The value of i is incremented. Now it is 3.
f) Is i <= 10? Yes. The loop body executes and the value of i is printed.
g) The value of i is incremented. Now it is 4.
h) Is i <= 10? Yes. The loop body executes and the value of i is printed.

and so forth. Eventually i is incremented to 11. When that happens the loop condition will fail and the loop ends. The 11 is never printed because the loop will end before it gets a chance to do that.

Printing out numbers from 1 to 10 isn't too exciting. Let's print out all the numbers from 1 to 1000000.

for (i = 1; i <= 1000000; i++) {
  printf("%d\n", i);
}

See how easy that was! If you try running this program you probably won't want to wait for it to print everything. You can abort the output by typing ^C (you might have to type it several times).

The for loop is very flexible. The initialization expression, loop condition, and end expression are all totally general. Suppose you wanted to count down instead of up

for (i = 10; i >= 1; i--) {
  printf("%d\n", i);
}

This gives i an initial value of 10, loops as long as i >= 1, decrementing i as it goes. What if you wanted to count up by two at a time?

for (i = 1; i <= 10; i += 2) {
  printf("%d\n", i);
}

Here I'm doing i += 2 at the end of each loop pass. That has the effect of advancing i by two instead of just one. This loop prints out the values 1, 3, 5, 7, 9. After the 9 is printed, i is made into 11. Since 11 <= 10 is false the loop ends without printing the eleven.

It is even possible for the various expressions in a for loop to be unrelated!

for (i = 34; a == b; j = 3 * k) {
  printf("Huh?");
}

This loop really doesn't make much sense. It first sets i to 34. Then it asks, "is a equal to b?" If so, it runs the body of the loop (the one printf statement). Afterwards it then computes a new value for j by multiplying k by 3. Then it asks again "is a equal to b?" Since nothing that has happened so far has changed a or b, the answer is probably still yes. Thus this loop either won't run at all or runs forever. Such an example is pretty strange. I'm just showing it to you so that you realize that you can put anything you want in there!

I find that understanding for loops is much easier if I use the phrase "as long as" when I read the loop condition and "as we go" when I read the end expression. Let's look at my first example again

for (i = 1; i <= 10; i++) {
  // etc...
}

I would read this as "for i set equal to one, as long as i is less than or equal to 10, incrementing i as we go..." Sometimes students try to write that loop this way

for (i = 1; i == 10; i++) {
  // etc...
}

Students familiar with BASIC, for example, tend to do this. That's because in BASIC for loops work a bit differently and people get used to that. However, in C this loop never executes. It first sets i to 1. Then it asks, "is i equal to 10?" Of course not! You just set it to 1! Since the loop condition is false right away, the loop ends at once.

IMPORTANT: Depending on how you set it up a for loop might not execute at all or it might execute forever.

Loops that execute forever ("infinite" loops) are actually sometimes useful. Keep in mind that "forever" does not really mean until after the stars go dark. It just means for as long as the program is allowed to run. A typical server program executes forever and probably is built around a giant infinite loop. However, infinite loops are usually bugs. When they happen your program will get "stuck" either doing apparently nothing or doing something undesirable without stopping.

Off by one!

As I've mentioned in earlier lessons, being off by one is a very common error. It shows up most often in loops. Suppose I wanted to loop exactly 10 times. Take a look at the following choices.

  1. for (i = 1; i < 10; i++) { ...

  2. for (i = 0; i < 10; i++) { ...

  3. for (i = 1; i <= 10; i++) { ...

  4. for (i = 0; i <= 10; i++) { ...

Of these four loops only two of them actually execute 10 times. Can you tell which? Let's look at them more closely.

  1. This loop only runs 9 times. When i is made into 10, the loop condition will fail (10 < 10 is false) and so the loop will end without the body executing that last time. In particular, the body of the loop executes with i ranging from 1 to 9.

  2. This works. Here i starts at 0. As with part (1) it only goes to 9, but the range 0 to 9 contains 10 values.

  3. This also works. Here the loop condition specifies that the loop should run when i is still 10. The value of i has to be 11 to end the loop. Thus the loop body executes while i is in the range 1 to 10.

  4. This loop runs 11 times. In particular it runs while i is in the range from 0 to 10.

Getting this sort of thing wrong is a very common error. If your loops don't execute often enough you will typically leave the last bit of data unprocessed. This annoys users who, naturally, expect that all of their data will be processed. If your loops run too often you will typically try to process data that isn't there. This causes all manner of bad effects. Don't do it!

In general, if you just want to run a loop a certain number of times, I recommend that you start it at zero and use version (2) above. Normal people start counting at one, but it turns out to be more natural in the computer world to start at zero. Get in the habit of doing it.

Some terminology

By the way, the variable, i, that I've been using in my examples is typically called a "loop index variable". It is an ordinary variable that you need to declare like any other. However, if you hear people talking about the "loop index" I want you to know what that term means.

The variable names that I've been using in my examples are usually pretty bad. That's because my examples are taken in isolation. In a real program you should not use variables named a or b. Instead you should choose names that reflect the meaning of the variable with respect to your problem. However, loop index variables are a special case. There is a long standing tradition that dates all the way back to the 1950s for giving such variables names like i, j, and k. You may follow that tradition without sacrificing any clarity in your program. However, if you want you could write your loops like this

for (loop_index = 0; loop_index < 10; loop_index++) {
  printf("%d\n", loop_index);
}

It's a little more wordy but it is quite clear at least.

Summary

  1. You can use the && operator for AND, the || operator for OR, and the ! operator for NOT in order to create complicated conditions. For example, the body of the following if statement executes if both of the smaller conditions are true.

    if (x == y && x > z) { ...
    
  2. You can use a for loop to cause your program to execute the same code over and over again. To set up a for loop you must fill in three things: how to initialize the loop, what condition must be true to run the loop, and what calculation should be done at the end of each loop pass. For example, the following loop executes 10 times.

    for (i = 0; i < 10; i++) { ...
    
  3. It is easy to write loops that execute once too often or once not often enough. To avoid this, pay close attention to the condition you use to control the loop. For example, the loop above runs when i == 9, but not when i == 10. If the condition were changed to i <= 10 the loop would run when i == 10 and not stop until i was made into 11.

© Copyright 2003 by Peter C. Chapin.
Last Revised: July 21, 2003