Introduction to C Programming by Rob Miles, Electronic Engineering


Writing a Program

Comments
Program Flow
Conditional Execution - if
Conditions and Relational Operators
Combining Logical Operators
Lumping Code Together
Magic Numbers and #define
Loops
Breaking Out of Loops
Going Back to the Top of a Loop
More Complicated Decisions
Complete Glazing Program
Operator Shorthand
Statements and Values
Neater Printing

Comments

When the C compiler sees the "/*" sequence which means the start of a comment it says:

Aha! Here is a piece of information for greater minds than mine to ponder. I will ignore everything following until I see a */ which closes the comment.

Be generous with your comments. They help to make your program much easier to understand. You will be very surprised to find that you quickly forget how you got your program to work. You can also use comments to keep people informed of the particular version of the program, when it was last modified and why, and the name of the programmer who wrote it - if you dare!


Program Flow

The program above is very simple, it runs straight through from the first statement to the last, and then stops. Often you will come across situations where your program must change what it does according to the data which is given to it. Basically there are three types of program flow :

Every program ever written is composed of the three elements above, and very little else! You can use this to good effect when designing an overall view of how your program is going to work. At the moment we have only considered programs which run in a straight line and then stop. The path which a program follows is sometimes called its "thread of execution". When you call a function the thread of execution is transferred into the function until it is complete.

Conditional Execution - if

The program above is nice, in fact our customer will probably be quite pleased with it. However, it is not perfect. The problem is not with the program, but with the user.

If you give the program a window width of -1 it goes ahead and works out a stupid result. Our program does not have any checking for invalid widths and heights. The user might have grounds for complaint if the program fails to recognise that he has given a stupid value, in fact a number of cases are currently being fought in the United States courts where a program has failed to recognise invalid data, produced garbage and caused a lot of damage.

What we want to do is notice the really stupid replies and tell the user that he has done something dubious. In our program specification, which we give the customer, we have said something like:

The program will reject window dimensions outside the following ranges:

width less than 0.5 metres
width greater than 5.0 metres
height less than 0.75 metres
height greater than 3.0 metres

This means that we have done all we can; If the program gets 1 rather than 10 for the width then that is the users' problem, the important thing from our point of view is that the above specification stops us from being sued!

In order to allow us to do this the program must notice naughty values and reject them. To do this we need can use the construction:

if (condition)
	statement or block we do if the condition is true
else
	statement or block we do if the condition is false

The condition determines what happens in the program. So what do we mean by a condition? C is very simple minded about this, essentially it says that any condition which is non-zero is true, any condition which is zero is false. A condition is therefore really something which returns a number, for example:

if (1)
	printf ( "hello mum" ) ;

- is valid, although rather useless as the condition is always true, so "hello mum" is always printed (note that we left the else portion off - this is OK because it is optional).

Conditions and Relational Operators

To make conditions work for us we need a set of additional relational operators which we can use to make choices. Relational operators work on operands, just like numeric ones. However any expression involving them can only produce two values, 0 or 1. Zero if the expression is not true, one if it is. Relational operators available are as follows:

==
equals. If the left hand side and the right hand side are equal the expression has the value 1. If they are not equal the value is 0.
4 == 5

- would return 0, i.e. false. Note that it is not meaningful to compare floating point values in this way. Because of the fact that they are held to limited precision you might find that conditions fail when they should not for example the following equation :

x = 3.0 * ( 1.0 / 3.0) ;

- may well result in x containing 0.99999999, which would mean that :

(x == 1.0)
If you want to compare floating point values subtract them and see if the difference is very small.
- would be false - even though mathematically the test should return true.
!=
not equal. The reverse of equal. If they are not equal the expression has the value 1, if they are equal it has the value 0. Again, this test is not suitable for use with floating point numbers.
<
less than. If the item on the left is less than the one on the right the value of the expression is 1. If the left hand value is larger than or equal to the right hand one the expression gives 0. It is quite valid to compare floating point numbers in this way.
>
greater than. If the expression on the left is greater than the one on the right the result is 1. If the expression on the left is less than or equal to the one on the right the result is 0.
<=
less than or equal to. If the expression on the left is less than or equal to the one on the right you get 1, otherwise you get 0.
>=
greater than or equal to. If the value on the left is greater than or equal to the one on the right you get 1, otherwise it is 0.
!
not. This can be used to invert a particular value or expression, for example you can say !1, which is 0, or you could say: !(x=y) - which means the same as (x!=y). You use not when you want to invert the sense of an expression.

Combining Logical Operators

Sometimes we want to combine logical expressions, to make more complicated choices, for example to test for a window width being valid we have to test that it is greater than the minimum and less than the maximum. C provides additional operators to combine logical values :

&&
and. If the expressions each side of the && are true the result of the && is true. If one of them is false the result is false, for example
 (width > 0.5) && (width < 5.0) )

- this would be true if the width was valid according to our above description.

||
or. If either of the expressions each side of the || are true the result of the whole expression is true. The expression is only false if both expressions are false, for example :
 (width <= 0.5) | (width >= 5.0) )
De Morgans theorem is the basis of this.
- this would be true if the width was invalid according to our above description. Note that if we put an or in we have to also flip the conditional operators around as well.

Using these operators in conjunction with the if statement we can make decisions and change what our program will do in response to the data we get.

Lumping Code Together

We have decided that if the user gives a value outside our allowed range an error is generated and the value is then set to the appropriate maximum or minimum. To do this we have to do two statements which are selected on a particular condition, one to print out the message and the other to perform an assignment. You can do this by using the { and } characters. A number of statements lumped together between { and } characters is regarded as a single statement, so we do the following:

if ( width > 5.0 )	 {
	printf ( "Width too big, using maximum\n" ) ;
	width = 5.0 ;
}

The two statements are now a block, which is performed only if width is greater than 5.0. You can lump many hundreds of statements together in this way, the compiler does not mind. You can also put such blocks of code inside other blocks, this is called nesting.

The number of { and } characters must agree in your program, otherwise you will get strange and incomprehensible errors when the compiler hits the end of the file in the middle of a block or reaches the end of your program half way down your file!

I make things much easier to understand by indenting a particular block by a number of spaces, i.e. each time I open a block with the { character I move my left margin in a little way. I can then see at a glance whereabouts I am in the levels at any time.

Magic Numbers and #define

A magic number is a value with a special meaning. It will never be changed within the program, it is instead a constant which is used within it. When I write my glazing program I will include some magic numbers which give the maximum and minimum values for heights and widths. I could just use the values 0.5, 5.0, 0.75 and 3.0 - but these are not packed with meaning and make the program hard to change. If, for some reason, my maximum glass size becomes 4.5 metres I have to look all through the program and change only the appropriate values. I do not like the idea of "magic numbers" in programs, what I would like to do is replace each number with something a bit more meaningful.

We can do this by using a part of the compiler called the C pre-processor. The pre-processor sits between your program and the compiler. It can act as a kind of filter, responding to directives in your program, and doing things to the text before the compiler sees it. We are just going to look at the one directive at the moment, #define:

#define PI 3.141592654

Whenever the pre-processor sees PI it sends the compiler 3.141592654. This means that you can do things like:

circ = rad * 2 * PI ;

The item which follows the #define directive should be a sequence of characters. You then have a space, followed by another sequence of characters. Neither of the two sequences are allowed to contains spaces, this would get the pre-processor confused. Anywhere you use a magic number you should use a definition of this form, for example:

#define MAX_WIDTH 5.0

This makes your programs much easier to read, and also much easier to change.

There is a C convention that you always give the symbol you are defining in CAPITAL LETTERS. This is so that when you read your program you can tell which things have been defined.

Note that the #define directive is not intelligent, and you can therefore stop your program from working by getting your definition wrong. Consider the effect of:

#define MAX_WIDTH *this*will*cause*an*explosion!

There are loads more pre-processor directives which you can use, the other one which we have already seen is #include.

We can therefore modify our double glazing program as follows:

/* Double Glazing 2 */
/* This program calculates glass area and wood */
/* required by a double glazing salesman. */
/* Version 22.15 revision level 1.23 */
/* Rob Miles - University of Hull - 13/11/89 */
#include <stdio.h>
#define MAX_WIDTH 5.0
#define MIN_WIDTH 0.5 
#define MAX_HEIGHT 3.0 
#define MIN_HEIGHT 0.75

void main ()
{
	float width, height, glassarea, woodlength ;
	printf ( "Give the width of the window : " ) ;
	 scanf ( "%f", &width ) ;
	if (width < MIN_WIDTH) {
	printf ( "Width is too small.\n\n" ) ; 
	width = MIN_WIDTH ;
	}
	if (width > MAX_WIDTH) {
		printf ( "Width is too large.\n\n" ) ; 
		width = MAX_WIDTH ;
	}
	if (height < MIN_HEIGHT) {
		printf ( "Height is too small.\n\n" ) ; 
		height = MIN_HEIGHT ;
	}
	if (height > MAX_HEIGHT) {
		printf ( "Height is too large.\n\n" ) ; 
		height = MAX_HEIGHT ;
	}
	woodlength = 2 * (width + height) ; 
	glassarea = width * height ;
	printf ( "Glass : %f Wood : %f\n\n",
			woodlength, glassarea ) ;
}

This program fulfils our requirements. It will not use values incompatible with our specification. However I would still not call it perfect. If our salesman gives a bad height the program stops and needs to be re-run, with the height having to be entered again.

What we would really like is a way that we can repeatedly fetch values for the width and height until we get one which fits.

C allows us to do this by providing a looping constructions.

Loops

Conditional statements allow you to do something if a given condition is true. However often you want to repeat something while a particular condition is true, or a given number of times.

C has three ways of doing this, depending on precisely what you are trying to do. Note that we get three methods not because we need three but because they make life easier when you write the program (a bit like an attachment to our chainsaw to allow it to perform a particular task more easily). Most of the skill of programming involves picking the right tool or attachment to do the job in hand. (the rest is finding out why the tool didn't do what you expected it to!).

In the case of our program we want to repeatedly get numbers in until while we are getting duff ones, i.e. giving a proper number should cause our loop to stop. This means that if we get the number correctly first time the loop will execute just once. You might think that I have pulled a fast one here, all I have done is change:

Get values until you see one which is OK

into

Get values while they are not OK

Part of the art of programming is changing the way that you think about the problem to suit the way that the programming language can be told to solve it. Further details can be found in Zen and the Art of 68000 Assembler from Psychic Press at [[sterling]]150.

do -- while loop

In the case of our little C program we use the do -- while construction which looks like this:

do
	statement or block
while (condition) ;

This allows us to repeat a chunk of code until the condition at the end is true. Note that the test is performed after the statement or block, i.e. even if the test is bound to fail the statement is performed once.

A condition in this context is exactly the same as the condition in an if statement, raising the intriguing possibility of programs like:

#include <stdio.h>

void main ()
{
	do
		printf ( "hello mum\n" ) ;
	while (1) ;
}

This is a perfectly legal C program. How long it will run for is an interesting question, the answer contains elements of human psychology, energy futures and cosmology, i.e. it will run until:

  1. You get bored with it.
  2. Your electricity runs out.
  3. The universe implodes.

This is a chainsaw situation, not a powerful chainsaw situation. Just as it is possible with any old chainsaw to cut off your leg if you try really hard so it is possible to use any programming language to write a program which will never stop. It reminds me of my favourite shampoo instructions:

  1. Wet Your Hair
  2. Add Shampoo and Rub vigorously.
  3. Rinse with warm water.
  4. Repeat.

I wonder how many people there are out there still washing their hair at the moment.

while loop

Sometimes you want to decide whether or not to repeat the loop before you perform it. If you think about how the loop above works the test is done after the code to be repeated has been performed once. For our program this is exactly what we want, we need to ask for a value before we can decide whether or not it is valid. In order to be as flexible as possible C gives us another form of the loop construction which allows us to do the test first:

while (condition)
	statement or block

Note that C makes an attempt to reduce the number of keys you need to press to run the program by leaving out the word do. (if you put the do in the compiler will take great delight in giving you an error message - but you had already guessed that of course!).

for loop

Often you will want to repeat something a given number of times. The loop constructions we have given can be used to do this quite easily:

#include <stdio.h>
void main ()
{
	int i ;
	i = 1 ;
	while ( i < 11) { 
		printf ( "hello\n" ) ; 
		i = i + 1 ;
	}
}
The variable which controls things is often called the control variable, and is usually given the name i.
This useless program prints out hello 10 times. It does this by using a variable to control the loop. The variable is given an initial value (1) and then tested each time we go around the loop. The control variable is then increased for each pass through the statements. Eventually it will reach 11, at which point the loop terminates and our program stops.

C provides a construction to allow you to set up a loop of this form all in one:

for ( setup ; finish test ; update ) {
	things we want to do a given
	number of times
}

We could use this to re-write the above program as:

#include <stdio.h>
void main ()
{
	int i ;
	for ( i=0 ; i != 11 ; i = i+1 ) {
		printf ( "hello\n" ) ;
	}
}

The setup puts a value into the control variable which it will start with. The test is a condition which must be true for the for -- loop to continue. The update is the statement which is performed to update the control variable at the end of each loop. Note that the three elements are separated by semicolons. The precise sequence of events is as follows:

Writing a loop in this way is quicker and simpler than using a form of while because it keeps all the elements of the loop in one place, instead of leaving them spread about the program. This means that you are less likely forget to do something like give the control variable an initial value, or update it.

If you are so stupid as to mess around with the value of the control variable in the loop you can expect your program to do stupid things, i.e. if you put i back to 0 within the loop it will run forever.....

Breaking Out of Loops

Sometimes you may want to escape from a loop whilst you are in the middle of it, i.e. your program may decide that there is no need or point to go on and wishes to tunnel out of the loop and continue the program from the statement after it.

You can do this with the break statement. This is a command to leap out of the loop immediately. Your program would usually make some form of decision to quit in this way. I find it most useful so that I can provide a get the hell out of here option in the middle of something, for example in the following program snippet the variable aborted, normally 0 becomes 1 when the loop has to be abandoned and the variable runningOK, normally 1, becomes 0 when it is time to finish normally.

while (runningOK) {
	complex stuff
	....
	if (aborted) {
		break ;
	}
	....
	more complex stuff
	....
}
....
bit we get to if aborted becomes true
....

Note that we are using two variables as switches, they do not hold values as such, they are actually used to represent states within the program as it runs. This is a standard programming trick that you will find very useful.

You can break out of any of the three kinds of loop. In every case the program continues running at the statement after the last statement of the loop.

Going Back to the Top of a Loop

Every now and then you will want to go back to the top of a loop and do it all again. This happens when you have gone as far down the statements as you need to. C provides the continue statement which says something along the lines of:

Please do not go any further down this time round the loop. Go back to the top of the loop, do all the updating and stuff and go around if you are supposed to.

In the following program the variable Done_All_We_Need_This_Time is set when we have gone as far down the loop as we need to.

for ( item = 1 ; item < Total_Items ; item=item+1 ) {
	.....
	item processing stuff
	....
	if (Done_All_We_Need_This_Time) {
		continue ;
	....
	additional item processing stuff
	....
}

The continue causes the program to re-run the loop with the next value of item if it is OK to do so. You can regard it as a move to step 2 in the list above.

More Complicated Decisions

We can now think about using a loop to test for a valid width or height. Essentially we want to keep asking the user for a value until we get one which is OK; i.e. if you get a value which is larger than the maximum or smaller than the minimum ask for another.

To do this we have to combine two tests to see if the value is OK. Our loop should continue to run if:

width > MAX_WIDTH

or

width < MIN_WIDTH

To perform this test we use one of the logical operators described above to write a condition which will be true if the width is invalid:

( (width < MIN_WIDTH) || (width > MAX_WIDTH) )

- note the profuse use of brackets. You must put these in.

Complete Glazing Program

This is a complete solution to the glazing problem. It uses all the tricks mentioned above, plus a few which are covered below.

/* Complete Double Glazing Program */
/* Rob Miles Nov. 1990  */

#include <stdio.h>

/* Define our window size range */
#define MAX_HEIGHT 3.0
#define MAX_WIDTH 5.0
#define MIN_HEIGHT 0.75 
#define MIN_WIDTH 0.5

/* Define a few costs */
#define COST_TO_MAKE 2.00 
#define WOOD_COST 2.00 
#define GLASS_COST 3.00 
#define MARKUP_FACTOR 2.25

/* Define the maximum number of windows on our house  */
#define MAX_WINDOWS 10

/* Program variables :  */
/* width - width of current window  */
/* height - height of current window  */
/* window_cost - cost to make the window	*/
/* window_sell - amount we sell the window for	*/
/* house_cost - cost to do the whole house  */
/* house_sell - amount we sell the house job for  */
float width, height, window_cost, window_sell, house_cost, house_sell ;

/* no_of_windows - number of windows in the house	*/
/* window_count - counter for current window	*/
int no_of_windows, window_count ;
void main ()
{
	printf ( "Double Glazing House Calculator\n" ) ; 
	printf ( "Rob Miles November 1990\n" ) ;

	do {
		printf ( "Give the number of windows : " ) ;
		scanf ( "%d", &no_of_windows ) ;
	} while ( (no_of_windows <= 0) ||
                              (no_of_windows > MAX_WINDOWS) ) ;

	house_cost = 0.0 ;
	house_sell = 0.0 ;

	for (window_count = 1 ; window_count <= no_of_windows ;
				window_count++ ) {
		printf ( "Window %d details.\n", window_count ) ;
		do {
			printf ( " enter the width : " ) ;
			scanf ( "%f", &width ) ;
		} while ( (width < MIN_WIDTH) ||
			(width > MAX_WIDTH) ) ;
		do {
			printf ( " enter the height : " ) ; 
			scanf ( "%f", &height ) ;
		} while ( (height > MIN_HEIGHT) || 
			(height < MAX_HEIGHT) ) ;
		window_cost = WOOD_COST * 2 * (width + height) ; 			window_sell = window_cost * MARKUP_FACTOR ;
		printf ( "Window %d costs %.2f, sells for %.2f.\n\n", 				window_count, window_cost, window_sell ) ;
		house_cost += window_cost ;
		house_sell += window_sell ;
	}
	printf ( "\nTotal cost to do all %d windows : %.2f.\n",
		no_of_windows, house_cost ) ;
	printf ( "\nTotal sale price for all %d windows : %.2f.\n",
		no_of_windows, house_sell ) ;
	}

Operator Shorthand

So far we have looked at operators which appear in expressions and work on two operands, e.g.

window_count = window_count + 1

In this case the operator is + and is operating on the variable window_count and the value 1. The purpose of the above statement is to add 1 to the variable window_count. However, it is a rather long winded way of expressing this, both in terms of what we have to type and what the computer will actually do when it runs the program. C allows us to be more terse if we wish, the line :

window_count++

- would do the same thing. We can express ourselves more succinctly and the compiler can generate more efficient code because it now knows that what we are doing is adding one to a particular variable. The ++ is called a unary operator, because it works on just one operand. It causes the value in that operand to be increased by one. There is a corresponding -- operator which can be used to decrease (decrement) variables. You can see examples of this construction in the for loop definition in the example above.

The other shorthand which we use is when we add a particular value to a variable. We could put :

house_cost = house_cost + window_cost

This is perfectly OK, but again is rather long winded. C has some additional operators which allow us to shorten this to:

house_cost += window_cost

The += operator combines addition and the assignment, so that the value in house_cost is increased by window_cost. Some other shorthand operators are:

a += b the value in a is replaced a+b. a -= b the value in a is replaced by a - b. a /= b the value in a is replaced by a / b. a *= b the value in a is replaced by a * b.

There are other combination operators, I will leave you to find them!

Statements and Values

One of the really funky things about C is that all statements return a value, which you can use in another statement if you like. Most of the time you will ignore this value, which is OK, but sometimes it can be very useful, particularly when we get around to deciding things (see later). In order to show how this is done, consider the following:

i = (j=0) ;

This is perfectly legal (and perhaps even sensible) C. It has the effect of setting both i and j to 0. An assignment statement always returns the value which is being assigned (i.e. the bit on the right of the gozzinta). This value can then be used as a value or operand. If you do this put brackets around the statement which is working as a value, this makes the whole thing much clearer for both you and the compiler!

When you consider operators like ++ there is possible ambiguity, in that you do not know if you get the value before or after the increment. C provides a way of doing it either way, depending on which effect you want. You determine whether you want to see the value before or after the sum by the position of the ++ :

i++ means give me the value before the increment. ++i means give me the value after the increment.

As an example :

int i = 2, j ;
j = ++i ;

- would make j equal to 3. The other special operators, += etc all return the value after the operator has been performed.

One neat place to use this facility is when you are updating a value and testing it at the same time, for example instead of :

i = i - 1 ;
if ( i == 0 ) {
	printf ( "Finished\n" ) ;
}

you could put :

if ( --i == 0 ) {
	printf ( "Finished\n" ) ;
}

An important thing to note is that not only does this facility make the program shorter, and therefore help the programmer, but it also allows the compiler to produce much faster programs. In the first instance the compiler would tend to produce a larger and slower program, because it would treat the two statements as separate. In the second case, because it knows that we are going to use the value of the sum in an expression it might not have to fetch it again.

This is why people say that the C language is faster than some others; it allows us to write more expressive programs which can be mapped more easily onto the actual machine which will run them. The bad new is that this expressiveness can also be used to write incomprehensible programs as far as people are concerned. In the final analysis, I am happier if the program is a little less efficient, but is easier for people to understand!

Neater Printing

Note that the way that a number is printed does not affect how it is stored in the program, it just tells printf to cut short the printing at a particular point.
If you have run any of the above programs you will by now have discovered that the way in which numbers are printed leaves much to be desired. Integers seem to come out OK, but real numbers seem to have a particularly large number of decimal places, which are not required for our program. You can give the printf function more information about how a value is to be printed, so that the values that come out look a lot better. For any item you can give the number of character positions to be used to print the number. In addition, for floating point values you can specify the precision, i.e. how many decimal places to print.

You tell printf what you want by putting values after the % but before the letter which identifies the type of variable to be printed. The general form is:

%width.precisiontype

Note that if you are printing integer types you do not need to give the point or the precision value, just the width.

Here are some examples, with explanations:

%5d - print the decimal integer in a space five characters wide. If the value is smaller than five characters, pad out with extra spaces.

%6.2f - print the floating point value in a space six characters wide, with two decimal places.

%.2f - print the floating point value in a space wide enough to it, with two decimal places.

There are other ways in which you can control the printing process, for example you can select whether "pad out" spaces are inserted to the left or right of the value. I will leave you to find out these! Note also that you can specify the print width of any item, even a piece of text, which makes printing in columns very easy.


Rob Miles, R.S.Miles@e-eng.hull.ac.uk, Electronic Engineering
HTML by Bronwen Reid, July 1995