1.4. Some more programs
While we're still in the informal phase, let's look at two more examples. You will have to work out for yourself what some of the code does, but as new or interesting features appear, they will be explained.
1.4.1. A program to find prime numbers
/* * * Dumb program that generates prime numbers. */ #include <stdio.h> #include <stdlib.h> main(){ int this_number, divisor, not_prime; this_number = 3; while(this_number < 10000){ divisor = this_number / 2; not_prime = 0; while(divisor > 1){ if(this_number % divisor == 0){ not_prime = 1; divisor = 0; } else divisor = divisor-1; } if(not_prime == 0) printf("%d is a prime number\n", this_number); this_number = this_number + 1; } exit(EXIT_SUCCESS); }Example 1.2
What was interesting in there? A few new points, perhaps. The program
works in a really stupid way: to see if a number is prime, it divides that
number by all the numbers between half its value and two—if any
divide without remainder, then the number isn't prime. The two operators
that you haven't seen before are the remainder operator %
,
and the equality operator, which is a double equal sign ==
.
That last one is without doubt the cause of more bugs in C programs than
any other single factor.
The problem with the equality test is that wherever it can appear it is
also legal to put the single =
sign. The first,
==
, compares two things to see if they are equal, and is
generally what you need in fragments like these:
if(a == b) while (c == d)
The assignment operator =
is, perhaps surprisingly, also
legal in places like those, but of course it assigns the value of the
right-hand expression to whatever is on the left. The problem is
particularly bad if you are used to the languages where comparison for
equality is done with what C uses for assignment. There's nothing that you
can do to help, so start getting used to it now. (Modern compilers do tend
to produce warnings when they think they have detected
‘questionable’ uses of assignment operators, but that is a mixed
blessing when your choice was deliberate.)
There is also the introduction for the first time of the if
statement. Like the while
statement, it tests an expression
to see if the expression is true. You might have noticed that also like
the while
statement, the expression that controls the
if
statement is in parentheses. That is always the case: all
of the conditional control of flow statements require a parenthesized
expression after the keyword that introduces them. The formal description
of the if
statement goes like this:
if(expression) statement if(expression) statement else statement
showing that it comes in two forms. Of course, the effect is that if the
expression part is evaluated to be true, then the following statement is
executed. If the evaluation is false, then the following statement is not
executed. When there is an else
part, the statement associated
with it is executed only if the evaluation gives a false result.
If
statements have a famous problem. In the following piece
of code, is the statement-2 executed or not?
if(1 > 0) if(1 < 0) statement-1 else statement-2
The answer is that it is. Ignore the indentation (which is
misleading). The else
could belong to either the first or
second if
, according to the description of the if
statement that has just been given, so an extra rule is needed to make it
unambiguous. The rule is simply that an else
is associated
with the nearest else
-less if
above it. To make
the example work the way that the indentation implied, we have to invoke a
compound statement:
if(1 > 0){ if(1 < 0) statement-1 } else statement-2
Here, at least, C adheres to the practice used by most other languages. In fact a lot of programmers who are used to languages where the problem exists have never even realized that it is there—they just thought that the disambiguating rule was ‘obvious’. Let's hope that everyone feels that way.
1.4.2. The division operators
The division operators are the division operator /
, and the
remainder operator %
. Division does what you would expect,
except that when it is applied to integer operands it gives a result that
is truncated towards zero. For example, 5/2
gives
2
, 5/3
gives 1
. The remainder
operator is the way to get the truncated remainder. 5%2
gives
1
, 5%3
gives 2
. The signs of the
remainder and quotient depend on the divisor and dividend in a way that is
defined in the Standard and shown in Chapter 2.
1.4.3. An example performing input
It's useful to be able to perform input as well as to write programs
that print out more or less interesting lists and tables. The simplest of
the library routines (and the only one that we'll look at just now) is
called getchar
. It reads single characters from the program's
input and returns an integer value. The value returned is a coded
representation for that character and can be used to print the same
character on the program output. It can also be compared against character
constants or other characters that have been read, although the only test
that makes sense is to see if both characters are the same. Comparing for
greater or less than each other is not portable in general; there is no
guarantee that 'a'
is less than 'b'
, although on
most common systems that would be the case. The only guarantee that the
Standard makes is that the codes for '0'
through to
'9'
will always be consecutive. Here is one example.
#include <stdio.h> #include <stdlib.h> main(){ int ch; ch = getchar(); while(ch != 'a'){ if(ch != '\n') printf("ch was %c, value %d\n", ch, ch); ch = getchar(); } exit(EXIT_SUCCESS); }Example 1.3
There are two interesting points in there. The first is to notice that at the end of each line of input read, the character represented by
'\n'
(a character constant) will be seen. This just like the way that the
same symbol results in a new line when printf
prints it. The
model of I/O used by C is not based on a line by line view of the world,
but character by character instead; if you choose to think in a
line-oriented way, then '\n'
allows you to mark the end of
each ‘line’. Second is the way that %c
is used to
output a character by printf
, when it appears on the output
as a character. Printing it with %d
prints the same variable,
but displays the integer value used by your program to represent the
character.
If you try that program out, you may find that some systems do not pass characters one by one to a program, but make you type a whole line of input first. Then the whole line is made available as input, one character at a time. Beginners have been known to be confused: the program is started, they type some input, and nothing comes back. This behaviour is nothing to do with C; it depends on the computer and operating system in use.
1.4.4. Simple arrays
The use of arrays in C is often a problem for the beginner.
The declaration of arrays isn't too difficult, especially the
one-dimensional ones, but a constant source of confusion is the fact
that their indices always count from 0. To declare an array of
5 int
s, the declaration would look like this:
int something[5];
In array declarations C uses square brackets, as you can see. There is
no support for arrays with indices whose ranges do not start at 0 and go
up; in the example, the valid array elements are something[0]
to something[4]
. Notice very carefully that
something[5]
is not a valid array element.
This program reads some characters from its input, sorts them into the order suggested by their representation, then writes them back out. Work out what it does for yourself; the algorithm won't be given much attention in the explanation which follows.
#include <stdio.h> #include <stdlib.h> #define ARSIZE 10 main(){ int ch_arr[ARSIZE],count1; int count2, stop, lastchar; lastchar = 0; stop = 0; /* * Read characters into array. * Stop if end of line, or array full. */ while(stop != 1){ ch_arr[lastchar] = getchar(); if(ch_arr[lastchar] == '\n') stop = 1; else lastchar = lastchar + 1; if(lastchar == ARSIZE) stop = 1; } lastchar = lastchar-1; /* * Now the traditional bubble sort. */ count1 = 0; while(count1 < lastchar){ count2 = count1 + 1; while(count2 <= lastchar){ if(ch_arr[count1] > ch_arr[count2]){ /* swap */ int temp; temp = ch_arr[count1]; ch_arr[count1] = ch_arr[count2]; ch_arr[count2] = temp; } count2 = count2 + 1; } count1 = count1 + 1; } count1 = 0; while(count1 <= lastchar){ printf("%c\n", ch_arr[count1]); count1 = count1 + 1; } exit(EXIT_SUCCESS); }Example 1.4
You might note that the defined constant ARSIZE
is used
everywhere instead of the actual array size. Because of that, to change
the maximum number of characters that can be sorted by this program simply
involves a change to one line and then re-compiling. Not so obvious but
critical to the safety of the program is the detection of the array
becoming full. Look carefully; you'll find that the program stops when
element ARSIZE-1
has been filled. That is because in an
N
element array, only elements 0
through to
N-1
are available (giving N
in total).
Unlike some other languages it is unlikely that you will be told if you ‘run off’ the end of an array in C. It results in what is known as undefined behaviour on the part of your program, this generally being to produce obscure errors in the future. Most skilled programmers avoid this happening by rigorous testing to make sure either that it can't happen given the particular algorithm in use, or by putting in an explicit test before accessing a particular member of an array. This is a common source of run-time errors in C; you have been warned.
Summary
Arrays always number from 0
; you have no choice.
A n
-element array has members which number from
0
to n-1
only. Element n
does not
exist and to access it is a big mistake.