Tutorial : C - Branching And Iteration

Branching and Iteration

The C language provides three types of decision-making constructs: if-else, the conditional expression ?:, and the switch statement. It also provides three looping constructs: while, do-while, and for. And it has the infamous goto, which is capable of both non-conditional branching and looping.

If-Else

The basic if statement tests a conditional expression and, if it is non-zero (i.e., TRUE), executes the subsequent statement. For example, in this code segment

if (a < b)
b = a;

the assignment b=a will only occur if a is less-than b. The else statement deals with the alternative case where the conditional expression is 0 (i.e., FALSE).

if (a < b)
b = a;
else
b += 7;

The if-else statement can also command multiple statements by wrapping them in braces. Statements so grouped are called a compound statement, or block, and they are syntactically equivalent to a single statement.

if (a < b) {
b = a;
a *= 2;
}
else {
b += 7;
--a;
}

It is possible to chain if-else statements if the following form

if (expression)
statement;
else if (expression)
statement;
else if (expression)
statement;
else
statement;

This chain is evaluated from the top and, if a particular if-conditional is TRUE, then its statement is executed and the chain is terminated. On the other hand, if the conditional is FALSE, the next if-conditional is tested. If all the conditionals evaluate to FALSE, then the final else statement is executed as a default. (Note, the final else is optional and, if it is missing, the default action is no action.)

An example if-else chain is shown below. This code segment performs integer division on the first k elements of an array of integers num[SIZE]. The first two if-statements do error-checking,1 and the final else does the actual calculation. Notice that the else is a compound statement, and that a variable (int i) is declared there; variables may be declared at the top of any block, and their scope is local to that block.

if (k < 0 || k > SIZE)
	printf("Error: Invalid number of elements (out-of-bounds).\n");
else if (denom == 0)
	printf("Error: Denominator is zero.\n");
else {
int i;
	printf("Result of division by %d: ", denom);
	for (i = 0; i < k; ++i)
		printf("%d ", num[i]/denom);
	printf("\n");
}

Note. A common mistake with if-else blocks is the “dangling else problem”. For example, consider the following nested-if statement.

if (a < b)
	if (m != 0)
		b = a;
else
	a = m;

The intention of the programmer, as indicated by the indentation, is that the else corresponds to the outer if statement. However, it actually belongs to the inner if statement. Desired association can be enforced by brackets.

if (a < b) {
	if (m != 0)
		b = a;
}
else
	a = m;

 

?: Conditional Expression
The conditional expression is a ternary operator; that is, it takes three operands. It has the following form

	(expression 1) ? (expression 2) : (expression 3)

If the first expression is TRUE (i.e., non-zero), the second expression is evaluated, otherwise the third is evaluated. Thus, the result of the ternary expression will be the result of either the second or third expressions, respectively. For example, to calculate the maximum of two values

c = (a > b) ? a : b; /* c = max(a,b) */

As a branching construct, the ?: operator appears far less frequently than the if-else and switch constructs.

 

Switch

The switch statement is a multi-way decision that tests whether an expression matches one of a number of constant integer values, and branches.

The general form of the switch statement is as follows

switch (expression) {
    case const-int-expr: statements
    case const-int-expr: statements
    default: statements
}

On evaluating the expression, if it matches one of the set of constant integer expressions, the switch branches to the matching case label and executes the statements following that point. Otherwise it jumps to the default label and executes its statements. The default label is optional, and if it does not exist, and none of the case labels match, the switch simply performs no action.

Note. The default label is typically the last label in the block. While this is good practice in general, it is not mandatory, and case labels may appear below default.

The statements following a case label are executed until terminated by a break statement, which causes an immediate exit from the switch block. However, if a break is not encountered, execution will flow on through to the next cases until the end of the block.2 This is termed fall through and is the default behaviour in a switch. Fall through is rarely used because it is difficult to code correctly; it should be used with caution.

Style Note. It is generally good practice to have a default label even when it is not necessary, even if it just contains an assert to catch logical errors (i.e., program bugs). Also, fall-through is much less common than break, and every case label should either end with a break or have a /* Fall Through */ comment to make ones intentions explicit. Finally, it is wise to put a break after the last case in the block, even though it is not logically necessary. Some day additional cases might be added to the end and this practice will prevent unexpected bugs.

It is worth mentioning here that all the control structures—if-else, ?:, while, do-while, and for—can be nested, which means that they can exist within other control statements. The switchstatement is no exception, and the statements following a case label may include a switch or other control structure. For example, the following code-structure is legitimate.

if (expression)
    while (expression)
        switch(integer expression) {
        case A1:
            switch(integer expression) {
            case B1: statements
            case B2: statements
            case B3: statements
        }
        case A2: statements
        default: statements
    }


The following example converts the value of a double variable angle to normalised radians (i.e., −π ≤ angle ≤ π). The original angle representation is either degrees or radians, as indicated by the integer angletype, and DEG, RAD, and PI are symbolic constants.

switch (angletype)
{
case DEG:
    angle *= PI / 180.0; /* convert to radians */
    /* fall through */
case RAD:
    while (angle > PI) /* normalise radians */
        angle -= 2.0*PI;
    while (angle < -PI)
        angle += 2.0*PI;
    break;
default:
    printf("Error: Invalid type\n");
    break;
}

While Loops
The while loop has the general form

while (expression)
    statement;

   
If the conditional expression is TRUE, then the while will execute the following statement, after which it will reevaluate the conditional. It continues this iteration while ever the conditional remains TRUE. As with the if-else statement, the while loop can execute multiple statements as a block by enclosing them in braces.    

For example, the following code segment computes the greatest common divisor (GCD) of two positive integers m and n (i.e., the maximum value that will divide both m and n). The loop iterates until the value of n becomes 0, at which point the GCD is the value of m.

while (n) {
    int tmp = n;
    n = m%n;
    m = tmp;
}

Do-While Loops
The do-while loop has the general form

do
	statement;
while (expression);

Its behaviour is virtually the same as the while loop except that it always executes the statement at least once. The statement is executed first and then the conditional expression is evaluated to decide upon further iteration. Thus, the body of a while loop is executed zero or more times, and the body of a do-while loop is executed one or more times.

Style note. It is good form to always put braces around the do-while body, even when it consists of only one statement. This prevents the while part from being mistaken for the beginning of a while loop.

The following code example takes a non-negative integer val and prints it in reverse order. The use of a do-while means that 0 can be printed without needing extra special-case code.

do
{
printf("%d", val % 10);
val /= 10;
} while (val != 0);

 

For Loops
The for loop has the general form

for (expr1; expr2; expr3)
    statement;

Its behaviour is equivalent to a while loop with the following arrangement of expressions.

expr1;
while (expr2) {
    statement;
    expr3;
}

In the for loop, expressions 1, 2, and 3 are optional, although the semicolons must remain. If expressions 1 or 3 are not there, then the loop simple behaves like the while loop above without expressions 1 or 3. If expression 2 is omitted, then the conditional is always TRUE, and an infinite loop results

for (;;) /* infinite loop */
    statement;

 
Note. It is possible to stack several expressions in the various parts of the for loop using the comma operator. The comma operator enables multiple statements to appear as a single statement without having to enclose them with braces. However, it should be used sparingly, and is most suited for situations like the following example. This example reverses a character string in-place. The first loop finds the end of the string, and the second loop performs the reversing operation by swapping characters.

for (j=0; str[j] != ’\0’; ++j);
for (i=0, --j; i < j; ++i, --j) {
    tmp = str[i];
    str[i] = str[j];
    str[j] = tmp;
}

 

Break and Continue
As seen previously, break can be used to branch out of a switch-statement. It may also be used to branch out of any of the iterative constructs. Thus, a break may be used to terminate the execution of a switch, while, do-while, or for. It is important to realise that a break will only branch out of an inner-most enclosing block, and transfers program-flow to the first statement following the block. For example, consider a nested while-loop,

while (expression) {
    while (expression) {
        if (expression)
            break;
        statements
    }
    statements
}

Here the break will terminate the inner while-loop and proceed to execute the statements of the outer while-loop.

This next example shows a fast technique for finding the smallest element in an array of length SIZE. A break is used to terminate the infinite outer while-loop.

i = SIZE;
temp = smallest = array[0];
while (1) {
    while (array[--i] > smallest);
    if (i == 0) break;
    array[0] = smallest = array[i];
}
array[0] = temp;

The continue-statement operates on loops only; it does not operate on switch. For while and do-while it causes transfer of program-control immediately to the conditional test, which is reevaluated for the next iteration. The same action occurs for a for-loop after first executing the increment expression (i.e., expression 3). Note that, as with break, continue acts on the inner-most enclosing block of a nested loop.

The continue statement is often used when the part of the loop that follows is complicated, so that reversing a test and indenting another level would nest the program too deeply

The following example shows the outline of a code-segment that performs operations on the positive elements of an array but skips the negative elements. The continue provides a concise means for ignoring the negative values

for (i = 0; i<SIZE ; ++i) {
    if (array[i] < 0) /* skip -ve elements */
        continue;
    /* process +ve elements */
}

Note. Both break and continue have no effect on an if-statement. A common misconception is that break can be used to jump out of an if compound statement. For example, given a nested construct of the form,

while (expression) {
    statements
    if (expression) {
        statements
        if (expression)
            break;
        statements
    }
    statements after if
}
statements after loop

it is commonly presumed that the break will transfer control to the statements after if, whereas it will actually transfer control to the statements after loop.

Goto

The goto statement has a well-deserved reputation for being able to produce unreadable “spaghetti” code. It is almost never necessary to use one, and they should be avoided in general. However, on rare occasions they are convenient and safe. A goto statement provides the ability to jump to a named-label anywhere within the same function.

One situation where a goto can be useful is if it becomes necessary to break out of a deeply nested structure, such as nested loops. A break statement cannot do this as it can only break out of one level at a time. The following example gives the basic idea.

1 #include <stdio.h> /* for printf() */
2 #include <stdlib.h> /* for rand() */
3 #include <string.h> /* for memset() */
4
5 #define SIZE 1000
6 enum { VAL1=’a’, VAL2=’b’, VAL3=’Z’ };
8 int main(void)
9 /* Demonstrate a legitimate use of goto (adapted from K&R page 66). This example is contrived, but the idea is to
10 * find a common element in two arrays. In the process, we demonstrate a couple of useful standard library functions. */
11 {
12 	char a[SIZE], b[SIZE];
13 	int i, j;
14
15 		/* Initialise arrays so they are different from each other */
16 		memset(a, VAL1, SIZE);
17 		memset(b, VAL2, SIZE);
18
19 		/* Set a random element in each array to VALUE */
20 		a[rand()%SIZE] = VAL3;
21 		b[rand()%SIZE] = VAL3;
22
23 		/* Search for location of common elements */
24 		for (i=0; i<SIZE; ++i)
25 			for (j=0; j<SIZE; ++j)
26 				if (a[i] == b[j])
27 					goto found;
28
29 		/* Error: match not found */
30 		printf("Did not find any common elements!!\n");
31 		return 0;
32
33 	found: /* Results on success */
34 		printf("a[%d] = %c and b[%d] = %c\n", i, a[i], j, b[j]);
35 }

16         The standard function memset() fills the first SIZE characters of array a with the value ’a’.
20–21     The standard function rand() returns a pseudo-random number from the range 0 to RAND_MAX, where RAND_MAX is an integer constant defined in header-file stdlib.h.
33–34     The named-label found: marks the next statement (the printf()) as the place to which goto will branch. A named-label must always be followed by a statement, even if it is just a null statement.
For example,
found: ; /* label to jump to a null statement */