Control Structures in C and C++

We quickly review the control structures of the C language. What we see here for C is essentially identical in C++, although C++ has other structures that don’t exist in C (e.g., try blocks and range-based for loops). The primary control structures of C are also found in other languages, such as Java, so perhaps these are things that you are already familiar with. Still, I recommend that you go through this review and practice with all the exercises.

Declarations, Definitions and Statements

A C program is made up primarily of a series of declarations, definitions, and statements. Declarations introduce new identifiers (names) for types, variables, and functions. For example, below is a declaration of two variables of type int:

int a, b = 0;

In this case, the declaration also defines the two variables, and it also initializes one of them. A declaration that also defines the identifier is also called a definition. So again, the line above contains a declaration and definition of the two variables a and b. Notice that a definition is also a declaration, but not vice-versa. You might wonder what would be a declaration that is not also a definition. We discuss all of this in detail later, but here are a few preview examples:

extern int counter;
float sqrt(float x);
struct linked_list;

The first is a declaration of an integer variable called counter. The second one is a declaration of a function called sqrt that takes a float argument and returns a float result. The third one is a declaration of a struct type. None of these declarations defines the corresponding variable, function, or type. If you want to declare and at the same time define a function, you can do that as you would expect:

int sum (int a, int b) {
    return a + b;
}

So much for declarations and definitions. As I said, we will come back to them at some point. Our focus here is on statements. Those are the chunks of code that actually tell the program to do something. There are various types of statements in C: expressions, compound statements, selection statements, iteration statements, and jump statements. Let’s see them one by one.

Expression Statements

Expressions define computational actions, meaning that they typically compute values and may have side effects. Syntactically, an expression statement is an expression followed by a semicolon:

Syntax: expression ;

For example, each one of the following lines is an expression statement.

b = 2;
a = 7 + b*3;
printf("%d\n", a);

Expressions are built from basic operators, such as the arithmetic operators of addition and subtraction (+ and -) and many others. The operands can be literal values such as 7 or "%d\n", variables such as a or b, and other subexpressions, such as 7 + b*3, which is itself an expression containing b*3 as a subexpression. Expressions include function calls. In fact, a function call can also be seen as the application of an operator, the function-call operator, to a subexpression that identifies the function, and another subexpression that defines the list of arguments for the call. But let’s not go into so much detail here. There is a lot to say about expressions, but we will do that later in a dedicated chapter.

One more detail about expression statements: the expression can be empty, so it is possible to have a “null” statement. What would be the point of that? Here’s an example:

unsigned int length (const char s []) {
    unsigned int l;
    for (l = 0; s[l] != 0; ++l);
    return l;
}

I know we’re getting ahead of ourselves, but where is the empty statement here?

Compound Statement

A compound statement is a series of declarations and statements grouped together to form a logical unit of computation. The statements within a compound statement are structured in sequence, meaning that their order in the program determines their order of execution. Syntactically, a compound statement is a series of declarations and statements enclosed in curly braces.

Syntax: { declaration | statement}

The “body” of a function in a function definition is a compound statement.

int main () { /* <---------------------compound statement--\ */
    int a;                    /* declarations/definitions  | */
    int b;                    /*                      ...  | */
    int s;                    /*                           | */
    scanf("%d", &a);          /*    expression statements  | */
    scanf("%d", &b);          /*                      ...  | */
    s = a + b;                                  /*         | */
    printf("%d + %d = $d\n", a, b, c);          /*         | */
} /* <-----------------------------------------------------/ */

Compound statements are also typically parts of other statements. For example, the body of a loop is a statement that is often a compound statement.

void insertion_sort (int A [], int length) {
    for (int i = 1; i < length; ++i) { /* <--------compound statement--\ */
        int j = i - 1;                 /*                              | */
        while (j > 0 && A[j - 1] > A[j]) { /* <--compound statement--\ | */
            int tmp = A[j];                /*                        | | */
            A[j] = A[j - 1];               /*                        | | */
            A[j - 1] = tmp;                /*                        | | */
            --j;                           /*                        | | */
        } /* <-------------------------------------------------------/ | */
    } /* <-------------------------------------------------------------/ */

Selection Statements

It is essential for programs to be able to execute something in some cases, and something else in other cases. That is what selection statements are for. For example, you could write a function to output your score in a game:

void print_score (int p) {
    if (p < 0) {
        printf("Something is wrong: your score is negative!\n");
    } else {
        printf("You have %d point", p);
        if (p != 1)
            printf("s");
        printf("\n");
    }   
}

Or you could write the following little program to test the Collatz conjecture one step at a time:

int main () {
    int n;
    scanf("%d", &n);    /* read a number from standard input  */
    if (n % 2 == 1)
        n = 3*n + 1;
    n = n / 2;
    printf("%d\n", n);
}

Did you try this program? Try it now! And now that you’ve also seen how to read a number from the standard input with scanf("%d", &n), you should also write a program to test the print_score function of the previous example. Do it now!

All of this is intuitive, but let’s review the syntax and semantics of if-statements. Let’s first look at the Collatz example. The program contains a simple if-statement that executes the expression statement n = 3*n + 1 when \(n\) is an odd number, or more specifically when the remainder of the integer division of \(n\) by 2 is 1. The syntax of that if-statement is as follows:

Syntax: if ( expression ) statement

Condition Expressions

The expression in parens in the if statement is interpreted as a Boolean condition, meaning that it is interpreted as having a true or false value. The statement that follows is then executed when the condition expression is true. We say that the expression is interpreted as a Boolean condition meaning that the expression does not have to yield a Boolean value. In fact, until relatively recently, the C language did not even have a proper Boolean type. C++ does have a proper Boolean type, bool with literal values true and false, and recent versions of C also support something similar. Still, the conditional expression can be of any scalar type, such as int or float, and its value is considered to be true if it compares not equal to 0, or false otherwise. In fact, we could rewrite the if statement in the above program as follows:

if (n % 2)
    n = 3*n + 1;

Full If Statement

An if statement can also include an else part. We have already seen an example of that in the print_score function above. Again, this is intuitive and should be well known, but let’s review anyway. The syntax is as follows:

Syntax: if ( expression ) statement else statement

The program evaluates the expression in parens, and then executes the first statement right after the expression if the result of the condition expression compares not equal to 0, or otherwise executes the second statement right after else if the condition expression compares equal to 0.

As an exercise, consider the variant of the Collatz example listed below:

int main () {
    int n;
    scanf("%d", &n);
    do {
        if (n % 2 == 1) 
            n = 3*n + 1;
        else
            n = n / 2;
        printf("%d\n", n);
    } while (n != 1);
}

Notice that here the n = n / 2 statement is executed only when n is even. Do you see the difference with the other variant of that computation? Here’s the analogous code for comparison:

int main () {
    int n;
    scanf("%d", &n);
    do {
        if (n % 2 == 1) 
            n = 3*n + 1;
        n = n / 2;
        printf("%d\n", n);
    } while (n != 1);
}

Exercise

Figure out the output of the two programs for an input value of 7. Do that first without executing the program, and then execute the programs to check your answers.

Switch Statement

Sometimes you have more than two alternative behaviors to select. Here’s an example in which we rewrite the print_score function to also classify a score in various categories:

void print_score (int p) {
    if (p < 0) {
        printf("Something is wrong: your score is negative!\n");
        return;
    }
    printf("You have %d point", p);
    if (p != 1)
        printf("s");
    printf("\nThat is ");
    switch (p) {
    case 0:
    case 1:
        printf("a bit low");
        break;
    case 2:
        printf("okay");
        break;
    case 4:
        printf("very ");
    case 3:
        printf("good");
        break;
    case 5:
        printf("excellent!");
        break;
    default:
        printf("unbelievable!");
        break;
    }
    printf("\n");
}

A switch statement allows you to jump to one of many points in a compound statement based on the value of a given expression. The syntax is as follows:

Syntax: switch ( expression ) statement

The expression in parens after the switch keyword must be of an integer type. In the example, the expression is defined by the variable p, which is of type int, so that works. The statement that follows is typically a compound statement in which some statements may be labeled as follows:

case constant-expression : statement (opt)

or

default : statement

A constant-expression in a case label is typically a literal value (an integer constant or a character constant) or in any case an expression that is evaluated by the compiler, not by the program at run-time. In our example above, we use numeric constants. The switch statement may contain at most one default label.

The execution of the switch statement starts with the evaluation of the expression in parens, and then jumps to the label corresponding to the value of that expression, or to the default label if there is no corresponding label and if there is a default label. The execution then continues sequentially through the compound statement. A break statement terminates the execution of the switch statement.

Quiz 1: What’s the output of print_score(0)?

Quiz 2: What’s the output of print_score(3)?

Quiz 3: What’s the output of print_score(4)?

Quiz 4: What’s the output of print_score(7)?

Quiz 5: What if there is no default label? What’s the output of print_score(7) then? I didn’t tell you, so this is just to test your intuition and prior knowledge.

Quiz for the wiz: The paragraph above states that a break statement terminates the execution of the switch statement. Is that always true? If not, write and test a counter-example.

Iteration Statements

You must be able to tell a computer to do something repeatedly. You can do that with iteration statements. There are three flavors of iterations: while-loops, do-while-loops, and for-loops. At a high-level, they are pretty much the same: the program executes some code repeatedly as long as a certain condition is true.

While Loops

In this most basic form of iteration, you have a condition and a statement:

Syntax: while ( condition-expression ) statement

The while statement repeatedly evaluates the condition expression and executes the statement or “body” of the loop if the condition is true. The condition expression is evaluated before the execution of the statement, so if the condition is false at the beginning, the while statement terminates immediately. For example:

int main () {
    int n;
    scanf("%d", &n);
    while (n > 0) {
        printf ("%d\n", n);
        --n;
    }
}

Quiz 1: What happens when you input 0?

Quiz 2: What happens when you input 10?

Do-While Loops

If you want to check the loop condition after the body of the loop, then you write a do-while loop.

Syntax: do statement while ( condition-expression ) ;

The do-while statement first executes the statement or “body” of the loop and then evaluates the condition expression, and if that is true then executes again the body of the loop, and so on. Example:

int main () {
    int n;
    scanf("%d", &n);
    do 
        printf ("%d\n", n--);
    while (n > 0);
}

Quiz 1: What happens when you input 0?

Quiz 2: What happens when you input 1?

Quiz 3: What happens when you input 10?

For Loops

You often want to write loops in which, in addition to checking the loop condition, you do something right before the loop starts, and something else at the end of each loop iteration. A for loop makes those things explicit.

Syntax: for ( init-clause ; condition-expression ; iteration-expression ) statement

Example:

int main () {
    int n;
    scanf("%d", &n);
    for (int i = 1; i < n; i *= -2) 
        printf ("%d\n", i);
}

Of course you can do the same with a while loop:

int main () {
    int n;
    scanf("%d", &n);
    int i = 1;
    while (i < n) {
        printf ("%d\n", i);
        i *= -2;
    }
}

Exercise

Write a function print_sequence(int n) that, given a non-negative number n, outputs \(1, 2, \ldots, n\) on a single line with all adjacent numbers separated by a single space. In fact, write three variants of print_sequence(int n) with a while, do-while, and for loop, respectively.

Exercise

Write a function print_sequence_backwards(int n) that, given a non-negative number n, outputs \(n, n-1, \ldots, 1\) on a single line with all adjacent numbers separated by a single space. Write three variants of print_sequence(int n) with a while, do-while, and for loop, respectively.

Exiting or Short-Cutting a Loop

In some cases, you want to terminate a loop from within the body of the loop, without having to wait for the evaluation of the loop-condition. In other words, you want to break out of a loop. You can do that with a break statement. Example:

int main () {
    int n;
    scanf("%d", &n);
    for (int d = 2; d*d <= n; ++d) {
        if (n % d == 0) {
            printf ("%d is composite\n", n);
            break;
        }
    }
}

Other times you’d want to short-cut the execution of the loop. That is, you want to immediately go to the next iteration without going through the rest of the body of the loop. You can do that with a continue statement. The continue statement works in for loops as well as in while and do-while loops. Here’s an example of a for loop:

void print_primes_less_than_100 () {
    int C[100];
    for (int i = 0; i < 100; ++i)
        C[i] = 0;
    for (int i = 2; i < 100; ++i) {
        if (C[i])
            continue;
        for (int j = i*i; j < 100; j += i)
            C[j] = 1;
        printf("%d ", i);
    }
    printf("\n");
}

Notice that the continue statement short-cuts the execution of the body of the loop, not the loop condition or the iteration expression in a for loop. In the example above, the continue statement effectively jumps to the iteration expression ++i that is then followed by the evaluation of the loop condition j < 100. In while and do-while loops, a continue statement would effectively jump to the evaluation of the loop condition.

The continue and break statements apply to their immediately enclosing iteration statement. The break statement also applies to its immediately enclosing switch statement. Example:

int main () {
    int A[100];
    int n = 0;
    while (n < 100 && scanf("%d", &(A[n])) > 0) {
        int found = 0;
        for (int i = 0; i < n; ++i)
            if (A[i] == A[n]) {
                found = 1;
                break;
            }
        if (found)
            continue;
        printf("%d\n", A[n]);
        ++n;
    }
}

Here the break statement is directly contained in the for loop and only indirectly contained in the while loop. So, the break statement terminates the for loop, not the while loop. The continue statement is contained within the while loop, so it may short-cut the while loop.

Did you try the example above? And the one above it (print_primes_less_than_100)? Remember, this is a read-do document!

Exercise

Explain the semantics of the example above. Explaining the semantics means describing what the code does independent of how it does it.

Jump Statements

We have already seen some jump statements. The break and continue statements are jump statements, since they cause the execution of the program to “jump” to some place in the program. This is an illustration of the “jumps” in the example above:

int main () {
    int A[100];
    int n = 0;
    while (n < 100 && scanf("%d", &(A[n])) > 0) {
        int found = 0;
        for (int i = 0; i < n; ++i) {
            if (A[i] == A[n]) {
                found = 1;
                break;          /* JUMP */
            }                   /*   |  */
        }                       /*   |  */
        /* HERE <--------------------'  */
        if (found)
            continue;           /* JUMP */
        printf("%d\n", A[n]);   /*   |  */
        ++n;                    /*   |  */
        /* HERE <--------------------'  */
    }
}

There are two more jump statements: goto and return.

Go-To Statement

The goto statement transfers the control of the program to a specified statement in the code. The target statement is identified by a label. The scope of a label is the entire function where the label appears. This means that goto can jump to any label within the same function.

Syntax: goto label ;

You can label any statement (including a null statement) as follows:

Syntax: label : statement

Compared to the other control structures of C and C++, the goto statement is a kind of low-level instruction. It definitely controls the execution flow of a program, but it provides little or no structure. You can use the goto statement together with if statements to realize any kind of control flow. However, the resulting program is likely to be hard to understand. For example, you can write:

int main () {
    int n = 0;
    iterate:
    printf("%d\n", n);
    ++n;
    if (n < 10)
        goto iterate;
}

However, even in this very simple example, the loop structure is not very visible. A better code that does the same thing might look like this:

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

The message here is, if you need your program to perform a loop, use an explicit loop statement. Same for selection. Some would even argue that the goto statement must be avoided completely. In fact, many other languages do not have the goto statement or anything like it. Still, there are situations in which the goto statement is appropriate and even more elegant than other structures.

The goto statement is particularly useful to deal with error conditions. Remember the example we discussed when we talked about the potential failure of input/output operations such as putchar? Here’s a slightly more compact version of that same example:

#include <stdio.h>
#include <ctype.h>

int main () {
    int c;
    int x = 0;
    int reading_number = 0;
    do {
        c = getchar();
        if (isdigit(c)) {
            reading_number = 1;
            x = 10*x + (c - '0');
        } else {
            if (reading_number == 1) {
                for (; x > 0; --x) 
                    if (putchar('#') == EOF) 
                        goto output_error;
                if (putchar('\n') == EOF) 
                    goto output_error;
                reading_number = 0;
            }
        }
    } while (c != EOF);
    return 0;
 output_error:
    perror("putchar() failed");
    return 1;
}

The benefit is that the error-handling code is in one place, and regardless of where you are in the function—even inside two nested loops—you can jump to that place in case of error.

Another use of goto statements is to implement a simple finite state machine. Don’t worry so much if you don’t know what a finite state machine is. Intuitively, it is a program that is in one of a number of “states”, and reacts to different input in different ways depending on the state it is in. For example, the following program counts how many words there are in its standard input. The idea is that the program might be reading a word, or it might be reading the spaces that separate words. Those are the two states:

#include <stdio.h>
#include <ctype.h>

int main () {
    int c;
    int n = 0;                  /*         START            */
                                /*    {n = 0}|              */
    space:                      /*           ,------------. */
    c = getchar();              /*           v            | */
    if (c == EOF)               /*  .-----(SPACE)--space--' */
        goto end;               /*  |        |            ^ */
    if (isspace(c))             /* EOF   non-space        | */
        goto space;             /*  |        |            | */
                                /*  |        |{++n}       | */
    ++n;                        /*  |        v            | */
    word:                       /*  |<-EOF-(WORD)--space--' */
    c = getchar();              /*  |        | ^----.       */
    if (c == EOF)               /*  |    non-space  |       */
        goto end;               /*  |        |      |       */
    if (isspace(c))             /*  |         `-----'       */
        goto space;             /*  |                       */
    goto word;                  /*  |                       */
                                /*  `----->(END) {print n}  */
    end:                        /*           |              */
    printf("%d\n", n);          /*          STOP            */
}

Return Statement

The return statement terminates the execution of the current function. The return statement can also specify an expression that defines the return value of the function received by the caller.

Syntax: return expression (optional) ;

Exercises

Anima

The following program takes some text input and produces some output. You can think of the output as an encoding of the input.

#include <stdio.h>

int main () {
    int c;
    while ((c = getchar()) != EOF) {
        switch (c) {
        case 'a':
        case 'e':
        case 'i':
        case 'o':
        case 'u':
            putchar(c);
            putchar('s');
        default:
            putchar(c);
        }
    }
}

Write a program that performs the inverse encoding. If you call the program above anima, and you call anima_reverse the program you write to undo the encoding, then you can test that your program works as follows. First, edit a test input input1.txt, then run the following commands:

$ cat input1.txt | ./anima | ./anima_reverse > input1.out
$ diff input1.txt input1.out