Part 3. Flow control

An important part of programming is telling your program to do different things depending on certain conditions. And that brings us to "flow control."

Tests

Before we can get into controlling the flow of a program, we need to first understand tests. These "tests" will evaluate the values of variables. The basic tests are is something equal to something else or is something greater than (or less than) something else?

==Equal. Tests if two values are the same, and returns a "True" value if they are. Otherewise, returns a "False" value.
!=Not equal. Tests if two values are the same, and returns a "True" value if they are different. Otherwise returns a "False" value.
<Less than. Tests if the first value is less than the second.
<=Less than or equal to. Tests if the first value is less than or equal to the second.
>Greater than. Tests if the first value is greater than the second.
>=Greater than or equal to. Tests if the first value is greater than or equal to the second.

There are three main ways to control flow in a program: conditional execution, loops, and functions. We'll focus on conditional execution and loops this week. We'll cover functions next week.

Conditional execution

if-else

The key to conditional execution is how to do certain things depending on a condition. And to do that, you need if and if-else.

In the simplest case, you can write an if statement on one line:

if (i == 1) puts("i is equal to one");

But it's more likely that you will want to execute several statements instead of just one. In this case, use curly braces to enclose your statements:

int i;
⋮
if (i == 1) {
   puts("i is equal to one");
   /* other statements here */
}

And you can execute other statements in an "else" condition. The else follows the same rule as if; you can put it on one line, but most of the time you'll probably put statements in a block:

int i;
⋮
if (i == 1) {
   puts("i is equal to one");
   /* other statements here */
}
else {
   puts("i is some other value");
   /* other statements here */
}

But what if you want to test several things at once? You can stack the if-else statements by enclosing each in a separate block. For example, if we wanted to test for additional possible values of i, we could combine the if-else statements like this:

int i;
⋮
if (i == 1) {
   puts("i is equal to one");
   /* other statements here */
}
else {
   if (i == 2) {
      puts("i is equal to two");
      /* other statements here */
   }
   else {
      if (i == 3) {
         puts("i is equal to three");
         /* other statements here */
      }
      else {
         puts("i is some other value");
         /* other statements here */
      }
   }
}

Since an else can be on one line, the statement that gets executed by the else can be another if block. I'll indent the code block below to show you. The indented block is the statement executed by the else statement:

int i;
⋮
if (i == 1) {
   puts("i is equal to one");
   /* other statements here */
}
else if (i == 2) {
        puts("i is equal to two");
        /* other statements here */
     }
     else if (i == 3) {
             puts("i is equal to three");
             /* other statements here */
          }
          else {
             puts("i is some other value");
             /* other statements here */
          }

This method of nesting if-else statements is very common. And it provides very readable code, because most people will read it as "if this, then do X … or if this, then do Y … or else do Z." A more natural (and common) way to write the if-else block is to indent everything at the first if level, like this:

int i;
⋮
if (i == 1) {
   puts("i is equal to one");
   /* other statements here */
}
else if (i == 2) {
   puts("i is equal to two");
   /* other statements here */
}
else if (i == 3) {
   puts("i is equal to three");
   /* other statements here */
}
else {
   puts("i is some other value");
   /* other statements here */
}

You can combine your tests with logical and and or statements. These join two tests, to determine if both are true, or if one of them is true. (You can also "negate" a test using ! - this turns a True value in a False value, and vice versa.)

&&And. If both tests are True, then return a "True" value. Otherwise, evaluates to a "False" value.
||Or. If either test is True, then return a "True" value.
!Not. Negates the test. If the test would have returned True, it instead returns "False."

You usually combine tests when you need to evaluate if two or more conditions match. For example, for two variables i and n, we might do this:

int i;
⋮
if ( (i == 1) && (n > 2) ) {
   puts("i is one, and n is greater than two");
   /* other statements here */
}

Note that I enclosed each test in its own pair of parentheses, and the overall if test was wrapped with its own pair of parentheses. This is a good way to keep your code readable.

(But there are usually better ways to do a test than using ! to negate the test. For example, if you planned to type if ( !(i == 1) ) you could instead write if (i != 1) and that would be more clear.)

switch-case

The switch statement is very simple. You pass switch a value, and use different case statements that match specific values of that value.

You'll usually need to define a default behavior, which is a catch-all for any other value not already defined by the other case statements. Let's rewrite the if-else blocks that test for different values of i using a switch statement:

int i;
⋮
switch(i) {
case 1:
   puts("i is equal to one");
   /* other statements here */
   break; /* you need a 'break' statement to stop processing here */

case 2:
   puts("i is equal to two");
   /* other statements here */
   break;

case 3:
   puts("i is equal to three");
   /* other statements here */
   break;

default: /* this gets executed when no other case statements match */
   puts("i is some other value");
   /* other statements here */
   /* if this is the last in the list, you don't need 'break' here */
}

Loops

Loops are a handy way to repeat a procedure. C supports two basic loop types: while and do-while. The difference between them is when the test get evaluated. The while loop puts the test at the beginning. If the while test is false, the loop never starts.

int i = 1;
⋮
/* loop through the numbers 1 to 10, and print them */
i = 1;
while (i <= 10) {
   printf("%d\n", i);
   i = i + 1;
}

The do-while loop puts the test at the end. If the do-while test is false, then the loop runs just the one time. That means in a do-while loop, the loop always runs at least once.

int i = 1;
⋮
/* loop through the numbers 1 to 10, and print them */
i = 1;
do {
   printf("%d\n", i);
   i = i + 1;
} while (i <= 10);

Another handy loop is the iteration, or for loop, which runs through a loop while (usually) iterating a variable at the same time.

int i;
⋮
/* loop through the numbers 1 to 10, and print them */
for (i = 1; i <= 10; i = i + 1) {
   printf("%d\n", i);
}

A classic program is the "Foobar" example. Iterate through a set of numbers, and print them. If the number is evenly divisible by 3, then print "Foo" next to the number. If the number is evenly divisible by 5, then print "Bar." If the number is both divisible by 3 and by 5, then print "FooBar."

#include <stdio.h>

int
main()
{
   int maxcount;
   int count;

   puts("Enter maximum number:");
   scanf("%d", &maxcount);

   puts("counting . . .");

   for (count = 1; count <= maxcount; count++) {
      printf("%d ", count);

      if (count % 3 == 0) {
         printf("Foo");
      }

      if (count % 5 == 0) {
         printf("Bar");
      }

      printf("\n");
   }

   return 0;
}

And with loops, we can now write the FreeDOS PAUSE command:

#include <stdio.h>

int
main()
{
   char ch;

   puts("Press Enter to continue . . .");

   /* loop until we find \n (newline) */

   do {
      scanf("%c", &ch);
      /* printf("debugging: [%c]\n", ch); */
   } while (ch != '\n');

   puts("Ok");

   return 0;
}

Here's a sample program to determine if a value is even. You can use a do-while or while loop for this. I've shown the solution with a do-while loop, and commented out the while loop:

#include <stdio.h>

int
main()
{
   int num = -1;

   do {
/* while (num != 0) { */
      puts("Enter a number: (0 to quit)");
      scanf("%d", &num);

      if (num % 2 == 0) {
         printf("%d is even\n", num);
      }
      else {
         printf("%d is odd\n", num);
      }
/* } */
   } while (num != 0);

   return 0;
}

If you used a do-while loop instead, the evaluation of num would happen at the end of the loop, after you've read a number into num. If you did that, you would not need to initialize num to a specific value at the start of the program.

REVIEW

Tests:

==Equal
!=Not equal
<Less than
<=Less than or equal to
>Greater than
>=Greater than or equal to

Combining tests:

&&And
||Or
!Not

Types of flow control:

1. Conditional execution

2. Loops

3. Functions

PRACTICE

Now that you've learned about flow control, try writing these programs to practice:

Practice program 1.

Write a program that converts different values from Fahrenheit to Celsius. This is similar to the practice program in part 2, but in this case iterate from Fahrenheit temperatures from 98 to 104. The formula to convert Fahrenheit to Celcius is:

𝒞 = (ℱ − 32) × 5/9

Practice program 2.

Write a program that calculates the factorial of a number. The factorial (𝓃!) of a non-negative integer 𝓃 is defined as the product (multiplication) of that number times all the numbers down to 1. The factorial of zero (0!) is 1, and the factorial of one (1!) is also 1. For example, the factorial of five (5!) is 5×4×3×2×1.

Practice program 3.

Write a program that asks the user to enter a single letter. Continue looping until the user enters Q or q.

Practice program 4.

Do you know the "folk" song about 99 bottles of beer? It's a song about beer on a shelf, presumably in a pub. If you take down one bottle of beer and pass it around, you have 1 fewer bottles on the wall. This is a very tedious song to sing, but easy for a program to iterate. Write a program to loop through the lyrics "99 bottles of beer on the wall. 99 bottles of beer! // Take one down, pass it around. 98 bottles of beer on the wall." Stop when you reach 0 bottles.

Need help? Check out the sample solutions.