Back to Chapter 2 -- Index -- Chapter 4

x = 0; i++; printf(...);In C, the semicolon is a statement terminator, rather than a separator as it is in languages like Pascal.

Braces `{` and `}` are used to group declarations and statements
together into a *compound statement*, or *block*, so that they
are syntactically equivalent to a single statement. The braces that surround
the statements of a function are one obvious example; braces around multiple
statements after an `if`, `else`, `while`, or `for`
are another. (Variables can be declared inside *any* block; we will
talk about this in Chapter 4.) There is no semicolon
after the right brace that ends a block.

if (where theexpression)statementelse_{1}statement_{2}

Since an `if` tests the numeric value of an expression, certain coding
shortcuts are possible. The most obvious is writing

if (instead ofexpression)

if (Sometimes this is natural and clear; at other times it can be cryptic.expression!= 0)

Because the `else` part of an `if-else` is optional,there is an
ambiguity when an else if omitted from a nested `if` sequence. This is
resolved by associating the `else` with the closest previous
`else`-less `if`. For example, in

if (n > 0) if (a > b) z = a; else z = b;the

if (n > 0) { if (a > b) z = a; } else z = b;The ambiguity is especially pernicious in situations like this:

if (n > 0) for (i = 0; i < n; i++) if (s[i] > 0) { printf("..."); return i; } else /* WRONG */ printf("error -- n is negative\n");The indentation shows unequivocally what you want, but the compiler doesn't get the message, and associates the

By the way, notice that there is a semicolon after `z = a` in

if (a > b) z = a; else z = b;This is because grammatically, a

if (occurs so often that it is worth a brief separate discussion. This sequence ofexpression)statementelse if (expression)statementelse if (expression)statementelse if (expression)statementelsestatement

The last `else` part handles the ``none of the above'' or default case
where none of the other conditions is satisfied. Sometimes there is no
explicit action for the default; in that case the trailing

elsecan be omitted, or it may be used for error checking to catch an ``impossible'' condition.statement

To illustrate a three-way decision, here is a binary search function that
decides if a particular value `x` occurs in the sorted array `v`.
The elements of `v` must be in increasing order. The function returns the
position (a number between 0 and `n-1`) if `x` occurs in `v`,
and -1 if not.

Binary search first compares the input value `x` to the middle element
of the array `v`. If `x` is less than the middle value, searching
focuses on the lower half of the table, otherwise on the upper half. In
either case, the next step is to compare `x` to the middle element of the
selected half. This process of dividing the range in two continues until the
value is found or the range is empty.

/* binsearch: find x in v[0] <= v[1] <= ... <= v[n-1] */ int binsearch(int x, int v[], int n) { int low, high, mid; low = 0; high = n - 1; while (low <= high) { mid = (low+high)/2; if (x < v[mid]) high = mid + 1; else if (x > v[mid]) low = mid + 1; else /* found match */ return mid; } return -1; /* no match */ }The fundamental decision is whether

**Exercise 3-1.** Our binary search makes two tests inside the
loop, when one would suffice (at the price of more tests outside.) Write a
version with only one test inside the loop and measure the difference in
run-time.

switch (Each case is labeled by one or more integer-valued constants or constant expressions. If a case matches the expression value, execution starts at that case. All case expressions must be different. The case labeledexpression) { caseconst-expr:statementscaseconst-expr:statementsdefault:statements}

In Chapter 1 we wrote a program to count the occurrences
of each digit, white space, and all other characters, using a sequence of
`if ... else if ... else`. Here is the same program with a `switch`:

#include <stdio.h> main() /* count digits, white space, others */ { int c, i, nwhite, nother, ndigit[10]; nwhite = nother = 0; for (i = 0; i < 10; i++) ndigit[i] = 0; while ((c = getchar()) != EOF) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': ndigit[c-'0']++; break; case ' ': case '\n': case '\t': nwhite++; break; default: nother++; break; } } printf("digits ="); for (i = 0; i < 10; i++) printf(" %d", ndigit[i]); printf(", white space = %d, other = %d\n", nwhite, nother); return 0; }The

Falling through cases is a mixed blessing. On the positive side, it allows
several cases to be attached to a single action, as with the digits in this
example. But it also implies that normally each case must end with a `break`
to prevent falling through to the next. Falling through from one case
to another is not robust, being prone to disintegration when the program is
modified. With the exception of multiple labels for a single computation,
fall-throughs should be used sparingly, and commented.

As a matter of good form, put a `break` after the last case (the
`default` here) even though it's logically unnecessary. Some day when
another case gets added at the end, this bit of defensive programming will
save you.

**Exercise 3-2.** Write a function `escape(s,t)` that
converts characters like newline and tab into visible escape sequences like
`\n` and `\t` as it copies the string `t` to `s`.
Use a `switch`. Write a function for the other direction as well,
converting escape sequences into the real characters.

while (theexpression)statement

The `for` statement

for (is equivalent toexpr;_{1}expr;_{2}expr)_{3}statement

except for the behaviour ofexpr; while (_{1}expr) {_{2}statementexpr; }_{3}

Grammatically, the three components of a `for` loop are expressions. Most
commonly, *expr _{1}* and

for (;;) { ... }is an ``infinite'' loop, presumably to be broken by other means, such as a

Whether to use `while` or `for` is largely a matter of personal
preference. For example, in

while ((c = getchar()) == ' ' || c == '\n' || c = '\t') ; /* skip white space characters */there is no initialization or re-initialization, so the

The `for` is preferable when there is a simple initialization and
increment since it keeps the loop control statements close together and
visible at the top of the loop. This is most obvious in

for (i = 0; i < n; i++) ...which is the C idiom for processing the first

As a larger example, here is another version of `atoi` for converting a
string to its numeric equivalent. This one is slightly more general than the
one in Chapter 2; it copes with optional leading white space
and an optional `+` or `-` sign. (Chapter 4
shows `atof`, which does the same conversion for floating-point numbers.)

The structure of the program reflects the form of the input:

*
skip white space, if any
get sign, if any
get integer part and convert it
*

Each step does its part, and leaves things in a clean state for the next. The whole process terminates on the first character that could not be part of a number.

#include <ctype.h> /* atoi: convert s to integer; version 2 */ int atoi(char s[]) { int i, n, sign; for (i = 0; isspace(s[i]); i++) /* skip white space */ ; sign = (s[i] == '-') ? -1 : 1; if (s[i] == '+' || s[i] == '-') /* skip sign */ i++; for (n = 0; isdigit(s[i]); i++) n = 10 * n + (s[i] - '0'); return sign * n; }The standard library provides a more elaborate function

The advantages of keeping loop control centralized are even more obvious when there are several nested loops. The following function is a Shell sort for sorting an array of integers. The basic idea of this sorting algorithm, which was invented in 1959 by D. L. Shell, is that in early stages, far-apart elements are compared, rather than adjacent ones as in simpler interchange sorts. This tends to eliminate large amounts of disorder quickly, so later stages have less work to do. The interval between compared elements is gradually decreased to one, at which point the sort effectively becomes an adjacent interchange method.

/* shellsort: sort v[0]...v[n-1] into increasing order */ void shellsort(int v[], int n) { int gap, i, j, temp; for (gap = n/2; gap > 0; gap /= 2) for (i = gap; i < n; i++) for (j=i-gap; j>=0 && v[j]>v[j+gap]; j-=gap) { temp = v[j]; v[j] = v[j+gap]; v[j+gap] = temp; } }There are three nested loops. The outermost controls the gap between compared elements, shrinking it from

One final C operator is the comma ```,`'', which most often finds use in
the `for` statement. A pair of expressions separated by a comma is
evaluated left to right, and the type and value of the result are the type
and value of the right operand. Thus in a for statement, it is possible to
place multiple expressions in the various parts, for example to process two
indices in parallel. This is illustrated in the function `reverse(s)`,
which reverses the string `s` in place.

#include <string.h> /* reverse: reverse string s in place */ void reverse(char s[]) { int c, i, j; for (i = 0, j = strlen(s)-1; i < j; i++, j--) { c = s[i]; s[i] = s[j]; s[j] = c; } }The commas that separate function arguments, variables in declarations, etc., are

Comma operators should be used sparingly. The most suitable uses are for
constructs strongly related to each other, as in the `for` loop in
`reverse`, and in macros where a multistep computation has to be a single
expression. A comma expression might also be appropriate for the exchange of
elements in `reverse`, where the exchange can be thought of a single
operation:

for (i = 0, j = strlen(s)-1; i < j; i++, j--) c = s[i], s[i] = s[j], s[j] = c;

The syntax of the `do` is

doThestatementwhile (expression);

Experience shows that `do-while` is much less used than `while` and
`for`. Nonetheless, from time to time it is valuable, as in the following
function `itoa`, which converts a number to a character string (the inverse
of `atoi`). The job is slightly more complicated than might be thought at
first, because the easy methods of generating the digits generate them in the
wrong order. We have chosen to generate the string backwards, then reverse it.

/* itoa: convert n to characters in s */ void itoa(int n, char s[]) { int i, sign; if ((sign = n) < 0) /* record sign */ n = -n; /* make n positive */ i = 0; do { /* generate digits in reverse order */ s[i++] = n % 10 + '0'; /* get next digit */ } while ((n /= 10) > 0); /* delete it */ if (sign < 0) s[i++] = '-'; s[i] = '\0'; reverse(s); }The

**Exercise 3-4.** In a two's complement number representation, our version
of `itoa` does not handle the largest negative number, that is, the value
of `n` equal to -(2^{wordsize-1}). Explain why not. Modify it to
print that value correctly, regardless of the machine on which it runs.

**Exercise 3-5.** Write the function `itob(n,s,b)` that converts the
integer `n` into a base `b` character representation in the string
`s`. In particular, `itob(n,s,16)` formats `s` as a hexadecimal
integer in `s`.

**Exercise 3-6.** Write a version of `itoa` that accepts
three arguments instead of two. The third argument is a minimum field width;
the converted number must be padded with blanks on the left if necessary to
make it wide enough.

The following function, `trim`, removes trailing blanks, tabs and
newlines from the end of a string, using a `break` to exit from a loop
when the rightmost non-blank, non-tab, non-newline is found.

/* trim: remove trailing blanks, tabs, newlines */ int trim(char s[]) { int n; for (n = strlen(s)-1; n >= 0; n--) if (s[n] != ' ' && s[n] != '\t' && s[n] != '\n') break; s[n+1] = '\0'; return n; }

The `continue` statement is related to `break`, but less often used;
it causes the next iteration of the enclosing `for`, `while`, or
`do` loop to begin. In the `while` and `do`, this means that the test
part is executed immediately; in the `for`, control passes to the increment
step. The `continue` statement applies only to loops, not to `switch`.
A `continue` inside a `switch` inside a loop causes the next loop
iteration.

As an example, this fragment processes only the non-negative elements in the
array `a`; negative values are skipped.

for (i = 0; i < n; i++) if (a[i] < 0) /* skip negative elements */ continue; ... /* do positive elements */The

Nevertheless, there are a few situations where `goto`s may find a place.
The most common is to abandon processing in some deeply nested structure, such
as breaking out of two or more loops at once. The `break` statement cannot
be used directly since it only exits from the innermost loop. Thus:

for ( ... ) for ( ... ) { ... if (disaster) goto error; } ... error: /* clean up the mess */This organization is handy if the error-handling code is non-trivial, and if errors can occur in several places.

A label has the same form as a variable name, and is followed by a colon. It
can be attached to any statement in the same function as the `goto`.
The scope of a label is the entire function.

As another example, consider the problem of determining whether two arrays
`a` and `b` have an element in common. One possibility is

for (i = 0; i < n; i++) for (j = 0; j < m; j++) if (a[i] == b[j]) goto found; /* didn't find any common element */ ... found: /* got one: a[i] == b[j] */ ...Code involving a

found = 0; for (i = 0; i < n && !found; i++) for (j = 0; j < m && !found; j++) if (a[i] == b[j]) found = 1; if (found) /* got one: a[i-1] == b[j-1] */ ... else /* didn't find any common element */ ...With a few exceptions like those cited here, code that relies on

Back to Chapter 2 -- Index -- Chapter 4