ibook.pub-mastering-c-programming
ibook.pub-mastering-c-programming
Mastering
C Programming
Palgrave Master Series
You can receive future titles in this series as they are published by placing a standing order.
Please contact your bookseller or, in case of difficulty, write to us at the address below with
your name and address, the title of the series and the ISBN quoted above.
Customer Services Department, Macmillan Distribution Ltd
Houndmills, Basingstoke, Hampshire RG21 6XS, England
0 C Programming
Mastering
W. Arthur Chapman
*
© W. Arthur Chapman 1991
All rights reserved. No reproduction, copy or transmission of
this publication may be made without written permission.
No paragraph of this publication may be reproduced, copied or
transmitted save with written permission or in accordance with
the provisions of the Copyright, Designs and Patents Act 1988,
or under the terms of any licence permitting limited copying
issued by the Copyright Licensing Agency, 90Tottenham Court
Road, London W1T 4LP.
Any person who does any unauthorised act in relation to this
publication may be liable to criminal prosecution and civil
claims for damages.
The author has asserted his right to be identified as the author of this
work. in accordance with the Copyright, Designs and Patents Act 1988.
Published by
PALGRAVE MACMILLAN
Houndmills, Basingstoke, Hampshire RG21 6XS and
175 Fifth Avenue, New York, N.Y. 10010
Companies and representatives throughout the world
PALGRAVE MACMILLAN is the global academic imprint of the Palgrave
Macmillan division of St. Martin's Press, LLC and of Palgrave Macmillan Ltd.
MacmillanCD is a registered trademark. in the United States, United Kingdom
and other countries. Palgrave is a registered trademark. in the European
Union and other countries.
2. Towards C
2.1 Introduction 27
2.2 The first C program 27
2.3 C program structure 31
2.4 Functions - a first look 33
2.5 From code to results 37
Summary 40
Exercises 41
7. The calculator
7.1 Introduction 153
7.2 Problem definition 153
Exercises 165
Appendix A
The ASCII codes 271
Appendix B
The line editor 272
Appendix C
The bridge tutor 288
vm Contents
Appendix D
Further reading 302
Index 303
0 List of Figures and Tables
FIGURES
TABLES
points and suggesting opening bids. For the most part no knowledge of
bridge is necessary but a familiarity with cards and card games such as
whist would be helpful. These latter two programs are listed in full, and
their functions are discussed in detail, in Appendices Band C respectively.
Most chapters conclude with a summary which highlights the main points
covered in the chapter and which serves to act as a revision aid to the
reader. In addition, most chapters contain exercises which are designed to
reinforce the topics covered and to develop the readers understanding of
C. Some of these exercises refer to the larger programs and as such the
answers can be found in the relevant program listings.
As you work through the material presented here you should develop a
good understanding of C and C programming. If by the time you have
completed your study of this text you have a desire to continue programm-
ing inC, wish to move on to more advanced aspects of the language, and
have even more importantly found that C programming is both challenging
and also fun, then the book will have achieved its purpose.
"Some problems are just too complicated for rational logical solutions. They
admit of insights, not answers." J. B. Wiesner
1.1 PRELIMINARIES
In this chapter we will be mainly concerned with the important topic of problem
solving. We will be looking at ways in which problems can be tackled and the
most productive ways of obtaining solutions - we will be concentrating on
problems which can be solved and for which "rational logical solutions" can be
found. In the process of working through this chapter you will be introduced to
some techniques which enable well structured programs to be developed. This
includes the idea of top-down design, the use of stepwise refinement and the
writing of algorithms. The fundamental control structures of procedural
languages will be introduced and their relevance for C indicated. A method of
writing algorithms using pseudocode will be developed and will be applied to
some programming tasks which we will be discussing in greater depth in later
chapters.
We will be looking at both the art and the science of programming so that by
the end of this chapter you will be able to develop an outline solution to most
problems. In future chapters these techniques will be extended to enable you to
write programs in C. Let us begin, though, by forgetting about the details of
computer programming and look first of all at problem solving in more general
terms.
2 Mastering C Programming
Computer programming
The art of problem solving is difficult to define. However the task of problem
solving, which is to find a solution to a particular problem, seems all too
obvious. This appears easy enough until you start the process. Some problems
are easy to solve, others are far more difficult. Problems come in all shapes and
sizes. They cover such diverse tasks as: getting up in the morning (a problem to
most people), preparing breakfast or making a cup of coffee, existing on
unemployment benefit, achieving world peace, saving the tropical rain forests,
or solving the all-embracing environmental problems.
We will concentrate on some more mundane tasks and will begin by taking a
look at a reasonably simple everyday problem. You are on the pavement at the
side of a busy road. You are in a hurry and you need to cross the road. A hundred
metres away, in the opposite direction, is a set of traffic lights (see Figure 1.1).
What do you do? Think about the possibilities, about what options are open to
you (which ones are safest, which ones are quickest) before reading on.
How did you approach this problem? Well, first of all there is no right answer;
there are many possibilities, most of which have their good and bad points.
Perhaps you decided to wait for a break in the traffic and then make a dash for it!
This solution is not to be recommended, especially if you have young children
or an elderly person with you. Alternatively you may have decided that the traffic
was too heavy and so walking up to the traffic lights was the best option - you
could afford to be a few minutes late rather than risk ending up in a hospital bed
or worse. Again you may have decided that it was rather a silly problem and
rather than try to solve it in advance you would wait until you next had to cross
a road. Whilst this solution (putting it oft) might be satisfactory in this case, it
cannot be allowed in computer programming - problems need to be solved
before they arise. However you may have decided that this problem was rather
silly on the grounds that you were not given enough information. If you came
up with this last point then give yourself a pat on the back.
One of the most important points which this seemingly simple problem
should have highlighted is that often you are not given all of the necessary facts.
For example:
If it is a Sunday then possibly walking across the road would be the best
option -observing the Green Cross Code of course.
The solution will obviously be different if it is the rush hour rather than
1.30 am.
• I neglected to tell you that there is a subway only a few metres away.
Even these few simple and obvious comments should help to underline the
important point that a problem may not be well specified and that in deciding on
a solution you may need to make some assumptions. If this is indeed the case
then these assumptions must be made explicit from the outset. Discovering
hidden assumptions, or making explicit assumptions which must be made are
part of the task of understanding the problem. Another vital part of the process
of understanding a problem involves drawing up a specification of the problem.
implemented. In computer terms this will involve translating the algorithm into
a computer program. Finally the plan is carried out and note taken of its
successes (and failures). With a computer program this will mean running it and
evaluating the accuracy of the results.
The stages in the problem solving process will generally be carried out in the
order given above. However in practice the frrst two stages may be mixed up and
a sufficiently detailed understanding of the problem may only be possible once
the process of devising a solution has begun.
• devising a solution
There are various approaches to the task of devising a solution. One of the most
common involves a 'top down' methodology. This means starting from the
problem definition and working step by step towards a solution. At each step in
the process the problem is broken up into smaller and smaller 'chunks'. This
process of stepwise rermement is then continued until a set of easily-solved
sub-problems has been arrived at
Charlie's desk
Charlie, a fresher of three weeks' standing, has been pondering the difficulties of
working at a tiny table with less than stable legs and is out searching for a desk
as a solution to 'all' his problems. Being of slender means (he is still awaiting
Beginning with problems 5
his grant) he drops into a shop littered with bric-tl-brac and second-hand goods of
all kinds.
While searching amongst the debris of bird cages, shooting sticks and battered
suitcases, he discovers the answer to his prayers. There in the corner, in a dusty
plastic bag, is a 'Student Desk', a self-assembly job at what he hopes is a
knock-down price. Summoning the shop assistant he enquires the price. 'That's
five pounds, sir' is the response to the vitally important question. So, dipping
into a pocket of his tattered denims he pulls out five pound coins and, not
believing his good fortune, walks out into the chilly October air with his newly
acquired possession.
Arriving back at the flat he decides to celebrate his astounding good luck by
having filtered coffee - there is just enough to make one last pot. Once the
coffee is on he starts the process of unpacking his desk. He carefully lays out the
pieces on the not very spacious floor and searches through the odds and ends for
the instructions. At last, in a packet containing assorted screws he finds,
somewhat tattered and torn, the crucial pieces of paper. He smooths them out and
putting on his battered spectacles peruses the words of wisdom.
After sorting through the bits and pieces of the kit, checking the contents
(luckily nothing appeared to be missing) and after an hour or so's work with the
scraps of instructions he finally came up with what seemed like a usable set of
instructions.
Charlie's instructions
1. Bookcase
2. Cupboard
Cupboard Door
Drawer
Glue the drawer wrap at the joints and glue four dowels into the holes
provided.
Glue the drawer front.
Assemble and leave until the glue sets (24 hours approx.).
When dry wipe over with a damp cloth to remove excess glue.
Fit handle.
3. Final Assembly
4. To finish
With the help of his own instructions and after a few bouts of trial and error
Charlie managed to complete the task and a day or two later was seen hard at
work at his newly-acquired masterpiece.
The strategy which Charlie used to solve the problem of assembling the desk,
and which the makers had also suggested, was that of stepwise refinement. The
task was broken up into a number of jobs, each of which could be carried out
separately. Once all the tasks had been completed the problem was solved and the
desk finished.
Beginning with problems 7
Before you begin assembly check the contents of the kit, they are li~.
CONTENTS
Desk Top
Bookcase End Cupboard Side
Bookcase Shelf Dowel
Bookcase Base Handle
Bookcase Back Screw (1 1/2 ")
Cupboard Side Screw (1")
Door Cupboard Back
Glue Sachet DoorKnob
METHOD
//
~
-
- .; 1 3/4" screws
8 Mastering C Programming
1.4 ALGORITHM
The above example illustrates some of the techniques of stepwise refinement, the
breaking up of the task into sub-tasks, the refining of these sub-tasks and the
logical nature of the solution. What Charlie ended up with could be described as
an algorithm for assembling the desk. This algorithm gives a complete set of
instructions which, if followed through in the correct order, will result in a
successful solution to the original problem - the desk is assembled. In the
context of computer programming the steps of problem definition and analysis,
stepwise refinement and the production of an algorithm should ideally be carried
out before any attempt is made to write the program itself. These rules are not
hard and fast but, if you follow this procedure when writing any program, you
are more likely to produce neat, workable and readable code than if you start by
'hammering away at the keyboard'.
In order to illustrate how the above procedure might work out in practice we are
going to work through a couple of examples. The second example is a
computing one and is one which we shall be working on throughout the book.
However we begin by applying stepwise refinement to the task of baking bread.
You will find that there are surprising similarities between such diverse tasks as.
making bread and writing a complex computer program.
First of all we need to define the problem or task. How much bread are we
going to make? Do we want wholemeal or white, rolls or loaves? If loaves then
what kind of loaves? These are just some of the questions which need to be
answered before we can even begin to work on a solution.
We can now start on the process of problem solving by making a list of the
various tasks which will form the basis for our 'solution'. In this case there are
four main tasks:
1. Initial jobs
2. Making the dough
Beginning with problems 9
3. Baking
4. Removing and cooling the loaves
Now that this first level of stepwise refinement is complete we can start on
the process of further refinement. To do this we look at each step in turn and
where possible break it up into smaller steps. This produces our first level of
refinement and in this case might result in the following:
1. Initial jobs
Wash hands
Find a recipe
Collect ingredients
Assemble cooking utensils
3. Baking
Switch on oven
Check oven temperature (repeat until correct temperature is reached)
Place tins in the oven
Wait until bread is baked
After this first level of stepwise refinement you will see that we have a
reasonably comprehensive algorithm. However there are still lots of details
which need to be filled in. One important point about the process of stepwise
refinement is that it is not necessary to fill in all the steps in the same amount
of detail at the same time. If some of the tasks are still hazy then the steps
involved can be filled in later.
Each of the above steps can be expanded as necessary with the help of a
recipe. As an example we list the next level of refinement for the important task
of making the dough.
10 Mastering C Programming
Rising stage
Shape the dough into a ball.
Place in a bowl and cover with a clean damp cloth.
Leave in a warm place until dough has doubled in size.
Allow to prove
Cover with a clean damp cloth.
Wait until the dough reaches the top of the tins.
The expanded algorithm for making the dough, given above, is sufficiently
detailed to enable the dough to be made. In some cases another level or two of
refinement might be required. {Incidently if you want to try the recipe and check
whether the algorithm works you can find the complete recipe from which this is
adapted in The Cookery Year, Reader's Digest, pp. 375- 6.)
Beginning with problems 11
1.5 PROGRAMMING
The examples given above should have started you thinking about how to apply
the principles to the task of writing a program, in particular about writing
programs in C. In this and the following sections we are going to look at the
various tasks which frequently occur in algorithms. This will lead on to a
discussion of program control structures and the development of pseudocode.
Let's begin by taking another look at the algorithm for making the dough. An
obvious first point is that steps are written down in the order in which they are
to be carried out. Notice next that the algorithm is broken up into discrete
sections. Thirdly you will notice that some tasks involve repeating something
until a certain condition is met. For example 'Cut up lard and rub into the flour
with the fingertips until the mixture resembles fine breadcrumbs' or 'Leave in a
warm place until doug~ has doubled in size'. Lastly a selection instruction
appears: 'If using dried yeast' and 'If using fresh yeast'. At other points a
selection process is implied or assumed. For example, if the oven isn't already
on, then switch it on (or light the gas).
These simple examples from a non-computing problem form the basis for
program structure and control. They relate very closely to the ideas of
sequence, functions, repetition and decisions. We now take a look at the
way these constructs can be applied to programming in C.
Functions are closely related to the simple sequential structure which we have
just been looking at. In fact in Figure 1.3 any statement could be replaced by a
function which carries out the same task as the statement being replaced.
However functions are by no means confined to such simple uses as we shall see
shortly.
statement 1
statement2
statements are
executed
sequentially
statementn
statement n+ 1
A function is •••
a collection of statements which
perform a well-defined task, or
tasks.
read new_number
add new_number to sum
Without repetition we would need to type in these two statements one hundred
times. With repetition we can condense the task to:
Notice also that with this structure it is a simple matter to change the number
of numbers by just altering the while condition (e.g. count is less than 1000).
important of these is that there must be some condition which is modified within
the loop and which enables the loop to be terminated: for example, stop reading
characters when a new line has been entered. Alternatively a counter can be used
to keep track of the number of times the loop has been repeated. In such cases
the execution of the loop may be terminated when a particular value has been
reached.
As we have noted, the constructs which we have been looking at are essential
elements in the design of C programs. In fact they are of central importance to
any programming language. However, before we look at their detailed
implementation in C, we need to examine how they can be used in the
development of algorithms.
1.6 PSEUDOCODE
The algorithms which we will be developing will be written for the most part in
a semi-formal system known as pseudocode. We have already used some
examples of pseudocode when we discussed repetition. For example we used
phrases like while and set ••• to •••. The object of pseudocode is to allow
these and similar control structures to be represented in a standard way without
the formality of the full syntax of the programming language.
Let's return to the bread making algorithm. Recall that we had a couple of
examples of selection where the actions taken depended upon the type of yeast
which we were using. Other examples of this type might be:
At a first glance the second example does not appear to be of this form.
However with a little imagination we can rearrange it to fit into our semi-formal
structure. Thus we could have:
The yeast example could be thought of in this form, in which case we would
have two successive selection statements. However a closer look at them reveals
that in fact they are alternatives. We will not use both dried yeast and fresh yeast.
So a second version of the selection construct has the form:
Again if the task consists of more than one activity then each activity will be
placed on a separate line and they will be indented as for the if construct This
loop works by testing the condition and if it is 1RUE performing the task. The
condition is then tested again and so on until the condition becomes FALSE at
which point execution of the loop terminates.
Another pseudocode construct which we have used is set ••• to. This is used
to assign a value to a name. So in the earlier example we had
which assigns zero to both sum and count. A value other than zero can be
assigned to a name and this value need not be confined to a numerical one.
16 Mastering C Programming
Now that we have outlined some of the stages involved in problem solving and
looked at some non-computing examples we can tum our attention to a
computing problem. Although we will be using a variety of short programming
examples to illustrate the various aspects of C we will also be using a few rather
longer programs. These longer examples will appear from time to time in the
book and as new control structures and data stuctures are met their relevance to
these larger programs will become apparent. By the end of the book you should
have three of four fully-fledged programs which might even be of use to you! As
part of the learning approach some of the functions will be left as exercises for
the reader to develop. You should try writing your own functions before turning
to the solutions which appear in the relevant Appendices. We will be developing
three main programs and a number of smaller ones. The most important
programs are a simple calculator, a bridge tutor and a line editor.
Specification
A line editor uses line numbers to identify lines of text in the text file and to
prevent confusion we need to be able to display a complete line on the screen,
which in turn means that we need to think a little about the length of lines.
Normal screens will have an 80 character display width which means that, if a
line length is greater than 80 characters, we will need to make a decision about
what to do with the excess. One option is to not allow a line to be greater than
80 characters and to produce a warning message if this occurs. A second option
is to start a new line. This will mean, if we are inserting a line, that subsequent
lines will need to be moved down a line and the lines renumbered accordingly.
Beginning with problems 17
We will adopt this second option, although to allow for the display of line
numbers we will restrict the line length to 75 characters.
The functions outlined above enable us to sketch out a rough diagram of the
program structure (see Figure 1.5). We have not mentioned visual representations
of algorithms until now, but you will find that often a quick sketch will provide
greater insights into how to devise a solution. (Charlie's instructions were
greatly helped by the presence of drawings indicating how the various parts of
the desk should fit together.) The sketch should indicate the relationship between
the various processes in an algorithm and should distinguish clearly between
processes and data; in Figure 1.5, for example, data are represented by rectangular
boxes whereas processes are represented by ellipses.
Even this very rough sketch is a useful first step in designing an algorithm. It
highlights the functions involved and gives an overall picture of the final
program. However, many details are left unexplained and a number of questions
are raised. These are useful, in fact vital, questions which need to be confronted
before much work can be done on designing the algorithm. For example, once
the file has been updated are we to exit from the editor or should this be a
separate task? What files are we going to use? Ideally we would want to be able
to edit any text file. For the moment, though, we will assume that the file to be
edited is called data.txt How is the text to be stored in memory? Do we want a
prompt, giving the possible edit options, to be always in view? Some of these
questions require a detailed knowledge of C before we can attempt to answer
them. However, even if we cannot answer them at present, they are useful in
helping us to think through the exact nature of the task and in helping to clarify
our thoughts concerning possible solutions and ways of implementing the
functions of the line editor.
Since the editor will be working on lines we will need some means of
identifying which lines are to be edited. The simplest way is to number each
line. This number can either be stored in the file or, better, displayed by the
program as and when necessary. The line number is only relevant during the
editing process so there is no need to store it with the text. Assume that there are
n lines in the text and that we have two integers, n 1 and n2, which represent
lines in the text (1 <= n1 <= n, 1 <= n2 <= n) then we can edit:
18 Mastering C Programming
At times we may wish to edit the complete file rather than selected lines. It
would therefore be convenient if another special character was set aside for this
task (e.g. %). Finally, if we only wish to edit the current line then all that
should be entered is the relevant command. If no previous line numbers have
been specified then the current line should be defmed as line 1. We thus have the
valid options shown in Table 1.1 for specifying the lines to edit:
Having decided upon how we are to specify which lines are to be edited our next
task is to decide upon the edit options required. All edit options should be
selected by means of a single letter, ideally the initial letter of the chosen option.
(This may be case-sensitive, i.e. it may only accept lower-case letters, or
conversely only upper-case letters, but it would be better if it were not.) Table
1.2 gives a list of the basic options, based on the earlier dicussions, together
with the character associated with each.
The program structure of the line editor given in Figure 1.5, together with the
above discussion, enables us to make an attempt at constructing an outline
algorithm. This could take the following form:
text in memory
~ ....--~w-·t_oo~rex_t~
~ mmemory
updared rext
file
Program flow
Dataflow
20 Mastering C Programming
4. Processing section
while command is not quit do
display command prompt (e.g. :>)
get the command and line details
check command and line details
if error then print message and quit
process the command
end while
5. Output section
quit
This algorithm, although still very bare, provides a useful start in the process
of program writing. A number of questions are still outstanding. For example,
what happens if the file does not exist? How are we to display the command
summary? We will need to consider how to make sense of the command and
check for illegal constructs. How are we going to number the lines? Can we
make the program versatile enough to allow for the editing of any text file?
Most of these questions cannot be answered until we have delved a little deeper
into control structures in general and C constructs in particular.
We have finally reached the stage where we can begin to think about computer
programs and it won't be long before you are deep into the heart of C. However
we haven't yet said what a computer program is, so let's rectify that immediately.
A computer program goes through various stages during its life and it is to a
short discussion of these stages that we now turn.
Like all animate objects and like many man-made creations a computer program
goes through a particular life cycle. The cycle may not be divisible into seven
clearly defined ages but even so there are useful analogies between the ages of
man according to Shakespeare and the stages in the life of a program (see Figure
1.6). Some of these stages we have already had a glimpse of; we will be looking
at the others later on.
The life cycle of a program begins when you decide that a particular problem
needs solving, or when you are asked to write a program to do something, or
when you suddenly feel inspired and are galvanised into creativity!
At this stage the problem will gradually be tightened up until you know what
assumptions are being made and what, if any, extra information needs to be
supplied. By the end of this process of problem analysis you will have arrived at
22 Mastering C Programming
a firm definition of the problem and have a reasonably clear idea of what fonn
the algorithm will take. The details may still need to be worked out but the basic
form of the solution should be fixed.
Childhood (Algorithm)
This is one of the most important parts of the whole process, as is the case with
all childhood. It is the formative stage of development and time well spent here
will be amply rewarded later on. Various schemes for arriving at an algorithm are
available, we have looked briefly at the use of stepwise refinement and
pseudocode as one way of doing so. Whatever the exact form, whether it is
through stepwise refinement, structure diagrams, flow charts, pseudocode or a
combination of them all, each one relies to a large extent on a systematic and
logical approach to the problem. The algorithm will be quite general and allow
for coding in a variety of languages. However on many occasions the language
will have already been chosen and so the algorithm will be written with a
specific language in mind. This should not worry you, as a good algorithm
should be suitable as a basis for coding the solution in any similar computer
language.
At last the stage has been reached when the code can be written and the program
in all its glory springs to life! Although much of the hard work has been done,
the program still needs to go through one very important sub-stage before it
really 'comes of age'. This involves the testing and debugging of the program.
All programs should be thoroughly tested before being let out into the big wide
world. This is often tedious and the most frequent question on the programmer's
lips at this stage is 'Why?' Even in the most carefully written programs bugs are
sure to appear -little eccentricities, or not so little- which need to be ironed out
by careful and systematic testing. Don't skimp this stage but make sure that you
always rigorously test each and every program. You will find that in the long
run it will be well worth while.
The fully fledged adult program is now released on an unsuspecting world and is
ready to perform all manner of wonders at the press of a key or the click of a
mouse button! Although it is fully operational there may be times when you
wish to delve into its depths or when some other talented programmer wishes to
pick your program's brains! Whatever the reason, program documentation should
go hand in hand with program development. Exactly what is contained in the
documentation will depend to some extent on the type of program that has been
written. However, at the very least, there should be a statement of the problem
(or the purpose of the program), a list of any assumptions made and any extra
Beginning with problems 23
problem definition
running _I
results
24 Mastering C Programming
information required, test data and test results should be included as well as the
algorithm and a program listing annotated with comments.
During this period of life changes to the program are often made. Maybe you
decide that there are other tasks which the program could perform and so you
make some modifications. Whatever the reasons are for changing the program,
you should always start with the algorithm, except in the simplest of cases.
Make the necessary changes to the algorithm before moving on to modifying the
code itself. It is at this time that the benefits of a well-structured algorithm
become apparent. You will find that changes can more easily be made without
causing disastrous problems with the logic of the program. Time and money
will also both be saved at this stage if a well structured algorithm is at hand.
Death
Strictly speaking, old programs never die, they just become obsolete. By the
time the program has reached this stage it has carried out the tasks for which it
was designed. Perhaps it was only wanted for a 'one off job' and is now surplus
to requirements. The program is now being superseded either because of software
developments, new languages, better implementations, or because of advances in
the hardware. Its use now is confined to the occasional nostalgic glance or to
some gentle cannibalism when a well-tested routine is resurrected for use in
another program.
SUMMARY
EXERCISES
c. Wallpaper a room. (Assume that you already have the wallpaper, paste
etc.)
2. Devise an algorithm for working out how many times one number divides
into another by using repeated subtraction. Take care of any special cases.
5. Try the following short questions to make sure that you have understood the
last section.
6. Use the methods developed in this chapter to derive algorithms for the
following problems.
"Really good programs live forever ... at least as long as the hardware, maybe
even longer." Charles Simonyi
2.1 INTRODUCTION
One of the aims, then, of this chapter is to introduce the basic structure, and
some of the key elements, of C programs. A second aim is to take you through
the various stages involved in moving from a program written in C to code
which can be executed by a computer. We will take a broad look at the processes
of editing, compiling and linking. Finally you will have an opportunity to take
a closer look at these processes as they are implemented on your own system.
Let us examine the structure of about the simplest C program. (You will find
variations on this program in most books on C, so why change now? Even in
computing there are traditions!)
Program 2.1
/* greeting.c */
/* The first c program - greetings */
28 Mastering C Programming
#include <stdio.h>
main()
Even if you have no previous experience of programming (in any language) you
may well be able to guess what the result of running this program would be.
You will have the chance to try the program for yourself later on and to see if
your guess was correct. Meanwhile we will go through the program line by line
to get an idea of the structure of the program and to find out what each line
means.
I* greeting.c *I
The program begins with a comment. In the C language comments are enclosed
within the two combinations of a slash and an asterisk, that is I * and * I .
Anything appearing between these pairs of symbols is ignored by the C
compiler and has no effect on the execution of the program. However comments
are extremely useful to the programmer as well as to others who might be
reading the program. Comments should be used as often as necessary within a
program. They form a very necessary part of the program documentation which
we discussed very briefly in the previous chapter. This particular comment
(greeting.c) indicates the name of the source file of this program. (The source file
is the file containing the coded program as typed into the computer, and as it
appears above.)
Note that the name of the program consists of a (meaningful) name plus a . c
extension to the name. The . c indicates that the file is a C source file. You will
find that this is common practice in C programming and it enables you to see at
a glance which files contain C programs. In addition most C compilers look for
filenames with this . c extension when starting the compilation process. The
filename should as far as possible be meaningful. Filenames such a 1 . c, b 6 . c
etc. are not very helpful. However it is not always possible to use the name we
would like since in most operating environments there is a restriction on the
length of a filename (e.g. a maximum of 8 character in MS-DOS). Within these
restrictions, though, try to use meaningful names and only shorten them when
necessary.
The second line is also a comment; it doesn't tell us much- well, there is not a
lot to say about this first program anyway! More informative comments would
Towards C 29
There are two more points worth noting before we leave the topic of
comments. Firstly, comments need not be restricted to a single line; remember
that all characters (including a newline or return character) are part of the
comment until the *I sequence is encountered. Thus we could have written the
first couple of lines of the program as
/* greeting.c
The first c program - greetings */
However the original form is slightly clearer, in that it separates off different
types of comment (the program name from its description).
#include <stdio.h>
If angle brackets are used then the compiler will search for the file (filename)
30 Mastering C Programming
main()
C programs consist of one or more functions and all programs must contain one
function bearing the name main. This is the main program, which will use any
other functions you define. All function names must be followed by a left and a
right bracket (parenthesis) even if the function requires no arguments. Functions
are at the heart of C and naturally follow on from our consideration of problem
solving in the last chapter. They comprise one of the building blocks of C
programs and represent chunks of code which perform a specific, well defined
task.
The left brace ( {) marks the beginning of the body of a function definition, the
right brace (}) the end. So this left brace marks the beginning of the main body
of the program (it performs a similar function to that of BEGIN in a Pascal
program). Braces are also used to group statements together to form one
compound statement, but more of this later.
This is the first executable statement in the program. This statement refers to a
function printf (),it has the formfunction-name(). Here it is not a function
definition as is the main () function reference, but a function call asking the
computer to perform the function. So this statement uses the function
printf () to perform a task which in this case is simply to print on the screen
whatever appears between the pair of double quotes. Thus when the program is
executed (or run) the words Welcome to will appear on the screen of the
computer.
Notice that this line ends in a semi-colon. All C statements either end in a
semi-colon or include a number of statements which are terminated by a right
brace. So the whole C program consisting of main () { } can be
Towards C 31
It is not difficult to imagine what this statement will do. If you expect the
world of C. \n to appear on the screen then you are not far wrong. However
there are a couple of important points to note about the print f () function and
about this statement in particular.
Thus the net result of the two printf () function calls in this program is to
display on the screen the truly exciting sentence
The cursor on the screen is then left at the beginning of the following line,
owing to the effect of the escape sequence \n.
This is the matching right brace which indicates the end of the function
definition rna in ( ) and in this example the end of the program.
Now that you have seen a simple C program we can sketch out the basic
structure of all programs written in C. Figure 2.1 illustrates the main
components which will be present in all C programs. As you can see, there
aren't too many! One point to note is that the function definitions can precede
the main program, however we will adopt the layout indicated here throughout
this book.
main()
{
}
preprocessor instructions
/* main program */
main()
statementl;
statement2;
statementn;
/* function definitions */
functionl ()
{
functionp ()
{
A C program •••
and •••
The function definitions are generally collected together at the end of the main
program, following the right matching brace of the function rna in ( ) (see Fig
2.1). As we pointed out in the last chapter, functions are an important aspect of
program design. In many cases the sub-problems (strictly speaking methods for
solving them) arrived at by stepwise refinement can be coded directly as
functions. These functions can be written and tested before being used in the
main program. This approach enhances the readability of the final solution and
makes maintenance and modification easier. In the next section we will take a
quick look at the way functions are declared and used.
The items in italics are optional and so the simplest (useful) function will have
as a minimum the following structure:
The latest ANSI (American National Standards Institute) and the British Standard
Specification (ISO/IEC DIS 9899) adopt this specification for functions while,
for the present, also allowing the original version, i.e.
34 Mastering C Programming
function_name ()
statements
(Note that even if there are no parameters in the function the two parentheses are
still required- and note that there is no space between them.) Whilst you may
find many programs written using this form you should adopt the ANSI standard
in your programming and we have endeavoured to use this standard in the
examples in this book.
Notice that a semi-colon is not used at the end of function name ().The
left brace({) marks the beginning of the body of the function and this must be
matched by the right brace at the end of the function definition. Between this pair
of braces is at least one statement - essential if the function is to do anything
useful!
We have encountered already the most important function inC- that is the
function main ():
main()
statements
which forms the skeleton of all C programs. Thus all C programs are
themselves functions with one thing in common- they are all called main.
Another example with which you are already familiar is the standard function
used for outputting data to the screen- namely printf () . This function, as
we have seen, has the form:
i)defmed,
to be of any use! If the function is not defined anywhere, the compiler will not
recognise it when it is referenced, and without it being called (executed, or used)
there is no point in declaring or defining it. (Of course the function need not be
in the main program, it could be in a header file, which would be loaded if
requested at preprocessor time. Or it might be in a library file, in which case its
Towards C 35
object code could be linked just prior to the production of the final executable
code of the progmm.) Notice that when a function is called on its own a
semi-colon follows the right bmcket - this is because in such a situation the
function call is in fact a statement and so must be terminated with a semi-colon.
We need to think a little about the layout of the screen before we start writing
the code. Even in this simple example a little thought at the early stages pays
off in the end. One possible display is given in Figure 2.2.
Now we can write the function to produce this display on the screen. We need
to give it a name, so why not main_menu- there is no need to be imaginative
at the moment! The text can be output to the screen in a number of ways, each
of which have their pros and cons. We will stick to the method with which we
are familiar, i.e. by using the print f ( ) function. We can now write the
function as follows (Program 2.2).
36 Mastering C Programming
************************** ******
BRIDGE TUTOR
************************** ******
1. shuffle
2. deal
3. display the hands
4. count the points
5. bid
6. play
Q. quit
Program 2.2
void main_menu(void)
You will notice that this function has no parameters and no declarations, it is in
fact one of the simplest of functions possible. However, to make it clear that the
function requires no arguments and does not return a value, the keyword void
is used in place of the parameter-list and the type respectively (compare with the
function skeleton on page 33). (These aspects will be discussed in more detail in
the next chapter.)
main_menu();
Using main_menu ()
Before we can use the function we need to include it in a program, called main ().
A very simple program to achieve this is shown below.
Program 2.3
#include <stdio.h>
main()
main_menu();
The earlier sections of this chapter have introduced the basic ideas concerning C
program and function structure and have given a glimpse of some of the basic C
statements. It is now time to get to work on the computer, but before reaching
for the switch and typing in a program we need to take a quick look at the
processes involved in moving from the C code to a working program. All
programs written in a high-level language need to be converted from the code-
the C language, or Pascal, or modula 2 - into a form which the computer can
'understand'. This form ultimately consists of a series of binary numbers which
represent instructions to the hardware and addresses in memory where the data is
stored.
• through an interpreter
• by means of a compiler
Interpreters and compilers are themselves programs which take as input the
source code and eventually produce an object code (in binary or hexadecimal
numbers) which, after passing through one further stage, can be executed or run
by the computer.
38 Mastering C Programming
An interpreter translates a line of source code, checks for any syntax errors
(i.e. mistakes in the arrangement of the 'words' in the 'sentence') and if there are
no such errors executes the translated code. The process is then repeated for the
next line of code and so on until the end of the program is reached. On the other
hand, when using a compiler, the whole program is translated to produce the
object code before any attempt is made to run the program. An additional task is
also usually carried out which adds in or links other ftles which are required by
the program. These library files are linked to the program prior to the final
production of the executable code. Library files might contain object code for
input/output functions, mathematical functions (e.g. trigonometric functions) or
more specialised functions required for a particular task.
The route in C
The exact means by which the C source file is translated into an executable file
which will produce results is dependent on the operating system of the computer
and the version of C which is being used. However the majority of C
implementations will be compiled. Assuming that this is so in your case we
will go through this process step by step (see Figure 2.3).
Once you have successfully negotiated the stages outlined in Chapter 1 and have
produced a program written in C (or 'coded' as programmers are wont to term it)
then you need to enter your masterpiece into the computer. To do this you will
need some form of text editor. This program allows text to be entered into the
computer and then saved for future editing or further processing. The text of the
program which you enter is known as the source code and in the C language
such ftles are distinguished from other files by the extension . c at the end of the
source ftlename.
Compiling
The next stage is to translate the source code into object code. Before the
compilation itself takes place the compiler performs a task known as
preprocessing. During this stage the header at the beginning of the C program is
processed. One example of a preprocessor instruction which we have already
encountered is the #inc 1 u de < f i 1 en arne> command. When the
preprocessor encounters this type of instruction the line is replaced by the
contents of the specified file. We will be revealing other preprocessor
instructions as and when necessary.
Once the preprocessor has done its work the compiler takes over. The task of
the compiler is twofold: to check the code for syntax errors, and to translate the
source code into object code. The first task is analogous to checking that the
Towards C 39
your masterpiece!
source code
header files
library files
results
40 Mastering C Programming
Provided that the source code is syntactically correct the compiler then
proceeds to translate the source file into an object file. The program is now
almost ready to be tried out but there is one more stage which it has to pass
through.
Linking
The linker is a program which combines the object code of the compiled
program with other files which may be required and produces the fmal executable
file. Now the program can be executed, or run, and the real fun begins!
Running
When a program is run, or executed, a variety of different results can ensue. The
most desirable result is that it runs correctly and the desired processing occurs
with output produced as required. This will not always be the case! A runtime
error may occur; for example, an attempt has been made to divide by zero, or to
index past the end of an array. The program may also run without producing any
errors but with incorrect output. In either of these situations the program will
require editing and the 'bugs' eliminated. At this point a well thought out and
well documented program is invaluable. (Although if it is really well thought
out, there will be no bugs!)
SUMMARY
EXERCISES
/* greeting.c *
/* The first c program - greetings */
tinclude stdio.h>
man()
{
b. printf("AbCdEfGhij\nKlMnOpQrSt/nletters\n");
printf("1234567890/numbers\n");
printf(\n that was all rather silly!\n");
3. There are a number of errors in the program below. Type it in and compile it.
Finally, link and run it. What happens?
tinclude (stdio.h)
main[]
(
print(" This is another very simple\n);
printf (" P\n")
printf(" R\n);
printf(" 0\")
print{" GRAM\n");
6. Modify the greeting program to produce on the screen your initials using
asterisks, call the new program initial.c.
7. Write a function to print on the screen your name, address and telephone
number. Make it appear like a business card. Embellish it as much as you wish,
using asterisks or plus signs to add a border.
system("cls");
(However if you use this you should also include the DOS header file by
inserting #include <dos .h> at the start of your program.)
Investigate the command for your particular implementation and see if you have
a comparable command. If not, can you think of any other way of achieving a
similar effect?
® Of words and objects
" ... language itself is the most remarkable tool that man has invented, and is the
one that makes all the others possible." The Story of Language, C.L. Barber
3.1 LANGUAGE
the instructions which can be used. This chapter introduces the basic data types
which C manipulates. We will be exploring their form and answering the
question 'What are valid objects inC?'; we will be taking an overview of the
ways in which they can be manipulated; and how the basic elements are gathered
together to produce a program.
Characters
Before we take a close look at what objects can be manipulated we need to step
back and examine the fundamental building blocks of the C language out of
which both instructions and data are made. These are the characters which can be
used in C. Some of these were introduced in the previous chapter. Many of them
will require no introduction, but for completeness we list them all here. These
characters, out of which everything is built, are:
Tokens
Tokens are valid combinations of characters which fall into six categories;
keywords, identifiers, constants, string literals, operators and separators (see
Figure 3.1). The tokens, grouped together by means of the rules of C, form a
program. The category which any one token falls into will depend mainly upon
its structure and in some cases upon its position in the program (i.e. its
context). We will be explaining some of the most important types of token in
this chapter. This will enable you to become familiar with these different
categories and their irilportance. Program 3.1 identifies some occurrences of each
of these six categories in a simple program.
Identifiers
An identifier is •••
You will have already noted that there are two types of identifier which can be
manipulated in C - variables and constants. Variables are used to identify storage
locations which may vary in value during the execution of the program.
Although the value of the variable may vary, the place in memory where it is
stored (i.e. its address) remains the same. Symbolic constants, on the other
hand, are used to identify data objects which will remain the same all the time
the program is being run. These are referred to as 'symbolic constants' because
each name is a symbol which stands for an actual constant (i.e. is just another
name for it). Another convention in naming identifiers is that upper-case letters
are used for symbolic constants and lower-case for variables. This makes it easy
to distinguish between the two in functions and programs.
Program 3.1
/* circle details */
/* this program computes the area */
/* and circumference of a circle*/
Of words and objects 47
#include <stdio.h>
main()
{ /* { , ; - separators *I
float radius, area, circum; /* float - keyword *I
/* radius, area, circum - *I
/* variables *I
You may be wondering why constant names are used when the value of the data
object itself would do. One answer is that it is much easier to identify a
symbolic name in a program than a string of numbers. So, for example, PI in
the above program is more immediately recognisable than 3.141592. Secondly
if the value of the constant requires changing, this can be achieved by simply
altering one statement: the one which defines the symbolic constant. All
occurrences of this constant will then be replaced by the new value before
compilation begins. A third reason, connected with the previous one, concerns
the laborious task of modifying each occurrence of numerical constants, with the
risk of missing one or, perhaps worse, altering the wrong one!
Keywords
As mentioned above there are some words which are reserved and cannot be used
for variable names or as symbolic constants. These are the keywords of C and
have defmite meanings which cannot be altered. They are the words which enable
data to be manipulated and useful tasks performed. Without the keywords we
would have data but no means of manipulating them. A programming language
devoid of keywords is like a cook with all the ingredients for a banquet but no
pots, pans or other kitchen utensils - or an experimental physicist with no
apparatus. Table 3.1lists the keywords of C.
The keywords in parentheses are not included in the original definition of the
48 Mastering C Programming
float for go to if
The keywords, together with variables and symbolic constants, and of course
constants (e.g. 2, 45. 8, 0. 0001, 'a'," a string ") enable the
programmer to write a program to perform almost any procedural-type task. The
various keywords listed above will be dealt with where necessary in the text -
by the time you have worked through this material, you will be familiar with all
these keywords and so be able to write quite complex C programs.
We now need to take a more detailed look at variables and symbolic constants.
We mentioned above that variables could store numbers, or characters, but we
need to be more explicit about what is meant by numbers, and what kind of
numbers can be manipulated in C.
Data are stored as a binary number, i.e. a series of Os and ls, which may take
up a byte, a word, or a number of words. The variables and symbolic constants
which we have just been discussing must at some stage during the execution of a
program be assigned a value which will be represented as a binary number; as a
Of words and objects 49
The keywords out of which the fundamental data types in C are constructed
are:
In the following sections we will look at the various data types available. We
begin with the most fundamental set- the data type int.
50 Mastering C Programming
Program 3.2
#include <stdio.h>
main()
{
int numl, num2, sum; I* integer variables *I
The keywords short, long and unsigned can be used to modify the data
type int. The allowable combinations are:
unsigned int
short int
long int
unsigned short int
unsigned long int
You may well be able to guess the effect of using unsigned as a prefix to
in t - it limits the range of permissible integers to those greater than or equal
Of words and objects 51
to zero, i.e. to positive integers. Since there is no need to use one of the bits to
indicate sign, the effect of using the data type unsigned is approximately to
double the largest positive integer which can be represented, compared with that
for an int data type (from 32767 to 65535 when using 16 bits). When might you
use such a data type? One use might be as a counter; for example the program
which I am using as a wordprocessor also provides a word count. The number of
words in a chapter will never be less than zero- even if it is still to be written!
The preftxes short and long also affect the range of integers which can be
represented. The type short int can be used when storage space is at a
premium; with this data type "a smaller if possible" amount of memory is used
to store an integer. For example two bytes might be used instead of four.
Conversely the data type long int allows a larger range of integers to be
used, e.g. eight bytes instead of four. The use of unsigned as a preftx with the
short int and long int data types has the same effect as when it is used
with int alone, i.e. it restricts the range to positive integers.
There are a couple of important points to remember about these various int
data types. First of all, they may be abbreviated by omitting the keyword int
wherever possible. A second point to note is that the range of integers spanned
by the data types int, short and long may overlap. On one computer the
data types int and short may mean the same thing, whereas on another the
int and long data types may extend over the same range. The reason for this
is based on the 'natural' word size of the machine in question. So on a machine
where the normal word size is 16 bits short and int may use the same
amount of storage (16 bits) whilst long will utilize 32 bits. On the other
hand, on a machine with a natural word size of 32 bits, long and int may
both use 32 bits to store an integer and short will use 16 bits.
A floating point number therefore contains the following four elements (see
Figure 3.3):
an integer part
a decimal point
a fractional part
an exponential part
Not all of these four parts need be present, but certain of them must be. A
floating point number must contain either a decimal point or an exponential
part, or both. If a decimal point is used, then either an integer part or a fractional
part, or both, must also be present. If an exponential part is used and no decimal
point is included, there must be an integer part in addition to the exponential
part. These rules will become clearer if we list a few examples:
;:~
\.:)~
27. .056e12
Of words and objects 53
So far we have restricted our discussion to the way in which real numbers (or
floating point numbers) are represented in C. Two other important factors which
affect the representation of these numbers are the range of values which can be
used and the accuracy (or precision) with which they can be stored. The typical
range of floating point numbers lies from about w-37 to 1038 and the precision,
when 32 bits are used, is approximately 6 or 7 significant figures. The precision
is limited by the number of BITS available for storing the (binary) fraction. The
range is limited by the number of BITS available for storing the (binary)
exponent.
Both the range and the precision can be increased by using the data type
double instead of float. The word double is used here because the number
of bytes used to store data of type do ub 1 e is twice that used by f 1 oat
data. The double data type increases the range typically to 10-307 to IQ+308
and the precision to around 16 significant figures. Check your version of C and
find out the range and precision for f 1 oat and do u b 1 e in your
implementation.
This data type is used to represent a single character; that is, each of the standard
keyboard symbols (letters, digits, punctuation, special characters) as well as a
number of non-printing characters such as newline and tab. One byte (i.e. 8
bits) is normally used to store each character, which means that there are a total
of 28 or 256 different codes available - more than enough to encompass the
normal set of characters. Each character is coded as an integer which is then used
to represent that character in all subsequent occurrences. The precise code used
will depend on the particular implementation; the most common one being the
ASCII system in which the character 'a' is represented by the decimal integer 97,
and 'A' by the integer 65. Furthermore digits do not represent themselves, so '0'
is represented by the integer 4 7, and '9' by 57. (See Appendix A for a list of the
ASCII character set.) In the new standard the keyword signed can be used with
a char. This facility has been included to aid portability where, for example,
data other than character data is to be stored in a char variable. This keyword
can also be used with other data types (e.g. int) but it is not necessary as they
are signed by default .
In addition to the data types we have mentioned above there appear, in certain
implementations of C, a number of other data types. We will examine one or
two of these later on, but for the moment you may like to check which other
54 Mastering C Programming
Having examined the various fundamental data types in some detail we can now
turn to the process of declaration. Before variables can be used in a C program
they need to be declared. This process enables room in memory to be set aside so
that values can be stored in the variables. As we have seen, the amount of
storage which a variable is allocated depends on the type of data which the
variable is to represent. A single character such as 'A' or a variable of type short
(perhaps used to store integers in the range 0 to 100) will be allocated less room
than a number representing the speed of light or the rest mass of an electron.
#include<stdio.h>
Of words and objects 55
main()
The way in which a declaration is made depends on whether the object being
declared is a variable or a symbolic constant.
Variables
data_type variable_list;
int i = 0, j = 100;
float sum = 0.0;
which will set the integers i and j to 0 and 1 0 0 respectively and the float
variable sum to 0 . 0.
Symbolic constants
A symbolic constant is always defined, that is declared and given a value at the
same time. This is achieved by using the preprocessor instruction #define at
the beginning of the program before the function definition rna in ( ) . For
example,
#define PI 3.141592
#define MEO 9.1083e-31 /*electron rest mass */
Not all the fundamental data types are available for use with symbolic
constants; usually they are restricted to the data types char, int and
double. The way in which the numeric value is written determines the data
type of the number and, in addition, the data type of its associated symbolic
constant. The new standard allows a constant to be coerced into a float by
appending for F, or into a long double by appending 1 or L. Also ints
may be coerced to long integers in the same way. An integer constant may
also be coerced to unsigned by appending au or U. Both L and u may be
appended to the same constant. In addition the derived data type string is also
available for use with symbolic constants. (Strings will be described later.)
The list below gives a few examples of the fundamental data types which can
be used to specify symbolic constants.
int 37 0 1782 -260
long 37L OL 1782L -260L
double 37.0 8.15 2.99793e8 -2.6
char 'a' 'H' '\n' ·-·
unsigned long 37LU OLU 1782LU
When defining a constant of type char (either one of the standard keyboard
characters or one of the non-printing characters) the character must be enclosed in
single quotes, e.g. ' a • . Most of these examples are obvious enough, but the
• \ n • requires further explanation. We saw earlier that there are some
non-printing characters in C which are used, for example, to format the output
from a program. These 'characters' are treated as a single character although they
consist of two characters: a backslash (\) followed by a single character. The
example given above (' \n ') is the code used to generate a newline.
Charlie's musings
The lecture the other day on programming, and the subsequent discussions
with his tutorial group, had left him still rather uncertain about where variables
and constants, identifiers and data types fitted into the jigsaw puzzle of
programming which was slowly beginning to assemble itself The street dipped
down and curved round in an arc to Charlie's right. As he continued his journey
he paused now and then to glance at the antiques in the equally antique-looking
shops which lined both sides of the narrow street and seemed to populate most
of this part of the city.
His thoughts turned to the chemistry book he had been browsing through the
night before. He was getting more and more fascinated by the structure of matter
and particularly by the fact that the fundamental building-blocks of all matter
were so few and apparently so simple(!). Just from a few basic particles (or were
they waves - he was still uncertain about this), like protons, neutrons and
electrons, the complex matter which made up the universe could be built. As he
was mulling this over he glanced up. The late afternoon sun lit up an
ancient-looking shop window, and with the help of its rays he could make out,
in the gloomy recesses of the shop, some shelves of jars containing liquids of
various colours. Lower down and to the right of these jars he saw others
containing crystals- white and blue and black. Pausing and looking over to the
left he could just make out a bank of drawers, all labelled but too much in the
shadows for him to decipher the writing. Why, this shop had altered hardly at all
in over a hundred years! (See Figure 3.4.)
Charlie stayed for a while in the rapidly fading twilight, staring through the
window with a look of enlightenment on his face, but he was brought sharply
back to reality by a sudden chilly gust of wind which eddied down the darkening
58 Mastering C Programming
street. He began his trek back to his flat feeling much happier and looking
forward eagerly to his evening meal.
Although we haven't covered all the aspects of C which are needed to write a
moderately useful program we have covered enough material to enable you to
understand simple programs. At this point you could usefully look back at the
two programs given so far in this chapter and check that you understand their
structure and purpose. (Some of the keywords and functions will not yet be
familiar to you, but you should nevertheless be able to grasp their purpose.)
This is a very simple problem so we are able to write down the algorithm with
very little thought, and without the need for stepwise refinement. However you
must not forget the importance of stepwise refinement - we will soon be dealing
with more complex programs which will require a little more planning before
the code can be written.
One of the pieces of information which we need is the conversion factor. The
definition above is no longer used, although it is interesting to know how the
measurement originated. The current definition lists 640 acres to one square
mile. We can find the number of square yards per acre as follows:
This calculation will be performed at preprocessing time and the resulting value
substituted for all occurrences of SYPA at compile time. Remember that there is
no equal sign in this (preprocessor) instruction and there is no semi-colon at the
end of the line. The identifier I have used (SYPA) stands for Square Yards Per
Acre - any other valid identifier could have been used but this is reasonably short
and acts as a sensible mnemonic.
The 'heart' of this program is the simple multiplication which uses this
conversion factor (SYPA) to convert acres into square yards. This calculation is
accomplished by the line:
sq_yards = acres*SYPA;
Notice here the use of an asterisk (*) for multiplication, rather than a cross
which could only be represented by the letter x and thus lead to confusion. The =
operator is known as an assignment operator and is used to place the result
of computing the expression to its right into the variable on its left.
The final task required in this program is a means of printing the result. The
printf () function can be used to achieve this, as follows:
The printf () function, as it appears here, works in the following way. The
string between the pair of double quotes is printed out exactly as it appears
except in the case of special characters, known as formatting characters (i.e. %f
and \n). The first occurrence of% f is replaced by the value of the first variable
in the list which follows the section in quotes, the second %f is replaced by the
value of the second variable. The %f also governs the way in which the variable
is printed out, in this case the output is in floating point form. Thus if the
variable acres has the value 1.0 then a line similar to the following would be
printed out
The effect of the \nat the end of the control-string is to move the cursor (or
print head) to the beginning of the next line.
Stagel:
Notice that the numbers 1760 and 640 are written as real numbers even though
they have no fractional parts. This ensures that SYPA is of type double. The
element missing from this program is a fairly crucial one: some means of
entering the data into the program. We need a function which will allow us to
enter, in acres, the area which is to be converted to square yards. You have
already come across the function we require, in the circle and add programs, it is
is the scanf () function. This, like printf (),is part of the standard i/o
library of C and has the form
scanf(control-string,argument-list)
This function is quite straightforward. However there is one very important point
which you need to know before you use it. The elements in the argument-list do
not represent the variable names, but point to the addresses in memory where the
data are stored. To refer to the address of a variable, rather than the contents of
the variable, the variable name is prefixed by an ampersand(&). Thus &acres
represents the address in memory where the variable acres is to be found. So
the statement which we need to read in the area in acres is simply
scanf("%f", &acres);
The %f format string causes the scan f ( ) function to interpret the string of
characters entered as a floating point number just as, when it is used in a
printf () function, the output is formatted as a floating point number. Other
format strings which can be used with both scanf () and printf () will be
introduced later on in this chapter.
This statement on its own is still not very helpful to the potential user. Can
you think why? If you can't then enter the program listed below, compile, link
and run it - and see if that helps! If you are still stuck, ask a friend to run the
program and watch their reaction!
62 Mastering C Programming
Stage 2:
scanf("%f", &acres);
sq_yards = acres*SYPA;
printf("%f acres equal %f square yards\n", acres, sq_yards);
When the above program is run the computer awaits an entry from the keyboard
before continuing. The problem is that without some indication of what should
be entered the user is liable to be in the dark - you should know what needs to
be entered, but you will be very lucky if anyone else does, and after all your
ultimate aim is to write programs which anyone can use! To overcome this
problem all we need do is to insert a prompt to the user before the value is read
in by the scanf () function. We can achieve this quite straightforwardly by
using apr int f ( ) statement, for example:
The prompt can be as verbose or as concise as you wish as long as it gets the
message across. Having introduced the scanf () function we have arrived at the
fmal version of the program which is reproduced below.
Program 3.3
Notice that the first printf () statement leaves the cursor at the end of the
prompt, it doesn't move to a newline. This is very convenient when prompting
for the entry of data. Once a number is entered, and the Return key is pressed,
the variable acres is assigned the entered value, the computation is carried
out, and the result is printed out on the following line.
3.5 OPERATORS
= + - * I
In the next few sections we will be covering the use of all these operators as
well as a few other useful ones, but we will begin our tour with the assignment
operator.
The= operator inC does not perform the same task as the 'equals sign' in
mathematics, as for example in the equation
x2 + 3x- 2 = 0
In mathematical terms this expresses the fact that the left hand side of the
equation is numerically equal to zero. InC the equals sign(=) is used to assign
the value of whatever appears to the right of the equals sign to the variable
which is on the left of the sign. What is the difference? The difference becomes
apparent when we examine a common programming expression which is used to
increment a variable. Consider the expression:
i =i + 1
In mathematical language this does not make sense. How can a quantity be equal
to one more than itself? In the terminology of computing, however, it does
make sense. It in fact states that a new value of the variable i is to be obtained
64 Mastering C Programming
expression value
All the following are valid assignment statements, provided that the variables
are all declared as type int.
X = 6;
month = 12;
i = count = total 0;
Of words and objects 65
The last statement may appear somewhat strange; most computer languages do
not allow multiple assignment statements like this one. However in C such
statements are perfectly valid and are quite common. This statement also
illustrates the order in which arithmetic assignments are carried out, that is from
right to left. Thus the variables are assigned the value zero in the order: total,
count, i. The following are also valid assignment statements:
h = a*a + b*b
energy= mass*v*v/2.0
Note, however, that garbage will result unless the variables to the right of the
assignment operator have previously been assigned a value.
total=total+8;
total = total + 8;
total = total + 8;
Obviously the legibility of the program is improved if the second option is used.
However in long expressions this can become cumbersome. The important point
to remember is that spaces may be used to separate operators but they must not
be used within identifiers themselves. Thus
The rules for precedence operate first. so in expressions which have a mixture
of operators with different precedence those with the higher precedence are
evaluated before those of lower precedence. If the precedence of two operators are
equal then the rules for associativity are implemented. The rules for all the
arithmetic operators are summarised in Table 3.2. The remaining operators are
dealt with in the next section.
Operator(s) Associativity
() Left to Right
- (unary) ++ -- Right to Left
* I % Left to Right
+ - (subtraction) Left to Right
Right to Left
The multiplication and division operators have a higher precedence than the
addition and subtraction operators and so an expression involving either of these
operators is evaluated before an expression involving either addition or
subtraction. To force evaluation in a different manner, and to clear up any
ambiguities which may be present in the user's mind, parentheses can be used,
i.e. (and). These have the highest precedence, which means that any expression
enclosed within round brackets will be evaluated first, even if this expression
includes only addition or subtraction. The following example (Figure 3.6) will
help to illustrate the effect of parentheses on the evaluation of an arithmetic
expression. Assume that a has previously been assigned the value 2 . 4, and b
the value 1. 2.
Of words and objects 67
There are four more arithmetic operators which you will find invaluable. These
are:
(unary) negative, or negation
% modulus
++ increment
decrement
The (unary) - operator affects the operand immediately to its right and simply
reverses the sign of that operand.
Program 3.4
#include <stdio.h>
main()
n1 = 17;
n2 = 3;
printf("\n n1 n2 n1/n2 n1%%n2\n");
/* the two % characters are necessary in the above */
/* see section 3.9 for the reason */
n1 n2 n1/n2 n1%n2
17 3 5 2
-17 3 -5 -2
17 -3 -5 2
-17 -3 5 -2
The next operator is the increment operator which performs the incrementing
operation which we mentioned briefly earlier in this chapter; it is used to
perform the very common task of adding one onto a variable. This operator can
be placed either before or after the variable. In the former case it is known as the
prefix incrementing operator, whereas in the latter case it is called the postfix (or
suffix) incrementing operator. The following short program illustrates how it
works.
Program 3.5
int i, j, n;
i = j = 0;
printf("These are the initial values of i and j");/* line 1*/
printf("\n i %d: j = %d \n",i,j); /* line 2*/
printf(" Now i and j are incremented\n");
printf(" ++i = %d: j++ = %d \n",++i,j++); /* line 4*/
printf(" and these are the new values of i and j ") ;
printf("\n i = %d: j = %d \n",i,j); /* line 6*/
Of words and objects 69
Enter this program, then compile and link it. Once you have successfully
obtained an executable file run the program. You should obtain the following
output:
Notice the difference between the values printed out for i and j in line 4. The
prefix incrementing operator ( ++ i) tells the compiler to increment i before
performing any further operations; in this case before printing out the new value
of i. Thus i is increased to I and this new value is printed out. However when
written in its postfix form ( j + +) the value of j is first used and then it is
incremented. So the current value of j (i.e. 0) is output and only then is it
increased by one. The sixth line of the output shows that both i and j are set
to one following the execution of the commands + + i and j ++. Another
example of how this operator works is given below.
int i, j; int i, j;
i 1; i 1;
2* (++i); /* 4 *I 2* (i++); /* j 2 *I
/* i 2 *I /* i 2 *I
Note that the incrementing and decrementing operators can only be used with
variables; the following are incorrect usages of these operators.
All of the arithmetic operators which have been introduced in this chapter have
their equivalent arithmetic assignment operator and they are listed in
70 Mastering C Programming
Table 3.3. These operators can prove very useful in shortening a piece of code
and you will soon find that they become second nature to you and that you will
be using them in preference to their longer equivalents.
assignment
operation operator example equivalent to:
addition += i += 5 i = i + 5
subtraction k -= 12 k = k - 12
division I= n I= 2 n = n I 2
int i;
float x, y, z;
i = 5;
X = 6.4;
y xl5;
z = y + i;
In many computer languages, such as Pascal and Modula-2, the last two lines
would cause an error because both statements consist of a mixture of data types,
i.e. int and float. However inC mixing data types is perfectly legal. Let's
deal with these two cases in turn. The first case involves the division of a
floating point number ( 6. 4) by an integer. The evaluation of the right-hand side
of the statement (i.e. xI 5) is performed by converting the integer to a float
and then carrying out a floating point division. This produces the result 1.28
which is then assigned to the variable y. In the second case because the variable
y is of type float the value of i is converted to a float before being added
toy. Note that i is still stored as type int; its value and type are unchanged in
memory. Finally the result is assigned to z, giving it the value 6.28. These two
examples illustrate the process known as type conversion. This feature of C can
be very useful, but at least to begin with you should treat it with care.
Of words and objects 71
highest double
float
unsigned long
long
unsigned int
int
unsigned short
short
char
Promotion
In an operation which involves two types both the values are converted to the
'higher' of the two types before evaluation. Normally no problems will be
encountered when this process of promotion occurs.
int i;
char c;
Demotion
When an assignment statement is involved and the types are mixed the final
result of the calculations is converted to the type of the variable on the left of the
equal sign. This may cause no problems, particularly if the type of the assigned
variable is 'higher' than the other types involved. However if this type is 'lower'
then the process of demotion will occur. Thus a float type may be assigned to
a variable of type int. This can cause problems, since the assigned variable
may not be big enough to hold the result, or it may be truncated to an integer.
average = sum/count;
Casts
In addition to the above implicit conversions between data types C also provides
explicit conversion by means of casts. So, for example, if n is of type int then
it can be casted into afloat by the expression
(float) n
and any expression of which this is a part will now use the f 1 oat version of n
instead of the original in t version. The cast operator is a unary operator and has
right to left associativity and the same order of precedence as other unary
operators. So
(int) x + 5 * i
is equivalent to
((int) x) + 5 * i
Thus the conversion is carried out before the other arithmetic operations. Some
other examples are
(int) (PI * r * r)
x * (float) i I (float) n
(char) (n = i - (int) c)
Of words and objects 73
y (double) (3 * x * x - 2 * x + 6)
Notice that, although the third of the above expressions is valid, an expression
of the form
(int) x = 2 * y
is invalid. The reason for this is that the process of casting forces a temporary
change in the variable, or expression, involved. The variables themselves still
retain their declared data types and their current values. If we wished to assign the
integer part of 2 * y to x, we would need to write it as follows:
x = (int) (2 * y)
3.8 EXPRESSIONS
365
3.141592
1e21
'e'
"A string of some sort"
c
PI
year
count
Expressions, though, will usually be more complex than these simple examples.
They will normally consist of a combination of operators and operands. The
operands can be constants, as above, variables or symbolic constants, or even
other expressions (often called subexpressions). The operators may be arithmetic
operators, arithmetic assignment operators, logical operators and so on.
365/12
v*v/ (c*c)
74 Mastering C Programming
s=u*t+O. 5*a*t *t
x*(u=v+5)+z
Most of these are perfectly straightforward and, given the values of the variables,
you could easily evaluate them. But what about the last example? This looks
rather odd and would not be allowed in most other programming languages, but
by now you will not be surprised to learn that in C it is perfectly valid!
Although such expressions are perfectly valid it is considered poor style to
include assignments within other expressions unless it is absolutely necessary. It
is better to keep assignment statements separate so that it is clear where
assignments occur. However this example does illustrate an important feature of
expressions in this language, namely that all expressions have a value. This
means that, in the last example, the expression u=v+S has a value which is
then multiplied by the variable x. But what value do such expressions have? The
value of an expression as a whole is simply the value which the variable to the
left of the assignment operator (=) has following the evaluation of the complete
expression. So, for example, the expression u*t+O .S*a*t*t, (with u=O,
t=l, and a=9.8) evaluates to 4.9, the variables becomes 4.9 and thus the value
of the whole expression ( s=u*t+O. 5 *a *t *t ) is also 4.9. Now in the last
example above, if x=2, v=l4 and z=3, then the variable u becomes 19, which is
therefore the value of the whole expression in parentheses and so the complete
expression becomes 2*19+3, which produces 41 as the value of the complete
expression.
3.9 STATEMENTS
The two types of statement are known as simple and compound. A simple
statement is a complete instruction which ends in a semi-colon; whereas a
compound statement consists of two or more statements enclosed within braces
({}).A complete instruction is an expression which performs some task. The
semi-colon and the braces form part of another group of tokens called separators,
you could think of these as the punctuation marks of C.
Simple statements
int count,n,m;
float s;
count=S;
s=u*t+O.S*a*t*t;
p=x*(u=v+S)+z;
printf(" Welcome to the world of C\n");
scanf("%d %d",&n,&m);
The first two are declaration statements which allocate space in memory for the
various variables; the next two are simple assignment statements; the fifth is a
more complex assignment statement, made up of a number of sub-expressions;
whilst the last two statements consist of function calls. All of these types of
statement were present in the conversion program. Look back at that program
now and make a note of which statements belong to which category (remember,
the catogories are: declaration, simple assignment, complex assignment and
function).
There is one further basic type of statement which you will need to be
familiar with. These statements are sometimes known as structured
statements. Examples include:
if (a >= max)
max = a;
These statements perform an action, but their structure is more complex than a
simple assignment statement. The details of these structured statements will be
left until later. However we will be dealing with the if statement in the next
chapter, so you will not have long to wait. Note the structure of the for
statement:
for ( expression] ; expression2 ; expression] )
statement
There are no statements within the parentheses - there are three expressions each
separated from the next by a semi-colon.
Compound statements
In this example all the statements enclosed by the braces ( { and }) are part of the
for statement and are executed in sequence each time the for statement is
executed, that is so long as the variable count is less than 10.
We will find many more uses for compound statements as we come across
other structured statements in subsequent chapters.
Layout
The exact position in which the right and left brace appear is not crucial.
Obviously the left brace must be placed at the beginning of the compound
statement and the right brace at the end. However the above for statement
could equally well be written as
++a;
++b;
c = a*a + b*b;
printf("%d %d\n", count, c);
A number of other possibilities exist but these two are the most usual.
Whichever approach you use be sure to: (I) be consistent; and (2) adopt a style
which allows compound statements to stand out clearly in the code, e.g. indent
the statements between the braces by a fixed amount.
In this final section we are going to take a further look at the way in which
formatted input and output can be achieved by using the functions scan£ () and
print£ ().You have already been introduced in this, and the previous, chapter
to various ways in which these two functions can be used. The object of this
Of words and objects 77
section is to bring together what you have learnt so far concerning input and
output and to extend it a little.
Both functions for formatted input and output have a similar syntax, i.e.
print/(control-string, argument-list);
scanf(control-string, argument-list);
Notice that the printf () control-string can contain either or both text (e.g.
Welcome to the world of c), or format strings (or conversion
specifications) (e.g. %d %c). White spaces can also be included in the
control-string of apr in t f function. You have already encountered one of the
white spaces, i.e. \n for newline. However there are a number of other
nonprinting characters which can be represented by a backslash followed by a
character and these are listed in Table 3.4.
Conversion specification
prjntfO scanfO
c as a character a character
s as a string a string
Note that to have a % appear the string %% is required. The reason for this should
be obvious since as this symbol is used to indicate a conversion specification
ambiguities would be bound to arise.
One other useful method of printing a character is to send the octal code
preceded by a backslash. Thus \ 10 1 in a control_string would print the letter A
(assuming the standard ASCII codes) and\ 0 07 would ring the bell.
The program below illustrates the use of some of the control_strings which
Of words and objects 79
main()
{
char cl, c2;
int il, i2;
float x;
printf("Using conversion specifications\n");
printf("\nPlease enter the following:\n");
printf("%s\n%s\n%s\n%s\n%s\n",
"an integer",
"a character",
"a float",
"a character",
" and finally another integer");
scanf("%d ", &il);
scanf("%c %f %c %d", &cl, &x, &c2, &i2);
printf("\n\nYou entered :\n");
printf("\t%d\t%c\n", il, cl);
printf("\t%f\t%c\n", x, c2);
printf(" and\t%d\n", i2);
printf(" The first character was (%c)\n ", cl);
printf ("and is represented by the integer %d\n", cl);
printf(" •• and the second character (%c) \n",c2);
printf("is represented by the integer %d\n", c2);
an integer
a character
a float
a character
and finally another integer
25 p 5.73
z
14
You entered
25 p
80 Mastering C Programming
5.730000 z
14
The first character was (P)
and is represented by the integer 80
•• and the second character (z)
is represented by the integer 122
Program analysis
The frrst couple of print f ( ) statements should have given you no problems,
but the third one looks a bit strange. First of all it contains an odd assorttnent of
characters in the control-string ( %s\n%s\n%s\n%s\n%s\n ), and secondly
the argument-list is spread over a number of lines. Let's dissect the
control-string fJI"St. If you look closely you will see that it is made up of five
strings of %s \n. The \n is simply the escape sequence for a new line, and the
%s is just the format expression for a string. So the effect of the complete
control-string is to output five strings on successive lines. This means that the
argument-list must contain five string constants; and indeed it does - if it did not
then the output would be undetermined. Note that these five string constants
need not have been written one to a line - the layout is unimportant so long as
the number of strings match the number expected. Both of the following are
valid constructs for this statement and would produce exactly the same result.
printf("%s\n%s\n%s\n%s\n%s\n",
"an integer", "a character", "a float",
"a character", "and finally another integer");
The data are input to the program via the two scanf () statements. The fJI"St of
these, scanf ( "%d ", &il) ; , is used to assign an integer constant entered at
the keyboard to the variable il. The integer is terminated as soon as a 'white
space' is entered at which point control moves on to the next statement. ('That is
as soon as a space, a tab, or a new line is encountered.) The second scanf ()
statement contains a control-string to read in the remaining items - a character
(%c), a float (%f), another character (%c) and an integer (%d). Notice that the
items read in must correspond to the formats defmed by the control-string but the
exact layout is immaterial. So the following two input strings would also be
read correctly by the pair of scanf () statements.
The data are output by the print f ( ) statements using the white space
character tab {\ t) to produce a rudimentary formatted layout. A later chapter will
detail how to produce a more fully structured layout.
The last two print f ( ) statements show how a character can also be
displayed in its (decimal) coded form (in this case ASCII), that is by using the
int conversion character {%d) instead of the one for char {%c).
SUMMARY
EXERCISES
1. Check your own documentation and find out the particular set of keywords
which are used in your implementation. You will find that the minimum you
have will be the 27 listed in Table 3.1 (i.e. all those apart from the ones in
parentheses).
82 Mastering C Programming
2. Which of the following are valid names for variables, or symbolic constants,
and which are invalid? Give the reason(s) for your answer.
j.SUN'S_DIAMETER
3. The program listed below uses the operator size of ( ) to compute the
number of bytes taken up by the various data types. This operator takes the item
immediately following it and computes its size in bytes. The item can be a data
type (which must be enclosed in parentheses)- as in this example- or a variable
name. So sizeof (area) would compute the number of bytes occupied by
the variable area not the value of the variable area. (Note that in the
ANSII standard the function sizeof returns a long int so the conversion
specification %Lf is required in the printf statements to produce the correct
result.) Enter the program, compile and link it Finally run it and compare the
results with the answers you expected.
4. Which of the following are valid assignment statements, and which invalid,
and why? a. 2x = 6;
b. year = 1988;
C. total + 6 = 8;
d. elephant = giraffe = monkey 0;
e. sum = sum + 4;
work out what the output should be. Once you have done that, run the program
and see how the results compare.
6. Write down the output which you would expect from the print f ( )
statements below, given the following information.
7. One of the options of the line editor program discussed in Chapter 1 is a help
option which is used to provide information about the various commands
available to the user, how to use them and their syntax etc. Design a suitable
display based on the list which we developed earlier and then write the code to
produce it. (For the moment this can be coded as a program; later on you will be
able to modify it so that it becomes a function.) Note that this problem requires
that a % symbol is displayed on the screen. There are two ways of doing this:
what are they?
8. Modify the conversion program (Program 3.3) to output the area in square
miles and hectares, as well as square yards.
9. The owner of a local cycle shop has just bought a microcomputer to help deal
with accounting and stocktaking. She is also keen on programming and has been
dabbling in C. One problem which she decided was worth tackling is the
following.
The gear ratio of a cycle is defined as the ratio between the number of teeth
on the chain wheel to the number on the rear sprocket (i.e. gear_ratio =
chain/rear.) Write a program to work out the gear ratio. Use the following values
to test your program: chain 34, rear 14, 16 and 18.
10. Extend the above program to work out how far the cycle will travel for each
revolution of the pedals - assume a wheel diameter of 27 inches.
11. Write a program to calculate the reciprocal, square and cube of a given
integer (in the range 1 to 20). Hint: think about what data types you should use.
12. Write a program to convert a time in seconds to hours minutes and seconds.
Produce the output in 24 hour format, e.g. 16:45:20.
~ Selection in C, or ~which way
~next?'
" ... to be or not to be, that is the question ... " William Shakespeare, Hamlet III i
4.1 INTRODUCTION
Relational operators
These operators are used to compare two expressions and they produce the int
value 1 (TRUE) or 0 (FALSE). (Remember the discussion in the last chapter
when we noted that an expression can be a single variable, a constant, or any
combination of valid tokens.) The complete list of relational operators is given
in Table 4.1.
equal to Low
> greater than High
< less than High
>= greater than or equal to High
<= less than or equal to High
!= not equal to Low
The first and last of these operators (== and ! =) are often referred to as equality
operators, for obvious reasons. These relational operators are all binary
operators which means that they have two operands. The syntax of a relational
expression is therefore:
i <= 100
x > max
(x * x - 2x + 4) != 0
ch != 1 \n 1
The first expression will have a value of 1 as long as i is less than or equal to
100 but it will take on the value 0 once i becomes greater than 100. In the third
example the whole expression is evaluated and then compared with the in t
constant 0. In the last example a character is compared with the 1 \n 1 character
to test for the end of a line.
When using the operators consisting of two characters make sure that no
spaces are inserted between them. Thus > = and ! = are not relational
operators.
86 Mastering C Programming
if (Z == 3,5 * X - 5)
Since the arithmetic operators have a higher precedence than the relational
operators this is in fact equivalent to:
if (z == (3.5 * x- 5))
This second form is preferable since it makes it clear that the variable z is being
compared with the expression ( 3 . 5 * x - 5) .
In addition to the relational operators which we have been discussing there are a
number of logical operators. There are three of these operators, I (NOT), &&
(AND) and II (OR). They are typically used to test sets of conditions. So, for
example, I might base my decision on whether or not to go for a walk on
whether there is enough time before tea and whether it is fine. The expression
might then take on the form
The NOT operator comes in useful when we want to put on our coat if it is
raining. So the expression NOT fine might be coded in C as:
i f ( ! fine )
printf("\n You had better put on your raincoat.\n");
Finally we might use the logical operator OR when there are alternative
conditions to test. So we might want to go for a walk if it is fine or if the rest
of the family are watching the television. We would then have
if ( fine I I tv_being_watched )
printf("\n Go for a walk to get some peace.\n");
Table 4.2 The truth-table for && and II in terms of truth values
X y X && y X II y
T T T T
F T F T
T F F T
F F F F
Under x the letters T and Fare written repeatedly as many times as is necessary.
For two variables this means two repetitions, with three it would mean four, and
so on. Under the y variable the values are written in pairs. Following this
procedure all possible combinations of the two variables x and y will be written
down. The columns under the AND and OR operators are then filled in,
according to the rules of the appropriate logical operators, to produce the final
table.
Table 4.3 The truth-table for && and II for any expression
X y X && y X II y
non-zero non-zero 1 1
0 non-zero 0 1
non-zero 0 0 1
0 0 0 0
Notice that each of these operators consists of two identical characters and that
there is no space between them (e.g. && is not the same as & &).
One of the examples of the use of these operators involves testing whether a
character is a vowel. There are five possibilities and an expression of the form :
if ( c == ' a ' I I c == ' e ' I I c == ' i ' II c == ' o ' I I c == ' u ' )
Suppose we have a test which involves fmding the reciprocal of a variable and
testing whether this reciprocal is greater than a fixed value. Let the variable be x
and suppose we want to continue if 1/x is greater than 0.001. We could use the
following expression to perform the test:
Listing some possible values of x in a modified truth-table (Table 4.4) will help
to illustrate how this expression is evaluated.
The most important point to note about this example is that evaluation of the
expression (x != 0 && 1/x > 0.001) stops as soon as the first sub-expression
becomes FALSE (i.e. when x = 0). This ensures that a runtime error does not
occur in an attempt to evaluate 1/0. Whenever you are considering constructing
even a very simple logical expression it is well worth taking a few minutes to
construct a table similar to the one above, or to Table 4.2, so that it is clear
Selection in C, or 'Which way next?' 89
Table 4.4 Truth-table for the expression : (x != 0 && 1/x > 0.001)
A B A && B
X X != 0 1/x 1/x>0.001
10 T .1 T T
100 T .01 T T
1000 T .001 F F
0.0 F not evaluated F F
if(expression)
statement1
statement2
i) i f (error)
printf(" Incorrect range given");
iv) i f (c == 11I c
a 1 1 e 1 II c 1 i 1 II c 1 0 1 I Ic luI)
vcount++;
v) i f (c ==1 \t 1 ) {
tab_count++;
putchar(SPACE);
The examples above use a variety of different expressions as the test condition.
In example (i) the value of error is tested to see whether it is TRUE or
FALSE (i.e. non-zero or zero). Examples (ii), (iii) and (v) use the relational
operators== and> in the conditional expression and example (iv) uses a mixture
of both relational and logical operators. The body of the if statement (the
single statement in the first four examples or the block consisting of two
statements in example (v)) is executed if the corresponding expression is TRUE.
Program 4.1
I* simple if example *I
I* giving change *I
I* money.c *I
#include <stdio.h>
main()
I* calculate change *I
printf("\n You need %fin change \n", change);
pounds = change;
five_pound = pounds I 5;
pound = pounds % 5;
I* now calculate pence *I
pence= 100 * ( change+ .005- pounds);
fifty = pence I 50;
rest = pence % 50;
twenty = rest I 20;
rest = rest % 20;
ten = rest I 10;
rest rest % 10;
five = rest I 5;
rest = rest % 5;
two = rest I 2;
one = rest % 2;
printf("\n The change can be made up as follows: \n\n");
if( five_pound > 0) {
printf ("\t %d five pound note", five_pound);
if( five_pound > 1 )
printf("s");
printf("\n");
if ( pound > 0) {
printf("\t %d pound coin", pound);
if ( pound > 1 )
printf("s");
printf("\n");
if ( fifty > 0)
printf("\t %d fifty pence coin\n", fifty);
if( twenty> 0) {
printf ("\t %d twenty pence coin", twenty);
if( twenty> 1 )
printf("s");
printf("\n");
i f ( five > 0)
printf("\t %d five pence coin\n", five);
i f ( two > 0) {
printf("\t %d two pence coin", two);
i f ( two > 1 )
printf("s");
printf("\n");
i f ( one > 0)
printf("\t %done pence coin\n", one);
This program, although rather long, only uses a few simple concepts. We will
look at each one in tum. First notice the use of meaningful variable names. This
helps considerably with the readability of the program and should help you to
understand how the program works. Using names in this fashion provides a
certain amount of self-documentation and alleviates the need for comments
explaining the purpose of each of the variables.
Next, notice the way the change is calculated. Initially the amount in whole
pounds is calculated by simply assigning the int variable pounds to the
float variable change. Truncation of the floating point number occurs thus
providing the correct whole number of pounds. However if we rely on truncation
to obtain the amount of pence required we run into difficulties. Since the
floating point number change may not be stored exactly, in order to make sure
that the correct arithmetic is carried out we need to add half a pence (.005) onto
change to obtain the int number of pence.
The third point to note is the way we have used integer division and the
modulus operator to compute the number of coins for each denomination.
Finally we come to the use of the i f statements! The frrst one simply prints
a message out if we have failed to hand over enough money. The program is
then terminated by use of the exit () statement. This function returns the
value of its parameter to the calling process (e.g. the system). By convention 0
is used to indicate that the program ran correctly and a non-zero value is used to
indicate abnormal program termination. The main use of the if statements
occurs in the output section. We could produce a very rough and ready output
without the use of these statements, but that would result in output of the form:
Thank you
The majority of the if statements simply test whether the relevant variable is
greater than zero and if so print the appropriate message. But notice how we have
employed an if statement within another if statement to add an 1 s 1 on when
necessary. This code can be further simplified by using the keyword else, to
which we now turn our attention.
If you are at all familiar with programming, you will not be surprised to learn
that C provides a means of extending the usefulness of the if statement by
means of the keyword else. The syntax of the if . . . else statement is:
if (expression)
statement1
else
statement2
next statement
i f (a != 0)
printf(" a is positive or negative \n");
else
printf(" a is zero \n");
printf("The End\n");
94 Mastering C Programming
We can modify our change program using if . . . else as follows. When the
possibility arises of more than one coin being given we need to cater for adding
an 1S1 to the end of the message e.g.
3 pound coins
We can achieve this by using a character which can be either the letter s or a
space. (Since the extra character will come at the end of the line the space won't
affect the appearance of the output.) We can then output this character as part of
the main print statement. So we might have, for the pound coin case:
i f ( pound > 0) {
i f ( pound > 1 )
plural 's'; I* char variable plural */
else
plural I I ;
/* space character */
printf("\t %d pound coin %c\n", pound, plural);
This whole program fragment is in fact one single statement. There is only one
semi-colon and no braces. Now consider the following example:
Program 4.2a
i f (a != 0)
i f (b == 0)
printf(" a is non-zero and b is zero\n");
Selection in C, or 'Which way next?' 95
else
printf(" a is zero \n");
printf("The End\n");
The intention is, and the layout implies, that if a is zero then the message "a
is zero" is displayed. If a is positive or negative and ifb is zero then the
message "a is non-zero and b is zero" is displayed, whilst ifb is
non-zero no message should be displayed. However what actually occurs is that
in this last case (both a and b non-zero) the message "a is zero" appears-
not what we might have expected or intended! How can we explain this? A close
look at the code of Program 4 .2a will show that the e 1 s e is in fact part of the
second if statement and not the first as was intended. This brings us to the
following rule:
Program 4.2b
if (a!= 0)
i f (b == 0)
printf(" a is non-zero and b is zero\n");
else
printf(" a is zero \n");
printf("The End\n");
This makes it clear to the reader what the code is doing - even though it is not
doing what was intended. How can we rectify the matter, since Program 4.2b
although laid out correctly will still not produce the intended result? There are a
number of ways to avoid this pitfall, two of which are illustrated below.
Program 4.2c
This piece of code uses braces to force the required association - because of the
braces the nearest if to the else is the first one, as required.
96 Mastering C Programming
Program 4.2d
In this example we have made use of another else, which will be associated
with the inner if, and the empty statement. The empty statement is necessary
because the else on its own is a keyword, not a statement. It is normal
practice, and good practice, to position the semi-colon as in this example, rather
than on the same line as the else. This helps to make it clear that an empty
statement is indeed intended. If this statement is reached, because both a and b
are non-zero nothing happens but control passes directly to the statement
following (i.e. The End is displayed).
When the choice between alternatives only amounts to two or three, the if
else construct which we have been looking at will probably suffice. However,
as the number of alternatives increases, this construct can become very
cumbersome. In such cases the switch statement is a useful alternative. The
basic syntax has the form:
switch (integral_expression) (
case integral_constant_expressionl
statement] /* can be one or more statements */
case integral_constant_expression2
statement2
default
default statement
next statement
Consider the task of identifying 'white spaces' in a stream of data and keeping
count of the number found and the total of other characters in the data. A switch
statement which would accomplish this task is:
switch (c) (
case '\n' :
case '\t' :
case • ' :
ws_count++;
break;
default:
other_count++;
break;
next statement
Note that, strictly speaking, the last break; statement is not necessary as
control will fall through to next_statement after other_count has been
incremented. However if you adopt this convention then if further case
statements are added the presence of this break will prevent the unintended
execution of the new statements. As an illustration, suppose we wish to add to
the above a case statement which will enable a count to be made of the number
of occurrences of the% character and that other count will be used to count
any other character not already counted. We then have:
switch (c) {
case '\n'
case '\t'
case ' ' ·
ws_count++;
break; I* break #1 *I
default:
other_count++;
break; I* break #2 *I
case '%' :
p_count++;
break; I* break #3 *I
next statement
I* function check_command()
** this function simply checks for valid commands
Selection in C, or 'Which way next?' 99
Program 4.3
Here we have used the 'fall through' facility to allow each of the valid options to
set valid to zero at one point rather than repeating the assignment statement
for each case.
This function uses arguments in its parameter list (em, nl and n2) and
returns an integer value which is then used in main () to control subsequent
program action. Although we haven't formally discussed these details you will at
least be familiar with the former through the use of print f ( ) and scan f ( )
and we mentioned the latter in Chapter 2. We will be dealing with functions in
more detail in Chapter 6.
100 Mastering C Programming
Another part of the line editor program is the one used to process the chosen
option. This function can easily be developed at this point. Recall that the
process function is required to transfer control in order that different processes can
be performed (Table 1.2). The options are: append, delete, help, insert, print,
quit, read and save. Assuming that the character variable com holds the relevant
character, the pseudocode becomes:
SUMMARY
• used the relational operators ==, <=, >=, <, > and ! .
EXERCISES
a. !a && !b II c.
c. b II !c II d II (e && !EOF).
2. A line editor program uses a function to check that a valid range of line
numbers has been entered in the edit command. Two line numbers are required
(n1 and n2) which may be in the range 1 to last, where last is the last line
number of the current text; n1 must be less than or equal to n2. There are a
number of ways of coding this. Write down a version using a single i f
statement and one using switch.
Other cards do not contribute to the point count. In addition extra points are
allocated depending on the distribution (i.e. the number of cards held in each
suit). These extra points are added as follows: if a suit has more than four cards
then 1 point for cards five and six, 2 points for the seventh and subsequent cards.
102 Mastering C Programming
• 2J • AK6 + A K J 10 9 5 2 4 4
Write an algorithm which will calculate the total number of points held in a
hand of thirteen cards.
7. One system of bidding in bridge lists the point count requirements for various
opening bids. The table is given below. By using switch and/or if I if
. . . else statements write a program fragment to simulate this table and print
the appropriate bid. For the purposes of this exercise you do not need to know
the definitions of the various terms involved. Allocate variable names for terms
such as balanced hand, powerful distribution (these are mutually exclusive),
vulnerable etc. and test their values to select the appropriate action. Base your
program on opening bids up as far as two in a suit, ignore the three and four
bids.
POINTS NEEDED FOR AN OPENING BID
" 'You are old, Father William,' the young man said,
'And your hair has become very white;
And yet you incessantly stand on your head -
Do you think, at your age, it is right?'
5.1 INTRODUCTION
while (expression)
statement
time = 0; /* (i) */
while( time < limit
time++;
i = -100; /* (iii) */
while ( i <= 0) (
printf("%d\n", i);
i += 10;
/* (V) */
while((c=getchar()) != EOF && c != '\n')
char_count++;
Notice that in all of the above examples the expression part of the wh i 1 e
statement consists of a relational or logical expression - i.e. an expression
which tests a condition the result of which determines whether or not the body of
the loop is to be executed. If the test proves TRUE then the statement (perhaps a
compound statement) in the body of the while loop is executed and control
passes again to the expression, which is tested once more and the whole process
is repeated. In some cases (e.g. (ii) and (v)) the test incorporates a modification
to the variable being tested - in these examples a character is read from the input
device by means of the get char () function. In the other examples the
variable tested is modified in the body of the while loop (by t irne++; , i +=
10; andscanf("%f", &number); respectively).
The while statement performs the test at the beginning of the loop, which
in turn means that the statements in the body of the loop may not be executed at
all.
Doing it again and again! 105
while( (c = getchar()) == 1 1 )
/* do nothing */
All the work done by this statement is performed by the expression part of
while- the body itself 'does nothing' and to an uninitiated observer this might
appear as a useless statement. The expression c = get char () reads a
character from the standard input device; this character is then compared with the
character ' ' and the loop continues while spaces are read in. Notice that we
could also lay out this particular loop as:
but this is more confusing than the former version. Whenever a null or empty
statement is used it is much better to make it obvious that this is indeed intended
by placing the semi-colon on a separate line.
if(expression) while(expression)
statementl statementl
next statement next statement
With just the very brief outline given above we can write down the essential
elements of the algorithm.
declarations
set vowel counters to zero
read character
while character not End-Of-File do
if character is 'a' then increment a_count
if character is 'e' then increment e_count
if character is 'i' then increment i_count
if character is 'o' then increment o_count
if character is 'u' then increment u_count
read next character
end while
print a_count, e_count, i_count, o_count, u_count
Before we start coding let's decide upon the layout for the summary. This is not
a very difficult task, but it is worth thinking about the output before coding,
especially in the case of more complex programs. One layout might be:
a e i 0 u
number found: 12 23 10 8 0
There are many other possibilities, and you might like to modify the program to
suit your aesthetic tastes later on!
The next task is to decide on what variables are required; we need one to
represent the value of the character read (say ch) and then a counter for each
vowel. We have called them a_count, e_count etc. in the algorithm; these
seem to be reasonable names, so we will stick with them.
Doing it again and again! 107
There is one further point we need to address before starting to code the
algorithm and that concerns how we tell when the stream of characters has been
terminated. With a file the EOF marker will be used (typically 1 \n 1 followed
by a CTRL D, but this is system-dependent); with the keyboard this will not be
the case. Various solutions can be found, but let us suppose that the presence of
two successive newline characters signifies the end of the input stream. This
presents us with another problem - the program needs to 'remember' the
previous character read - so we need to introduce another variable (say
last_ch). Two conditions therefore have to be met before we exit from the
while loop, both ch and last_ch must be newline characters. How can we
set up a conditional expression to achieve this control? One approach is to use a
flag which is set to FALSE when these conditions are both met. This involves
the use of an if statement to test the values of ch and last ch. We can now
write a refmement of our original algorithm.
declarations
integer a_count, e_count, i_count, o_count, u_count, more
character ch, last_ch
set vowel counters to zero
set more to TRUE
read character
while more do
if character is 'a' then increment a_count
else if character is 'e' then increment e_count
else if character is 'i' then increment i_count
else if character is 'o' then increment o_count
else if character is 'u' then increment u_count
set last_ch to ch
read next character
if last_ch is newline AND ch is newline then
set more to FALSE
end while
print heading lines
print a_count, e_count, i_count, o_count, u_count
Program 5.1
#include <stdio.h>
#define TRUE 1
#define FALSE 0
main()
{
char ch, last_ch, more = TRUE;
int a_count=O, e_count=O, i count=O, o_count=O,
u_count=O; /* declare and initialise counters */
Notice that the program does not follow the algorithm exactly. We have
initialised the vowel count variables to zero and the flag (more) to TRUE at the
same time as declaring them. We have also added a few more print statements.
Doing it again and again! 109
A comment on break
We mentioned at the beginnning of this section that the keyword break could
be used with the while statement. (In fact it can be used with any of the
looping control structures.) Its function is the same as when used within a
switch statement. Once it is encountered control passes to the next statement
outside the enclosing looping statement. Its main use is to allow exit from the
loop from a point in the middle of the loop. Consider the following program
fragment.
Exit from the while loop can be achieved in two ways. Either a newline character
is read or the variable count exceeds 1000.
We noted above that it is possible for the body of a while statement not to be
executed. This is because the condition is tested on entry to the while loop,
and if it is found FALSE then control passes directly to the statement following
the body of the while loop. However there are a few occasions when it is
necessary for the statements in the body to be executed at least once. For such
cases the do . . . while is the obvious choice. This loop, which is often just
called a do loop, has the form:
do
statement
while (expression);
which means that the loop (i.e. statement and expression ) is executed at least
once - because the test is not carried out until the end of the body of the loop.
program, was intended to display a list of options on the screen from which to
choose (the main_menu function). One of these options was the quit option
and this provides us with a ready example of the use of the do statement.
Consider the logic of the algorithm; the menu must be displayed until the quit
option is chosen, in which case the program terminates. The algorithm takes the
form:
do
execute main_menu()
read option
if option is '1' then execute shuffle()
else if option is '2' then execute deal()
else if option is '3' then execute display()
else if option is '4' then execute count()
else if option is '5' then execute bid()
else if option is '6' then execute play()
while option not equal to 'Q'
This form ensures that the menu will be displayed on the screen until the Quit
option is selected. (We have introduced one further pseudocode keyword, i.e.
execute. This is used to indicate that a function (or an as yet ill-defmed series
of statements) is to be carried out.)
This particular control structure can be very useful for specific tasks. At other
times the whi 1 e construct is more appropriate. It is a good rule to use
whichever most naturally expresses the problem concerned. One instance where
you should not employ the do loop is illustrated by example (iv) in the
previous section. Here there are two uses of scanf ():one prior to entry into
the while loop, and one in the body of the loop. At frrst sight it might appear
that this structure could be replaced by the following using do . . . while:
A close examination of this piece of code should reveal the error which has been
made. We now only use the scanf () function once, which is one step forward,
but because the test is carried out on exit from the do loop it is possible that a
negative number may be entered and an attempt made to find the square root - at
least two steps back! We repeat the correct version for comparison.
while (n > 0) {
printf("The square root of %f %f\n",n, sqrt(n));
scanf("%f", &n);
One final point worth making before we leave this control structure concerns the
form of the body of the loop. Even when the body consists of only a single
statement it is considered good programming practice to enclose it within braces.
This prevents the while keyword at the end of the loop being confused with a
simple while statement. Thus
do {
c = getchar();
} while ( c == ' ' ) ;
The final method used to perform repetitive tasks in C is the for statement.
Consider example (iii) of section 5.2 in which a variable (i) is initialised to
-lOO(i = -lOO),modified(i += lO),andcompared(i <= O).Thesethree
operations of initialisation, modification and comparison are crucial to the
correct operation of this and many other similar loops, but they are distributed
over three lines of the program. It would be much neater and clearer for these
operations to be collected together in one place. The for loop enables us to
achieve this, so we can rewrite the while loop of this example as:
The loop behaves in exactly the same way as the while loop in the original
example, but it has the advantages that it is both more compact and easier to
interpret.
,
expression 1
FALS E
~ expression2 1---
statement
expression3
'
next_statement
In the remainder of this section we will be examining the for loop in some
detail. Once you have worked through this material and the exercises at the end
you should be familiar with a rich variety of uses for this particular control
structure and you will have discovered that it is much more versatile than the
standard for loop in many other programming languages.
If you are conversant with other languages (e.g. Pascal or BASIC) then the
essential idea of a for loop will already be familiar to you. You will be aware,
for instance, that such loops are often used to perform a predetermined number of
iterations, or repetitions, of one or more statements. So, for example, we might
wish to produce a table of squares and cubes of whole numbers from 1 to 20 in
steps of 1. This could be achieved by the program fragment:
The variable num is first initialised to 1, this value is then tested for being
less than or equal to the constant 20 and, this expression being TRUE, the
number itself, its square and cube are displayed. The value of num is now
incremented (n urn++) and the test applied again. This process continues until
num reaches the value 21, at which point the second expression (num <= 2 0)
becomes FALSE and so the for loop is exited.
This, perhaps rather obvious, structure can be extended to give even more
flexibility. We will examine a few possibilities. Take a look at the diagrammatic
representation in Figure 5.1. The structure of a for loop does not in fact dictate
what type expressions 1, 2 and 3 should be. All that is specified is when these
expressions are evaluated. The diagram helps to make this point clearer - the
for loop continues execution and thus the repetition of statement continues
until expression2 is FALSE. Note that expression] can be any valid
expression, although most usually it will be used to update a control variable.
Since there are no restrictions on what this expression can be we have at our
fingertips a wide variety of ways of modifying the control variable. Some
examples are given below using the various arithmetic assignment operators
introduced in Chapter 3:
n *= 2 n I= 2 n is multiplied/divided by 2
at the end of each pass
It will not have escaped your notice that expression2 will require some
modification if the control variable is being reduced rather than increased (i.e. by
means of subtraction or division). Thus to produce a loop which starts with i
equal to 100 and steps down by 5 while i remains positive we might have the
following for expression:
In the basic structure of the for loop, any of the expressions within the first set
of parentheses can be omitted; the modification of the test variable can be
performed within the body of the loop; the variables can be initialised outside the
loop, or can be non-existent.
In the following example all three expressions are missing from within the
brackets:
for (;;)
statement
next statement
You may be asking what this achieves. The statement within the body of the
loop will be executed continually; since there is no test expression (expression2
in the basic form) the test is always considered TRUE and thus there is no means
of exiting from the loop. When would such a construct be used? If we wanted to
read a character from the keyboard repeatedly, this could be achieved by this type
of loop, for example:
Doing it again and again! 115
for (;;) {
c = getchar();
putchar (c) ;
char_count++;
Note that a while loop could also be set up to perform the same function. An
equivalent version to the above, but using while, is:
while (1) {
c = get char() ;
put char (c) ;
char_count++;
Since the constant 1 represents the truth-value TRUE this loop will execute
until the program is terminated (e.g. by a run-time error, or by the power being
switched oft). Although these two examples of control structures are logically
correct they should be used with caution as they both give rise to an infinite
loop. They are most often used when testing a piece of code and an unknown
number of repetitions are required or possibly in conjunction with break.
The last main topic in this chapter concerns a new operator. The comma operator
serves a special function and can be particularly useful in conjunction with the
for statement. Consider the task of finding the sum of whole numbers and the
sum of their squares in the range I to 20. This could be achieved as follows:
sum = 0;
s_sum = 0;
printf("\n\t i\t sum \t sum of squares");
for (i = 1; i <= 20; i++){
sum += i;
s sum += i*i;
This version, making use of the comma operator, keeps the initialising of the
three variables, s urn, s _ s urn and i together in one place in the for statement
and makes it clear that these variables belong in the same section of code.
One further aspect of the comma operator is illustrated by the program below
which represents two attempts at calculating the first five terms of the series
defmed by the equation
Xi =Xi-1 + Yi , i = 1, 2, ..... 10
Yi =Yi-1 + 1
where x0 =10, Yo = -1,
which has 10, 11, 13, 16, 20 as its first few terms.
Program 5.2
#include <stdio.h>
main()
int x, y;
/* attempt #1 */
printf("\nThe series x = x + y, y = y + 1 \n");
printf("\nWith expression 3 : x += y, y++ \n");
for (y = 0, x = 10; y < 5; x += y, y++ )
printf(" y = %d: x = %d \n", y, x);
/* attempt #2 */
printf("The series x = x + y, y = y + 1 \n");
printf("\nWith expression 3 replaced by y++, x += y \n");
for (y = 0, x = 10; y < 5; y++, x += y
printf(" y = %d: x = %d \n", y, x);
These two attempts illustrate the care which must be taken when comma
Doing it again and again! 117
operators are used. The only difference between them concerns the order of the
two terms making up expression3 of the for loop. This expression contains
two sub-expressions, one of which depends on the other. Thus the effect of
(y++, X += y)
The series x = x + y, y = y + 1
y 1 X = 10
y 2 : X = 11
y 3 X = 13
y 4 X = 16
The series x = x + y, y = y + 1
y 1 X = 11
y 2 X = 13
y 3 X = 16
y 4 : X = 20
Notice that we could have changed the order of the sub-expressions in the
expression! (y++ and x = 10) without affecting the calculations, the reason
being that x is initialised to a constant (i.e. 10) and so the value of y has no
effect on x in this expression.
The comma operator should not be confused with the comma separator which
is used for example to distinguish between function arguments. Thus the comma
in the expression s urn (a , b) which refers to the function s urn which requires
two arguments is a separator. However in the expression s urn ( (a, b) , c)
the first comma is a comma operator and the second is a separator. Since the
comma operator ensures evaluation from left to right this expression is
equivalent to sum ( a, c) •
118 Mastering C Programming
We have made one addition to the algorithm which we outlined above in that we
have an outer while loop so that we can continue running the program for
different values of n. We exit the loop, and the program, by entering a negative
number. This algorithm is very easily coded in C now that we have this level of
detail. Notice that we have used a while loop in the algorithm for the bulk
processing. This is perhaps better converted to a for loop as indeed we have
done in the program (Program 5.3 below).
One of the problems with this program is the time it takes as n gets large -
well, I did say it was a brute force program.
Doing it again and again! 119
Program 5.3
#include <stdio.h>
#define FALSE 0
#define TRUE 1
main()
long int i, n;
int prime; /* used to flag whether or not n is a prime */
if ( prime
printf("\n %Ld is a prime\n", n);
printf("\n Enter a positive integer (0 to terminate) ");
scanf("%ld", &n);
Notice the use of symbolic constants (TRUE and FALSE). This helps to make
the logic of the program clearer. Notice also the conversion specification for
reading and writing long integers.
We will be covering the detailed analysis of arrays and their use in programs and
functions in Chapter 8. However arrays fall naturally into a discussion of
repetition and it is appropriate to mention them briefly at this stage.
int a[lOO];
which would declare an array of type int capable of holding 100 integers and
indexed from 0 to 99.
Example
We can use arrays to make an improvement to the prime program we have just
been looking at. One method of computing primes is known as the Sieve of
Eratosthenes. This method allows us to compute all primes up to a given
integer. It is based on the observation that all numbers which are multiples of n
will also be divisible by n 2.Ild therefore cannot be primes. If we cross out all
these multiples, as we progress through the integers only primes will be left.
Let's write down the first twenty integers and see how this process works.
1 2 3 4 56 7 8 9 10 11 12 13 14 15 16 17 18 19 20
1 3 5 7 9 11 13 15 17 19
1 2 3 57 11 13 17 19
For the numbers up to twenty we have found all the primes. This process would
be continued and each time we only need divide by existing prime numbers. An
algorithm for this task could be:
Thus we begin with an array filled with Is (any other number or character would
do). The index of the array is then used to indicate the numbers we are testing.
Thus element 2 represents integer 2, element 3 the integer 3 and so on.
Program 5.4
tinclude <stdio.h>
long int i, j;
short a[SIZE]; /*short is sufficient as we are only*/
/* storing the integers 0 and 1 */
.
while ( i < SIZE )
printf("%d , i); /* prime *I
j = i;
while ( ( j += i) < SIZE)
a[ j l = 0;
while(++i < SIZE && a[i] -- 0)
/* skip to next prime */
SUMMARY
EXERCISES
1. For each of the examples above ( (i) to (v), p. 104) write down the conditions
which would prevent evaluation of the while loop, that is the conditions which
render the expression being tested FALSE. Write simple programs to test these
loops and check your answers.
2. Enter the vowel count program (Program 5.1), compile, link and run it. Test
it with some short pieces of text. Modify the conditional expression in the
while statement so that & & is used instead of 1 1. Use a truth table to work out
the required expression before coding it.
4. The vowel count program discussed above can be modified to use a do loop
instead of a while loop. Is this a better solution? If so why, if not why not?
Recode the program using a do loop and test it.
Write a program using both the do and the while constructs. Within the
loops you can simulate the action of the editor by using printf () statements
to display a suitable message. Which option, if any, is preferable, and why?
6.
a. Enter program 5.3 and compare your results with those given in the text.
Doing it again and again! 123
b. Now suppose that the expression x = 10 which forms part of the initialising
expression in the for loop is replaced by x = 10 + y. Is the order of the two
sub-expressions important? If so which version is correct? (That is (x = 10 +
y, y++), or (y++, x = 10 + y)). Modify your program to check your
answer.
7. Write a program to compute the square cube and reciprocal of integers from 1
to 20.
8. Modify the above program to allow for the start, step and end values to be
entered. Use a check to trap a divide by zero error. Include in your program tests
and suitable messages for
X- K3 + KS ....... .
3! 5!
c. Find out about the library function sin(x) and use this function in your
program to compare results with the above method.
10. Write a program which computes the sum of a series of integers stored in an
array.
0 Functions - making them
~ useful
6.1 INTRODUCTION
In Chapter 1 we introduced the concept of functions and took a brief look at how
they are used in C. The functions which we have written so far have been very
simple ones with only a limited amount of communication between the function
and the outside world. We have used library functions which are more complex,
print f ( ) being the most obvious example, and in this chapter some of the
finer points of function structure will be explored. In particular we will be
looking in detail at means of transferring information between a function and the
calling environment (i.e. the program or function which is using it).
The keyword return is used to pass control back to the calling function. (We
will drop the use of program in this context as programs are themselves
functions.) This has two forms:
In the first of these forms it is used simply to transfer control back to the calling
function. The second form enables a value to be passed back at the same time as
transferring control. An example of this use of return is illustrated by the
function ch_test ( ) which checks whether the character input is a digit or a
letter and displays a suitable message, depending on the value of the character
which has been entered.
Program 6.1
char ch;
The above use of return simply transfers control back to the calling function - no
information (or data) is passed back. Note that a ret urn statement is not
required at the end of a function since transfer of control occurs automatically
once the closing right brace of the function is reached. This particular function
could in fact be written without the use of return. How might this be
achieved?
sum()
declarations
Integer isum, number
set isum to zero
read number
wbUe number is not zero do
add number to isum
read number
end while
return isum
Program 6.2a
I* function sum() *I
I* to sum a series of integers read from the keyboard *I
I* zero terminates entry *I
scanf("!fsd", &number);
while (number != 0) {
isum += number;
scanf("!fsd", &number);
return(isum);
This example illustrates how the ret urn statement is used to pass back (i.e.
return!) the value of the variable isum. In order to test this function we need a
program which uses it One such program is given below.
Program 6.3
!include "stdio.h"
main()
Exercise
Enter the program s _test and the function sum () , compile and run it. Use as
test data the integers
Discussion
Now that you have some results we can take a closer look at the way the sum ()
function works.
First the variable is urn (which keeps track of the sum of the integers as they
are entered) is initialised to zero. Next we use the scanf function to read in the
ftrst integer and assign the value to the variable number. The while loop is
then entered and a check made to see if the number is zero. If it is non-zero the
body of the loop is executed; the number is added to the variable is urn and then
the next number is read in. The test is then carried out again. If the new number
is non-zero the while loop is executed again. Once a zero is encountered
control is passed from within the loop to the statement following, which is the
return statement. Since this particuler return statement contains an
argument (isum) its value is returned to the calling function (in this case
main ()).In fact the value of the function sum () takes on the current value of
is urn.
total = sum() ;
and this assigns to the variable tot a 1 the value of is urn. This statement
illustrates an important point concerning functions, which is that all functions
have a speciftc data type. So what type is the function s urn ( ) ? It would be
convenient, and sensible, if it was of type int, and this is indeed the case. By
default all functions are of type int, and if any value is returned this will also
be of type in t. If the variable being assigned to the function is of a different
128 Mastering C Programming
type then type conversion will take place according to the rules given in section
3.7.
Functions thus have a data type and, in general, they will also have a value.
The value of the function depends on the expression in the return ( )
statement which is executed within the function. We have just seen that in our
sum ( ) function the value returned is the value of the sum of the integers
entered. Other functions will return different values. The scanf () function for
example returns a value after each call. The three possible values it can return
are:
0 if an error occurred while the entry was being read (e.g. because a
number of type float was entered when an int was expected)
We can use this information to make the function sum () more robust
Program 6.2b
return (isum);
The if statement is used to test for a valid integer entry. If the character(s)
entered do not match the conversion_string then scanf () returns a 0 or an
EOF, the test fails and the value of isum is returned to the program. If normal
termination occurs (through the entry of 0) then the second part of the
conditional expression becomes FALSE and again isum is returned.
One further modification can be made to this function. Now that we are using
the return value of scanf () to test for a valid entry we can use any invalid
entry to terminate the calculation, thus dropping the need for zero to be used as a
stop indicator. So we have for the final integer version of this function:
Functions - making them useful 129
Program 6.2c
float product(void)
float x;
return(x);
double y;
int i;
return(y);
char c;
return (c);
130 Mastering C Programming
The s urn ( ) function (Program 6.2b) can now be rewritten so that the sum of a
set of numbers of type f 1 oat can be calculated. We only need to make a few
modifications to the program and the function to achieve this. A first attempt is
set out below:
Program 6.4
#include <stdio.h>
main()
float f_total; /* A */
If this program and function declaration are entered as they stand and compiled a
compilation error may occur. This is because the function f _ s urn () is first
encountered in the statement
f_total = f_sum();
where there is a mismatch between the data type of the variable f_ total and
the function f _ s urn ( ) . Why is this since we have defined f _ s urn ( ) to be of
type f 1 oat? The reasoR lies in the fact that this defmition comes after the frrst
call of the function and therefore the f _ s urn ( ) is assumed to be of type in t
(by default). There are two ways to get around this difficulty. The first (known as
prototyping) and most common, is to specifically declare the function at the
beginning of the program, either externally (before main ())or internally in the
Functions- making them useful 131
body of the program. Thus we could solve our problem by modifying line I* A
* I in Program 6.4 to read:
float f_total, f_sum(void); /* A */
This informs the compiler that a function f _sum ( ) is being used which is of
type float.
Remember that the data type returned from a function is that of the function
itself, so that if the expression within the ret urn statement is of a different
type from that of the function then the normal type conversions will occur.
132 Mastering C Programming
return -100;
So far we have dealt with ways in which information is passed back from a
function to the calling environment. The next aspect we need to investigate is
how to pass data into a function for use within it. We are now in a position to
consider the general form of a function which is:
return(expression);
1. Shuffle
2. Deal
3. Display
4. Count points
5. Bid
6. Play
QQuit
We require a function to read and check for valid characters entered from the
keyboard and a second function to carry out the chosen command.
Functions - making them useful 133
The pseudocode for the function to read a character from the keyboard is:
do
read a character
while not a valid option
return (character)
which in C is simply:
Program 6.5
char valid_c(void);
char c;
do {
c = getchar();
) while (c !='Q' && (c < '1' II c > '6'));
return (c);
Notice that although 1 and 6 are integers in this function they are treated as
characters. The reason for this is that they are just being used as symbols to
effect a choice and so the numerical values are unimportant. It also means that
we can treat the character 'Q' in the same way as the digits 1 to 6, thus
simplifying the code. You may be puzzled by the form of the conditional
expression c ! = ' Q ' & & ( c < ' 1 ' 1 1 c > ' 6 ' ) . It is not immediately
obvious that this is the correct expression. However a little thought should
prove to you that it is correct. If you are still not sure, construct a truth-table
such as that outlined in Chapter 4.
Having obtained a valid character from the keyboard you can use it to enable
the selection of the appropriate option to be made. An obvious way of doing this
is by means of the switch statement which we covered in Chapter 4. We can
write down the function almost immediately, viz:
Program 6.6
switch (c)
{
case '1':
printf("\n Executing shuffle\n");
break;
134 Mastering C Programming
case '2':
printf ("\n Dealing the hands\n");
break;
case • 3.:
printf("\n Displaying the hands\n");
break;
case • 4.:
printf ("\n Counting the points\n");
break;
case '5':
printf("\n Bidding \*");
break;
case '6':
printf ("\n playing Bridge! \n");
break;
case 'Q':
return(O);
return(l);
This function is the ftrst one which we have written which passes arguments
into the function. Notice that the parameter in the function call is declared in the
parameter list. In older implementations of C the declarations would be before
the opening left brace of the function body.
Notice that the return statement is used twice, once if the Q option is
executed, in which case the value of the function (execute_option ())is 0,
and once when any of the other options are invoked, in which case the value of
the function is set to 1. These values can be used to control the program after
this function has been called.
Program 6.7
main()
{
void main_menu(void); /* prototyping functions*/
char c, valid_c(void);
Functions - making them useful 135
do {
main_menu();
c = valid_c ();
while( execute_option(c));
Prototyping
The first point to note about this program is the way in which prototyping is
used for the functions. Two of the functions require no arguments and this is
indicated by use of the keyword void. The third function returns an integer and
because of the fact that the default type of a function is integer it is not strictly
necessary to add the keyword int. However it is good practice to adopt the
typing of functions even if they are of this type. Another point concerns the
prototyping of parameters. In the example above we used char ch to indicate
that an argument of type char is expected. Note that the scope of the parameter
ch is restricted to inside the parentheses. Any variable name, or none, can be
used, not necessarily the one used in the function prototype. So we could equally
well have
int execute_option(char character);,
int execute_option(char);,
or int execute_option(char c);
The one used in the program is preferable as it makes it more explicit what role
the argument is taking.
Notice that by using functions for each well-defmed logical process the form of
the program becomes very simple. It essentially consists of some declarations
and a single do loop which calls the two functions rna in_menu ( ) and
valid_c ().The bulk of the work is done by the execute_ option ()
function which forms the conditional expression of the do . . . while loop.
We will return to this program later on and add a few more functions to it so that
we can get something useful out of it!
In this section we are going to look at a few examples which illustrate the
design and use of functions. They cover some of the functions required for the
line editor program and a calendar function. We begin with some simple
136 Mastering C Programming
functions for use with the line editor. When entering the commands in the line
editor, spaces are irrelevant and it would be useful to have a function to skip over
them. We can use the get char () function which we have used already to
obtain a character from the keyboard. All we need in addition is some form of
loop to allow characters to be read until a non-space is entered. The do
while provides us with the simplest solution. So we have:
Program 6.8a
I* getnextchar() function *I
I* This skips spaces in a character stream *I
I* It returns the character read *I
char getnextchar(void)
char c;
do {
c = getchar();
) while(c == 1 1 ) ;
return(c);
do {
) while ( (c = getchar ()) == 1 1 );
An even more compact form can be obtained if we use the while construct
instead of the do . . . while loop, i.e.:
Program 6.8b
I* getnextchar() function *I
char getnextchar(void)
char c;
return(c);
Since in this program a variety of numbers and other characters need to be read in
another useful function would be one which tells us whether a character is a
digit. This will take a character as an argument and return TRUE if it is a digit
and FALSE otherwise. Once again we can write this function down straight
away.
Program 6.9
/* isadigit(char c) function*/
/* This takes a character as an argument and */
/* returns TRUE if it is a digit and FALSE otherwise */
int isadigit(char c)
The final function we are going to look at from the line editor program is one to
read a series of characters and convert them to an integer. The final version which
we will be using in the program will differ slightly from this one. We will
examine the modifications required later on. For the moment all we are concerned
with is that a decimal digit should be returned. We can convert a character into a
decimal digit quite simply by subtracting 1 0 1 from the character and assigning
it to an integer variable. (Providing of course the digit is in the range 1 0 1 to
1 9 1 ) What we are in fact doing is subtracting the ASCII code for
'0' from the
ASCII code for the character. This process can be represented in pseudocode as:
declarations
integer i
character c
read inc
if c is a digit then
set i to c - '0'
end if
Next we have to consider the case of integers which are greater than 9. We may
wish to edit line 15, for example. As successive characters are read in, if they are
digits we need to modify the existing number in some way before adding the new
digit to it. This process is again reasonably straightforward. We simply multiply
the existing number by 10 before doing the addition. So the first character is a 1
138 Mastering C Programming
and the second a 5, then the two together represent 15, which is just 10 times 1
plus 5.
In this function we need to test for a character being a digit. Well we have just
written one such function so we can incorporate it in our new function. We
therefore finally arrive at
Program 6.10
char c;
int i = 0; I* initialise the number to 0 *I
c = getchar();
while( isadigit(c)) I* execute while c is a digit *I
i 10 * i + c - '0'); I* decimal convert *I
c = getchar () ;
return(i);
These functions although small simplify the task of getting valid characters and
processing them for further use.
Now for a little light relief. You may be familiar with the nursery rhyme:
We are going to write a program which, given a date, will output the day of the
week corresponding to it and the appropriate section from the above rhyme. The
hardest part about this problem is finding out which day of the week corresponds
Functions - making them useful 139
to which date. Luckily it has already been worked out for us. Zeller's algorithm
performs this task. It works as follows. Suppose we have a date given in the
form 19/12/1965, which in general terms has the form 0/M/Y. Then, if month
(M) is January or February, they are taken as months 11 and 12 respectively of
the previous year. Other months are numbered starting with March as month 1
of the year. We now substitute this modified numerical date into the formula:
Program 6.11
wday =(day+(13*zmonth-1)/5+5*(zyear%100)/4-7*zyear/400)% 7;
return (wday);
All that we need now is a program to test out our function. One possibility is:
Program 6.12
/* Birthday program */
/* using Zeller's algorithm */
/* to compute the day of the week */
tinclude <stdio.h>
140 Mastering C Programming
main()
This concludes our look at simple functions. Try them out for yourself and
check that they work as expected.
Functions in C use the 'call by value' method to pass information from the
calling environment to the body of the function. This fact has had no obvious
effect on the operation of the functions which we have developed so far, but the
Functions - making them useful 141
next example illustrates what is meant by 'call by value' and how program calls
need to be modified in order for a function to operate as intended. We will
consider the example of a function which is used to find the maximum value of a
set of integers.
One way of accomplishing this task is to set a variable (e.g. max) to -32768
(a large negative integer) or some other similar integer and each time an integer
is read in compare the new value to the current value of max. If the number just
entered is greater than the present maximum value then it becomes the new
maximum. The integer -32768 is a suitable value since on most
implementations of C this will be the largest negative integer. Before looking at
the use of a function to accomplish this task let's write a program which will
find the maximum of a set of 10 numbers entered from the keyboard. The
pseudocode is:
Maximum algorithm
declarations
integer i, num, max
set max to the largest negative integer
set i to 0
while i is less than 10 do
set i to i plus 1
read in num
if the num > max then
set max equal to num
end while
print max
Program 6.13a
I* maximum program *I
Now that we have a working program (type it in and check it to verify that it is
correct) we should be able to modify it to use a function to perform the
calculation. At first sight the following function might appear to be a suitable
solution:
if (n > cmax)
cmax = n;
maximum(num, max);
Finally, by adding our new function at the end of the main program we have a
program to test it.
Exercise
Modify the original program as indicated above (remember to add the maximum ()
function). Compile and run the program using the same test data as before.
Discussion
You should have found that no matter what value was entered the result always
came to -32768. So max does not in fact alter, even though the program and
function appear to be written correctly. In order to investigate how the program
and function are working modify the function by adding the print f ( )
statements shown below.
Functions- making them useful 143
-37
cmax on entry = -32768 : cmax on exit -37
This test shows that the value of cmax on entry to the function is always
-32768 and that although the i f statement in the function enables this value to
be replaced by the new number, as intended, the value of max outside the
function does not change. The reason for this is that on entry to the function the
value of max is assigned to the local variable cmax, which is then updated;
however max is never assigned the new value of cmax, and thus remains at the
value of -32768, i.e. its initial value. The problem, then, lies in the fact that the
variable max is never in fact updated. How can we solve this problem? One
solution involves the use of return which we looked at earlier. All we need to
do is return the value of cmax, as we know that this value will be updated. The
variable max can then be assigned to the function maximum () in the usual
way. The function thus becomes:
Program 6.14
if (n > cmax)
cmax = n;
return (cmax);
The statement in the program can be changed to accept this returned value. So
the program now becomes:
Program 6.13b
I* maximum program *I
I* using a function returning the *I
I* current maximum value *I
A second solution requires a modification to the function and the function call to
allow the parameter cmax to be changed within the function and this new value
to be used to replace the current value of the variable max. This method involves
the use of pointers and will be covered in Chapter 8.
All the functions we have written so far have been placed at the end of the main
program. This means that the functions have all been part of the same source
code as the program which is using them and they have consequently been
compiled at the same time as the main program. This means in turn that if the
program uses any header files, or symbolic constants (defined using the
:lf:def ine preprocessor instruction), then these will all be accessible to all of
the functions which follow in the program source code. For many purposes this
state of affairs presents no difficulties. However suppose that a function which
Functions - making them useful 145
we have been using is required in more than one program. This, after all, is one
of the main reasons for writing functions in the first place, so that they can be
tested independently and then used in a number of programs as and when
necessary. When functions are extracted from a program for use elsewhere we
need to take care that the appropriate header files and other preprocessor
instructions are also included. For this reason it is considered good practice to
include all the necessary information in a separate header immediately before the
relevant function. This will ensure that the necessary information is maintained
with the function and that the function compiles and operates correctly.
As an example consider the case of reading characters from the keyboard and
changing all upper-case letters to their lower-case equivalents - all other
characters are to be left intact. We will add two further tasks to this function;
firstly the changed character should be displayed on the screen, and secondly the
function should return a count of the number of characters changed. The function
given below will perform the necessary operations:
Program 6.15
return(count);
This example uses the two library functions get char () and put char () and
thus the standard input/output header file s tdio. h must be included in the
program. (By means of the #include <stdio. h> preprocessor instruction.) In
addition we have used a symbolic constant (OFFSET) to add a constant value to
the upper-case letters in order to change them to lower-case letters. Again this
constant must also be available to the function and so must be defined within the
program. So, in order to make sure that the correct header files etc. are used, we
can add the following lines just before the function definition:
/* function to_lower() *I
/* This function converts upper-case letters to *I
/* lower-case letters and returns the number of *I
146 Mastering C Programming
As with program titles I have included a brief description of the function to make
it clear what it does. Notice the way in which OFFSET is declared; we do not
need to know the precise ASCII codes for 'a' and 'A'; we can let the computer
work out their separation for us. This does, however, make one assumption. Can
you see what it is?
If these lines are included with the function to_lower () itself then all the
necessary information is available for use by the function. The header file is not
explicitly included here as it may already be loaded, in which case loading it
again would be inefficient and could cause confusion. However the #define
instruction can remain as this is likely to be local to the function. We can even
move this instruction to within the body of the function. These means that it
will be local to the function and not available to any other functions. However
in this instance that does not matter.
As you will by now be aware all variables and functions require to have a data
type specified, even if by default (as we saw earlier, functions are by default of
type int). In addition to this attribute, variables and functions also have a
storage class attributed to them. The storage class governs the availability of the
variable (or function) to other functions, other parts of the program, or other
programs. In more usual language the storage class affects the scope of variables
and functions.
The storage_class keyword is used in the same way for a function, except that
the data_type keyword may be absent- if the function is of type int. We now
discuss each of these storage classes in tum.
auto
Unless variables are modified specifically by use of a storage class type then they
Functions- making them useful 147
are local to the block (function or program) in which they are declared - they
have no effect outside the block. Thus no identifier defined within a function can
be accessed from outside that function. This may appear to be a disadvantage but
it does in fact have advantages. For one thing it aids the modularity of functions
and programs. Each function, including its variables, is self-contained and will
not affect other functions or variables unless explicitly instructed to do so. Very
often the letters i, j or k are used as int variables in the control of program
flow, in loops for example, and these variables may well be used in the main
program as well in various functions called by the main program. The basic
scope (or storage class) rules mean that a variable can be used both in the main
program and in a function without one use affecting the other.
Program 6.16
main()
(
int n, i, sum(int num); /* i local to main() */
int sum(int k)
The auto storage class is the default class for variables declared within a
function and is only rarely used explicitly. The scope of such variables is local
to the block in which they are declared and so the same variable name can be
used in two different blocks without either value affecting the other. One case
when this storage class could be used explicitly might be to indicate that a
variable is intended to be an auto variable and that it is not simply an oversight
148 Mastering C Programming
extern
One use of the storage class extern is to allow the transmission of information
from an outer to an inner block (for example, to allow a function access to a
variable, or a symbolic constant declared in the main program). All variables
declared outside a function and the main program (i.e. before main ())are by
default of storage class extern and have storage permanently allocated to them.
Although this facility allows communication between the calling program and
its functions care should be taken if this method is used to alter an external
variable. Good programming practice dictates that programs should be modular -
that is, they should consist of self-contained units of code with well-defined
means of transmitting data between the modules (in C the modules are
functions!). We have seen one method of achieving this modularity which is by
means of the ret urn statement; the other method uses the argument list.
The extern storage class is also used to inform the compiler that a variable
or function is external to the present program. An example of when such a
declaration might be used is when a set of functions are located in a file separate
from the main program and some of these functions use variables which are
declared in the main program. The variables would be declared outside the
function main () and within the separate functions (in the other file) would be
declared with storage class extern. All functions have storage class extern and
as a result are available to all other functions as well as the main () program.
register
The storage class register has the same effect as the auto storage class except
that the compiler is 'advised' to place variables with this storage class in a
particular area of memory - the CPU registers if possible. No guarantee can be
given that the variables will be placed in these particular locations. The most
common use of register variables is to speed up the access to these specific
variables. Thus a variable which is used frequently in a program could be given
the register storage class in an attempt to speed the program up.
static
Program 6.17
main()
/* maximum function */
/* - this returns the current maximum integer */
int maximum( int x)
Within the function maximum () .the variable max is set to -32768 once only -
the first time the function is called. On subsequent calls the value of the variable
is the latest value. Thus repeated calls of this function will produce the current
maximum value which can be accessed via the return statement Note that
max is invisible to the main program; its value can only be accessed by an
expression such as m = maximum (b). One further point concerning this
function is that, as it stands, it is impossible to reinitialise max to -32768
within the program - initialisation occurs once only, on the first call of the
function. Thus this is not a good idea if you need to find maxima twice within
one program. So care has to be taken with functions of this type as they are
often the cause of hard to find errors.
150 Mastering C Programming
SUMMARY
• saw how the ret urn statement can be used to pass control
back to the calling program or function.
EXERCISES
1. Change the original version of sum () to version 2.0 (Program 6.2b) and use
the following sets of data to test the new version.
(i) 3 8 15 92 0
(ii) -4 9 12 R
You should notice that this time characters other than 0 can be used to terminate
the entry of integers.
2. Write a function to read characters from the keyboard and return the number of
characters read in. Use an asterisk to terminate the string of characters (don't
include this in the count).
3. Use the sum () function as a basis for writing a function mean () which
computes the arithmetic mean of a set of integers. This time 0 must be a valid
entry and not a terminator. Use the value of scanf () to test for the end of the
input stream. Test your function thoroughly.
a b c X
1 2 -2 0 1.5 4
2 -3 1 -1 0 6
6. The maximum () function assumes that the data type int has a range of
-32768 to +32767. The sizeof () function can be used to check the number of
bytes occupied by int variables and can thus be used to calculate automatically
the constant MAXNINT (i.e. the largest negative integer of type int). Work out
how to do this and then modify the maximum () function and program
accordingly. Thoroughly test the function and program.
count_lower(int count)
152 Mastering C Programming
int i;
char c;
9. Write a function which will compute the sum of the squares of a set of real
numbers entered from the keyboard. Write a suitable program and test the
function.
10. Suppose we wish to modify the sum () function (Program 6.2c) to allow it
to keep a running total. What step(s) would need to be taken?
0 The calculator
7.1 INTRODUCTION
At this point we are going to take a short break from our detailed study of C and
develop the program for a simple calculator. This should serve to consolidate
your understanding of the topics covered so far. In this chapter we will be
devising an algorithm and developing the functions required for this task. In the
process we will be looking briefly at parsing and recursion. This chapter will
provide you with a number of useful functions which can be used in a variety of
programs. Furthermore the development of a solution from the initial problem
definition through the various stages of stepwise refinement, algorithm design,
coding and testing should be helpful in working through problems of your own.
There are no exercises as such in this chapter, but you should work through all
the functions and test them out on your own system. There are alternative ways
of implementing some of the functions and new ones can be written which may
improve the modularity and enhance the operation of the fmal program. So, as
you work through this chapter, you should keep an eye open for any
enhancements or modifications which can be made.
In the days of cheap credit card sized calculators it may seem somewhat pointless
to write a program in C to simulate just the basic functions of such a machine.
However it is a useful exercise for a number of reasons, some of which have
already been alluded to. One additional reason is that anyone reading this book
and/or seriously wishing to program in C will be familiar with the operations of
a simple arithmetic calculator. So the task of writing the algorithm which
involves examining the logic of the way a calculator works can be concentrated
upon - there is no need to explain the nature of the problem before we start the
problem-solving process. Also the control structures needed in order to emulate a
calculator have already been discussed. Therefore this is an appropriate point at
which to use some of the constructs in the solution of a more substantial
154 Mastering C Programming
2.4 5.3 +
This method is used by Kernighan & Ritchie as an example in their book (The
C programming language, 2nd edn., 1988, pp. 74 ff) and the reader is referred to
their example after working through this chapter.
Again we would hope to obtain the answer 7.7! This method has the advantage
that it is rather more natural than the reverse Polish notation and thus easier to
understand. However this it at the expense of an added complication in that
parentheses are required for some operations. To illustrate this aspect, consider
the calculation:
2.4 * 3 + 5.1
Unless parentheses are used this calculation could be ambiguous. Does it imply
that we should first multiply 2.4 by 3 and then add the result to 5.1, or are we to
first add 3 to 5.1 and multiply this result by 2.4? These two possible
interpretations in both reverse Polish notation and conventional notation using
brackets are illustrated below.
The operations
Now that we have settled which method of calculation we are going to use we
can move on to the next stage in the design process which involves deciding
upon what operations are to be allowed. We will begin by sticking to the four
basic arithmetic operations, remembering that we must allow for the entry of left
and right parentheses to resolve the ambiguities which we have just been
discussing. One last point on this aspect: we need to decide when the expression
has been terminated so that the calculation can be performed. For the moment we
will simply assume that an expression is terminated once the Return key is
pressed. The use of the = key could be added at a later date as a possible
enhancement
The display should include the expression, as entered, plus on the following
line the result of evaluating that expression. We can incorporate a number of
other items on the screen if we wish, possibly even a picture of a calculator, but
again these frills can be ignored for the moment.
We now have enough information about the basic operation of the calculator to
outline the basic structure of the calculator algorithm. This will be very basic,
but it will serve as a starting point. One possible outline is :
(We may wish to add a facility to output error messages such as an attempt to
divide by zero.)
156 Mastering C Programming
The above skeleton algorithm outlines the tasks involved but we now need to
decide on one or two language dependent aspects before we can proceed further.
For example, how exactly are we going to input the characters? Later on we
could use an array, or a linked list to store the keystrokes - this would mean that
the complete expression could be entered before any attempt at a calculation is
made. However we have only dealt briefly with arrays and have yet to investigate
lists, which means that we will need to process the characters as they are entered,
using a minimal amount of storage. At this stage it is worth thinking about the
types of valid expression which may be input. Some examples include:
(i) 2+3
(ii) 14*2/9
(iii) 2.4*3+5.1
(iv) 12 + 3/2
(v) -4 * 6
(i) 2+3
This is one of simplest of examples; single digit integers are used and the
operation is addition. Extending this example we can see that the process of
reading in a character is such that we cannot easily use the scanf () function-
using it implies that we know in advance the types of the variables being read.
Although it is likely that the first character will be a digit, we cannot guarantee
that- it could be. a left parenthesis.
(ii) 14*2/9
This second example differs only marginally from the first; however it illustrates
that integers consisting of more than one digit may well be entered and that we
must take account of more than one arithmetic operation in the same expression.
(iii) 2.4*3+5.1
These two points are further illustrated by this example which mixes integers
and real numbers as well as two arithmetic operations. So to generalise we need
to treat all numbers as type (at least) float. In fact to deal with as wide a range
of numbers as possible it would be best to treat them as type double. We also
need to allow for multiple arithmetic operations. Provided we keep track of the
current result there will be no need to impose any definite limit on the number
of operations.
The calculator 157
(iv) 12 + 3/2
(v) -4 * 6
These two examples illustrate the fact that spaces may, intentionally or
inadvertently, be inserted in the expression. We should therefore use a function
which will ignore spaces. However we will treat as an error a number which
contains spaces. Another point is illustrated by example (v) and that is that a
number may be preceded by a minus sign, so we should allow for this
eventuality as well.
At this point we need to take another look at the way in which various
arithmetic operators are used in an expression. Let's return to an earlier example
(2.4*3+5.1). In normal arithmetic this would reduce to:
7.2 + 5.1
In this case we are able to calculate the expression exactly as it is written down,
i.e. from left to right. We simply retain the current value of the first
sub-expression (2.4*3) and then add it to the final number (5.1). But what if the
expression were to be entered in the form 5.1+2.4*3? If we adopt the process
just described then we will get an erroneous result (i.e. as if it had been entered
with brackets, (5.1+2.4)*3). This leads us to conclude that if the first operation
is addition then the first two operands must be retained as well as the '+'
operator until the next operator is supplied. If this is an '*' then we need to get
the third number. We can then process this last triplet (e.g. 2.4 * 3) and place
the result in num ber2. The process is now repeated. If at a subsequent stage a '+'
is encountered then the first triplet can be processed (numberl '+'and number2).
We can now generalise this a little by noting that the rules governing the
evaluation of an expression remain the same when a '-' is replaced by a '+' or an
'*' is replaced by a '/'. What this discussion reveals is that multiplication and
division should be carried out before addition and subtraction - the basic
precedence rules of arithmetic. It also shows that, at most, three numbers and
two operators need to be retained at any one time. Let us now write down an
algorithm which represents the processing which we have just been discussing
(Figure 7.1).
Take a few minutes to look over this algorithm. It looks rather daunting, but
the ideas involved are not all that perplexing. The algorithm uses two functions
which have yet to be detailed: getopO and getnumQ. These functions return the
next operator and a number respectively. When we come to code the processO
158 Mastering C Programming
function we will see that getopO simply needs to ignore spaces and return the
next character. The getnumO function is a modification of a function which we
wrote earlier (Program 6.10) and can therefore be coded without much effort.
This uses xl, x2 and x3 for the numbers being read in,
and opl and op2 for the two operators.
That is the syntax of an expression at any one time
is at most of the form:xl opl x2 op2 x3.
set xl to getnum()
set opl to getop()
while opl not equal to return do
if opl is equal to +or - then
set x2 to getnum()
set op2 to getop()
while op2 not equal to return do
if op2 is equal to + or - then
set xl to evaluate(xl, opl, x2)
set opl to op2
set x2 to getnum()
else if op2 is equal to • or I then
set x3 to getnum()
set x2 to evaluate(x2, op2, x3)
else
print Invalid operator and stop
end if
set op2 to getop()
end while
set xl to evaluate(xl, opl, x2)
return xl
else if opl is equal to • or I then
set x2 to getnum()
set xl to evaluate(xl, opl, x2)
else
print Invalid operator and stop
end if
set opl to getop()
end while
return xl
A final point to consider before we go any further is how to deal with a bracketed
expression, for example 2*(4+3*(7-5)/(10-6)). Once parentheses are introduced
we need to think carefully about the way the expression is evaluated. One way of
setting out the evaluation of this expression is given in Table 7.1, using the
variables x1, x2, x3, op1 and op2, as used in Figure 7.1.
The calculator 159
xl opl x2
2 * ( suspend evaluation
xl opl x2 op2 x3
4 + 3 * ( suspend evaluation
xl opl x2 op2
7 5 )
2 result
4 + 3 * 2
4 + 6 I ( suspend evaluation
xl opl x2 op2
10 6 )
4 result
4 + 6 I 4
4 + 1.5 )
5.5 result
2 * 5.5
11 final result
A careful study of Table 7.1 will show the processes that are involved when a
left bracket is encountered.
The only position at which a left bracket can validly occur is when a number is
expected: e.g. 3*(7-5) is valid, but not 3(7-5). Bearing this in mind, we can carry
out the checking for a left bracket in the function which reads a number (i.e. in
getnum() ).
Now that brackets have been introduced we will need to modify the process
algorithm to take account of the new possibilities. Since the test for the left
brackets is made in getnum () we only need to consider a test for closing
brackets. This can be achieved by just adding another test to the two while
loops, so these become:
respectively. As soon as a right bracket is found the while loop is exited and
the process () function returns the value in xl.
Program 7.1
double process()
{
char opl, op2, getnextchar();
double xl, x2, x3, getnum(),
evaluate(double x, char op, double y);
xl = getnum () ;
while((opl = getnextchar()) != '\n' && opl != ') ') {
switch (opl) {
case '+':
case '-':
x2 = getnum () ;
while((op2 = getnextchar()) != '\n' && op2 != 'l'l I
switch (op2) {
The calculator 161
case '+' :
case '-' :
xl = evaluate(xl, opl, x2);
opl = op2;
x2 = getnum () ;
break;
case '*' :
case 'I' :
x3 = getnum();
x2 = evaluate(x2, op2, x3);
break;
default:
printf("\n * Invalid operator *\n");
printf(" op = %c \n", op2);
exit(l);
return (xl);
getnum ()
We now need to consider the other processes in the basic algorithm (Figure 7.1).
Let's take a look at the getnum () function first. This function reads characters
one at a time and converts the resulting stream of digits to a decimal number.
(Actually the conversion takes place as the digits are read.) The process of
converting a series of character digits into an integer has already been discussed
and a program written (see Program 6.10). Read over that discussion now to
make sure that you understand the principles involved.
We will need to modify the function slightly as we are dealing with real numbers
not just integers. Thus a decimal point is a valid character in a stream of digits.
So how do we cope with the decimal point? One way is to continue the process
of shifting the variable (num) which we are building up to the left, with
successive multiplications by 10 and at the same time keep track of the number
of digits which have been read in since the decimal point. Before reading on, see
if you can work out why this additional counter is needed.
Consider a number such as 237.58. Using the above expression we will have
read in, up to the decimal point, the digits '2' '3' '7', which will have been
converted by the above formula to the decimal number 237. Once the decimal
point is encountered we begin incrementing a variable, say nexp, each time a
new digit is read. Thus when the end of the number is reached we have a number
23758, and the counter nexp is set to 2. All that is required now is to divide
our integer (23758) by 10 twice (i.e. the value of the counter nexp) to shift the
number right by two decimal places. Now that we have worked through this
process we can write down the algorithm in pseudocode.
The algorithm above forms the basis for the getnum () function. However we
still need to make one or two refinements for the function we require. These are
to check if the number is negative, and to check for the left bracket. The first of
these is simple. If a minus sign is read as the first character then we set a flag
and read another character (which we will assume is a valid digit). The converted
number is then negated just prior to returning it from the function. Checking for
a left bracket is also quite simple. Instead of returning a number directly from the
The calculator 163
return(process());
Program 7.2
char c, getnextchar(void);
double num = 0, process();
int flag = 0, nexp = 0, minus 0, isadigit(char c);
c = getnextchar();
if ( c == ' ( ')
return(process());
else if( isadigit(c) II c == '.' II c == '-')
; /* valid numeric input - skip */
else
printf("\n **Numeric digit . or (expected** \n");
printf(" Character is %c \n", c);
exit(1);
if ( c == ,_,)
minus = 1; /* minus */
c = getnextchar();
There is one addition to this function which we haven't yet discussed. That is the
purpose of ungetc (c, stdin). This function is a standard library function
which 'ungets', that is pushes back onto the input stream, a character just read
from the input buffer. Here stdin is a file pointer which is associated with the
standard input device (i.e. the keyboard). (A fuller discussion of file pointers and
stdin is given in Chapter 9.) The reason that we need this function is that,
once a return from getnum () has been made, the character which was just read,
and was not a valid character for getnum (),is needed by process () to get
the next operator. (An alternative is available; can you work out what it is? If
so, try it out.)
Finally we need to write one more function which performs the actual
arithmetic calculations. This function requires three arguments: two operands and
an operator. It returns the result of the calculation. This can be coded by using
another simple switch statement. It is given in Program 7.3, below.
Program 7.3
We have now reached the stage where the program itself can be written, since the
two remaining functions which we require have already been written. These are
the getnextchar () and the isadigit () functions. These are given as
Program 6.8 and Program 6.9 in Chapter 6.
The calculator 165
Program 7.4
#include <stdio.h>
main()
{
double process(), getnum(), evaluate( double x,
char op, double y);
char getnextchar();
That completes our tour through the development of the simple calculator
program. Notice the simplicity of the program itself! All the work is done in the
functions. Try the program out and see how you get on with it.
EXERCISES
There are a number ofimprovements which could be made to the program. For
example there is no check that a number being entered is too large, or that two
or more minus signs are entered during a getnum () call. The program could
also be extended to compute powers. You could even add the trigonometric
functions to it.
® Pointers, arrays and strings
"To me the most important part of a program is laying out the data structure."
Dan Bricklin
8.1 INTRODUCTION
In this chapter we will be concerned with pointers, arrays and strings. If you
have done any programming before beginning to study C, you will have already
encountered and used arrays and strings. If you have arrived here via assembly
language programming, you will already be familiar with the concept of
pointers. If you have come to C by a different route and are uncertain about
pointers then now's your chance to find out! Pointers are one of the most
important features of C and as such contribute to the flexibility and power for
which the language is known. The concept of pointers is crucial to a clear
understanding of the way arrays work, and strings are simply arrays of type
char. It is for this reason that we have not so far concerned ourselves very
much with strings. However, by the time you have worked through this chapter,
you should be proficient in the basic uses of pointers, be able to use one- and
two-dimensional arrays and be at home with strings.
8.2 POINTERS
As we saw in Chapter 3 all variables have an address at which the value of the
variable is located or stored. We have already encountered the idea of an address
when we used the scanf function. This function, you will remember, requires
the address of the variable to be given as a parameter to the function rather than
the name of the variable. The address operator ( &) is used for this purpose. Thus
Pointers, arrays and strings 167
You might expect that, since we have pointer constants which are analogous
to constants, there would also be pointer variables. If that is what you were
thinking then you would be quite correct. A pointer variable can be assigned a
value which will be the address of a variable. Thus
paddress = &first;
assigns to the pointer variable paddress the address of the variable first -
paddress now 'points to' first. This variable can be assigned to the address
of another variable, for example:
paddress = &second;
second = *paddress;
will assign the current value of first to the variable second. A little
thought will show that the pair of statements paddress = &first; and
second = *paddress; put the same value in second as the single
assignment statement second = first; as is illustrated in Figure 8.1. Thus
the (unary) operator * together with the unary operator & allows us to indirectly
achieve the same result as can be obtained by a single assignment statement -
hence the term indirection operator.
168 Mastering C Programming
65782 256.5
&first ftrst
Pointer declarations
Although we have illustrated the use of pointers we have still to show how a
pointer variable is declared. A pointer variable must be assigned a data type just
as do ordinary variables. Pointer variables do not simply point to an address;
they point to the address of a particular variable and the variable has a pre-defined
data type. So, in the example we used above, paddress will point to an int
if the variable first is declared as an integer; it will point to a f 1 oat if
first is of type f 1 oat. A pointer declaration must therefore have an
indication of the data type to which it will be pointing as well as some means of
identifying it as a pointer. The standard data types can be used for the former and
the indirection operator can be used for the latter. Thus the declaration
int *paddress;
Since each pointer is associated with a particular data type it is not permissible
to use a pointer of one type to point to a variable of a different type. Thus,
Pointers, arrays and strings 169
px = & z;
would cause a compilation warning (or error) since an attempt is being made to
use a pointer to float (px) to address a variable of type double.
o Using, in the body of the function, the indirection operator whenever the
parameter is being referenced.
These points are all illustrated in the complete solution to the maximum
program and function which is given below.
Program 8.1
main()
Discussion
If we look at the test program which calls the function we see that there is
only one change from the previous version, which is that the address of max is
passed as an argument to the function instead of the value. This is necessary
since the function is expecting an address (cmax is a pointer).
Exchanging values
One further example of the use of pointers will help to consolidate your
understanding of their operation. We will do this by taking as an illustration the
basis of many sort routines, that of a function which will reorder two values if
the first value is greater than the second. The pseudocode for such a function
might be:
exchange( a, b)
declaration
Pointers, arrays and strings 171
integer temp
if a> b then
set temp to a
set a to b
set b to temp
end if
Program 8.2
int temp;
if( *a> *b ) {
temp = *a; I* the contents of a *I
*a *b; I* the contents of b are placed in
the variable a *I
*b temp; I* the previous contents of a are
placed at the address pointed to
by b *I
Note that the variable temp does not need to be a pointer variable since it is
accessing the contents of an address pointed to by a.
172 Mastering C Programming
ptr = &x;
contents_of_x = *ptr;
o Pointers are declared with the combination of a data type and the *
operator, e.g.
int *ptr;
float *fptr;
(i) by value, e.g. s urn (a, b) , in which case the values of the variables
a and b will not be changed.
(ii) by reference, e.g. exchange (&a, &b), which passes the addresses
of a and b, thus allowing the contents of these locations in memory to
be changed.
8.4 ARRAYS
In programming we often need to process a set of data all the items of which
have the same data type. For example, we might wish to compute some simple
statistics from a set of data on examination results. Each student's result will be
of the same type (say a percentage mark). We could allocate a variable for each of
the student marks. However this becomes very unwieldly once the numbers get
to more than ten. In addition we will need to rewrite our program and/or function
if the number of students changes. A much better solution is to use an array in
which to store these marks. We mentioned briefly in Chapter 6 that an array is
generally implemented as a block of storage in which the same data type is
stored in consecutive locations. In C an array is a derived type which can be
thought of as a variable which is indexed so as to refer to successive elements.
Pointers, arrays and strings 173
An array is declared by using square brackets which may be empty or which may
enclose a constant (symbolic or numeric) defining the size of the array, i.e. the
number of elements in the array. Thus
int marks[25];
declares an integer array called marks which has 25 elements. The first array
element is defined as element 0, and thus the last element is element SIZE-1,
where sIzE is the size of the array. So the last element of the array rna r k s is
marks [24].
will assign the value 78 to the contents of the fourth element of the array
marks (remember to count from marks [ 0] ). Similarly
marks[i] = 0;
will assign 0 to the i + 1 th element of the array. Note that i must be an integer
and be within the range 0 to 24.
Storage class
Closely connected with assignment is initialisation, but whilst any array can
have a value assigned to any of its elements only certain arrays can be initialised.
Arrays possess a storage class either by default or explicitly on declaration.
However arrays can only be of storage class automatic, external or static -
they cannot be register. The storage class of an array can affect the values to
which an array is initialised. Static and external arrays can have values
174 Mastering C Programming
will assign to a [ 0 ] the value 10, a [ 1] the value 5 etc. and also implicitly set
the size of a to 4.
Note that in these examples the size of the arrays is not specified. The size is
determined by the number of items enclosed by the braces. Thus a will have four
elements a [ 0 ] to a [ 3 ] whilst b will contain five elements b [ 0 ] to b [ 4 ] .
What happens if the size is specified and the number of items does not match?
The answer is 'It depends'! Let's look at each possibility in turn. First we will
consider the case when the number of values to be assigned is greater than the
size of the array, e.g. a declaration of the form:
in which the array size is set explicitly to 3, but four values are enclosed in the
initialising braces. This is the simplest case and the end result is that a
compilation error will occur.
The second case occurs when the number of initialising values is less than the
specified array size. The first so many elements will be set as specified. The
remaining elements will be initialised to zero in both static and external arrays.
Note, however, that automatic arrays cannot be initialised and the elements of
such arrays will contain 'garbage' until values are assigned to them.
In the examples above we used an integer constant (i.e. 3) and an integer variable
(i.e. i) to access the individual array elements. However, more generally, we can
use b [integer_ expression] to access an element of the array b. An array
element is accessed so long as the integer_expression evaluates to an
integer greater than or equal to zero and less than the size of the array. An
Pointers, arrays and strings 175
attempt to access an array element outside these bounds may cause a run-time
error to occur, the exact nature of which is system-dependent InC there may be
no automatic checking to see if an array index is outside the bounds of the array.
It is therefore well worth checking all array indices, especially when they consist
of a complex integer expression, to ensure that the index is within the array
bounds.
int array_name[SIZE];
lower bound = 0
upper_bound = SIZE - 1
SIZE = upper_bound + 1
The assignment
aptr = &a[O];
will make the pointer variable aptr point to the initial element of a (that is to
176 Mastering C Programming
a [ 0])- apt r thus contains the address of a [ 0]. Since the array name a
stands for the address of the array, which is also the address of the initial element
of a, then the assignment
aptr = a;
P = a;
p++;
num = *p;
is equivalent to:
num=a[l];
before after
p =a; p++;
address contents p address contents
a~+
*p
&a[O]
Pointers, arrays and strings 177
Example
Consider an array a of type int of size 5 and let a have values of 28, -63, 5,
192, 10. The following program illustrates both pointer and array indexing of
arrays.
Program 8.3
#include <stdio.h>
main()
The output I obtained from this program is given below. Run the program for
yourself and compare your results with those shown here. The addresses of the
pointers will be different. Notice that I have used the %u conversion string to
print the addresses. This is because an address cannot be negative and it may be
larger than a normal int value allows.
Normal indexing
element 0 contents 28
element 1 contents -63
element 2 contents 5
element 3 contents 192
element 4 contents 10
Pointer indexing
178 Mastering C Programming
The important point concerning these results is that once p is assigned the
address of the array we can access successive elements by using pointer
arithmetic. By adding 1 to the pointer we can point to the next element in the
array. In this example we simply incremented p to step through the array.
However we can also access a specific element. So, for example, following the
assignment of p to a [ 0 ] we might access the contents of a [ 3] with the
expression* (p+3).
So far we have restricted our attention to int arrays. However, as you would
expect, arrays of the other data types can also be used. They are declared in an
obvious way. Some examples are:
j 0;
total = 0;
while ( j < 1000 ) {
total += x [ j l;
j++;
The above points are fairly straightforward, but how do pointers work with
arrays of data types other than in t? Provided that the correct pointer is used
with its corresponding data type, indexing an array using a pointer will still
Pointers, arrays and strings 179
access the correct elements. Incrementing a pointer adds one to the pointer itself
but the result is that the pointer now points to the next item of that particular
data type. In order to prove that this is the case we can modify the above
program to use a float array and a float pointer. With these modifications,
and appropriate changes to the printf statements, I obtained the following
output:
Normal indexing
element 0 contents 28.000000
element 1 contents -63.000000
element 2 contents 5.000000
element 3 contents 192.000000
element 4 contents 10.000000
Pointer indexing
Again on your system the results may differ slightly, but the important point to
note is the difference between the addresses of successive array elements. In this
float example the difference in address between successive elements is 4 bytes
(the size of a float variable on my system) whereas in the earlier int
example the difference was 2 bytes (the size of an int). Thus pointers know the
size of each data type, pointer arithmetic works in units of sizeof (type).
The programming style which we have adopted in this book is one of modularity
and one consequence of this is that arrays will frequently be required for use with
functions. We will begin our investigation of the use of arrays with functions by
summarising the relationship between array addresses, array elements, array
contents and pointers. Table 8.1 illustrates these relationships.
180 Mastering C Programming
Table 8.1 makes it clear that an array address is also a pointer and so if we
wish to pass to a function the address of an array we can just use the array name
as an argument in the function call. What form must the function definition take
if a parameter is an array name? One method is to declare the parameter as an
array of the appropriate type but with no size specified. Thus a function used to
compute the arithmetic mean of data stored in an array might have the following
structure:
mean(b, num);
/* where b is an int array declared as int b[SIZE] */
As well as the address of an array we could also need to pass the address of a
particular element of an array into a function. This is achieved by a call of the
form:
function_name(&b[3]);
which passes the address of the fourth element of array b into the function. The
function declaration of the function parameter would again have to be in one of
the two forms just introduced, i.e. int a [],or int *a. Notice that an array
element can also be passed as if it was a simple variable (i.e. as
function_name (b [3]), with a declaration within the function parameter
list of int a), but in this case the array element could not be modified from
within the function.
mean =ll:.
n
xi
where l:. represents summation over the set of values (1 ..... n). The standard
deviation is given by
Each of these can be written as a function which can be called, in the order given
above, by the main program. Apart from the above calls what else does the main
program consist ofl Obviously we will need to declare an array to hold the data
which is to entered from the keyboard. This can be done in two ways, but in
182 Mastering C Programming
each case we need to specify the size of the array as well as determine its type.
We will use double for the type as this should allow us to use the program for a
wide range of data. The simplest way to declare the array is by including the size
explicitly in the declaration of the array, e.g.
double data[lOO];
An alternative is to use a symbolic constant for the array size, in which case we
would have
Now what about the functions? We need to decide for each function what
information needs to be passed to the function and what information is to be
passed from the function to the program, and how this is to be achieved. We will
look at each in tum.
1. input_data ()
This function needs the address of the array data to be passed to it so that the
entries can be stored in the array. We can use the fact that a function can return a
value back to the program to return the number of elements read. We can use -1
to flag if too many were entered. The function header will thus be of the form
Notice that, although the data we are analysing is to be stored in an array of type
double, this does not alter the fact that the function itself (input_data ())
is of type int. We wish to return the number of values entered and so intis
the correct choice for this function.
2. mean ()
Once again we will need to pass the address of the array to the function, but in
addition we require the number of values read in, which is obtained from the
input_data function. The value we wish to return is the mean of the data,
which must be of type double, so if we use ret urn to pass back the result
Pointers, arrays and strings 183
then the function will also have to be of type double. The skeleton of this
function therefore looks like
return(average);
3. maximum ()
You are already familiar with this function, all that is required is a modification
to enable the function to accept an array as an argument rather than a single
variable. Based on an earlier version of the maximum () function we might have
the following code:
int i;
In this case we do not need to return a value so the function can be declared as
type void. The address of the current value of the maxium is passed via the
argument *max. This value must be initialised to the largest negative number
before it is used anywhere else in the program.
4. minimum ()
This function is very similar to the previous function and is left as an exercise
for the reader to write.
I* function st _deviation () *I
I* this computes, and returns, the standard deviation *I
I* of a set (n) of data of type double. *I
I* ensure that the header file *I
I* <math.h> is included in the program *I
int i;
double sum = 0, st_dev;
Since this function requires the square root function (sqrt () ) the maths header
file must be included in the program header (i.e. #include <math. h>).
6. output_result()
This is a simple function which need not return a value and so will be of type
void. It requires as parameters the values of the mean, maximum, minimum and
standard deviation. It would also be useful to pass into the function the
number of data values upon which the statistics are based. As an exercise try
writing this function for yourself before reading on.
Program 8.4
I* Simple Statistics *I
I* This program uses functions to compute the following
arithmetic mean
minimum
maximum
standard deviation
of up to 100 real (floating point) numbers.
More numbers can be analysed by change the value of SIZE
*I
#include <stdio.h>
#include <math.h>
#define SIZE 100
Pointers, arrays and strings 185
main()
{
void maximum(double a[], int n, double *max),
minimum(double a[], int n, double *min),
output result(double av, double max, double min,
double std, int n);
double mean(double a[], int n),
st_deviation(double a[], double mean, int n);
int input_data(double a[]);
int i = -1;
double x;
I* maximum function *I
int i;
I* minimum function *I
void minimum(double a[], int n, double *min)
int i;
Exercise
Enter the complete program and test it. Use simple data to begin with so that
you can hand test the results. To check the (limited) error trapping change SIZE
to 10 and enter more than ten numbers.
8.7 STRINGS
char name[20];
declares and initialises string constants. (The second of which takes over as the
longest word in the English language (Oxford English Dictionary, 2nd edn.)!)
These must therefore be static arrays which are either declared outside the
progmm definition (i.e. before main ()) or preceded by the keyword static.
We have seen already that there is a close relationship between pointers and
arrays and so it will come as no surprise that a string can be defined using
pointers, i.e. by means of the de-referencing operator*· Thus the prompta
string used above could also be written as
In the array version the name prompt a stands for the address of the frrst
element of the char array (i.e. the address of the first character 'P') and cannot
be altered. Just as you cannot change the address of a memory location so too
you cannot alter the address of an array name. However in the pointer version
promptp is a variable which points to the frrst character in the string "Press
any key to continue \n" and since it is a variable an extra location in
memory has to be set aside to store the contents of promptp- i.e. the address
to which the pointer prompt p is pointing. So that is the frrst difference. The
second difference you may have already spotted and this concerns the fact that
promptp is a variable whilst prompt a is a constant. This means that the
value of promptp can be modified: it can be made to 'point to' another string.
Thus, if we had a second string,
promptp = eprompt;
Once this latter assignment has been made we have lost access to the original
string "Press any key to continue \n" which was pointed to by
promptp. However, provided that the array prompta [] is declared in our
program, we can still obtain access to that striug via the assignment statement
Pointers, arrays and strings 189
promptp = prompta;
One simple type of code involves the substitution of one letter for another. If for
example the letter A is replaced by P once, in the coded message it will always
be replaced by P. In order to generate the code the alphabet is randomised and
placed under the normal alphabet, e.g.
ABCDEFGHUKL~OPQRS~Z
BSKYETOVPRQDAZLCJ~G~
The task of encoding a message then becomes simply that of replacing each
letter in the message from line one with its substitute in line two.
We will illustrate the use of strings by coding functions to generate the code
(i.e. line two), code the message, get the message and print it.
Program 8.5
int i, rnum;
char c;
This function should be easy for you to understand, given the description above.
Look over it to make sure that you understand how it works.
Program 8.6
/* get_message() function
This functions reads a message from the keyboard
and stores it in a char array
*I
void get_message(char *mess)
The function to code the message is also quite straightforward. Assume that the
array which holds the randomised alphabet is called code [ J• Then element 0 of
this array will contain the new letter for the letter A, element 1 the new letter for
B and so on. If the coded message is to be placed in the array c _mess [ J this
can be achieved with a statement of the form:
We will use the second option. There are a couple of other points which we need
to look at before we write the function. Firstly what are we to do about upper-
and lower-case letters? We could make the cases correspond in both the original
and the coded message, but that would be to give valuable information to any
unauthorised person reading the coded message! So we will code all letters as
upper-case. This means the function will need to convert lower-case letters to
upper-case ones. (This can be achieved with a standard function (toupper () ),
but we will write our own code to illustrate the method.)
The next point involves characters other than letters. The simplest option is
to ignore them and we will adopt this approach. However it might be useful to
code the full stop - this could be done by replacing it with the word STOP,
suitably encoded of course. We leave this as an exercise for the reader.
Program 8.7
/* code_message() function
This takes as parameters the addresses of the arrays:
mess - the original message
code - the code being used
c_mess - the final coded message
Punctuation is ignored (including spaces)
lower-case are converted to upper-case
*I
void code_message( char *mess, char *code, char *c_mess)
mess++;
*c mess = NULL;
The final function we require is to print out the message. This uses the library
function putchar () to print one character at a time to the screen. Since there
will be no spaces in the coded message we can add a space every five characters.
192 Mastering C Programming
Program 8.8
int i = 0;
put char ( 1 \n 1 ) ;
while( *mess !=NULL )
putchar(*mess);
mess++;
i++;
i f ( i == 5 ) { /* add a space every 5 characters */
putchar ( 1 1 ) ;
i = 0;
Program 8.9
#include <stdio.h>
main()
{
char a[27], mess[BO], coded_mess[BO];
checking purposes *I
print_message(a); I* normal alphabet *I
set_code(a); I* generate code *I
print_message(a); I* randomised alphabet *I
get_message(mess);
code_message(mess, a, coded_mess);
printf ("\n The coded message is : \n");
print_message(coded_mess);
These functions which are contained in the header file string. h, fall into two
broad categories, those which carry out tests on a suing and return an int, and
those which perform string manipulations (e.g. copy from one string to
another). This latter group do not check that the destination string is large
enough to hold the incoming string and thus array overflow may occur. It is
therefore up to the programmer to make sure that possible overflows are trapped.
We will see in one of the examples this process being implemented.
Test functions
strlen (s)
int slen;
static char long_word[] =
"pneumonoultramicroscopicsilicovolcanoconiosis";
slen strlen(long_word);
194 Mastering C Programming
strcmp(sl, s2)
strncmp(sl, s2, n)
strcat(sl, s2)
s 1 and s 2 are pointers to char. The string s 2, together with its null
terminator is appended to the end of string s 1, the frrst character of s 2 replacing
the null character in s 1. The value of s 1 is returned.
strncat(sl, s2, n)
This function is the same as the previous one but at most n characters are
appended
strcpy(sl, s2)
strncpy(sl, s2, n)
This function behaves as for strcpy but at most n characters are copied. s 1 is
padded out with '\0' s if s2 has less than n characters.
Example
The program given below illustrates a use of the strcpy () function and also
illustrates an important point concerning strings. We noted earlier that many C
compilers do not keep a check on array bounds and so the programmer needs to
Pointers, arrays and strings 195
do this for him or herself. This is particularly important where strings are
concerned as it is very easy to write over the end of an array using strings
without realising it We will ftrst list the program and then present some typical
output. We will then go on to discuss some points of interest involving strings.
Program 8.10
main()
{
static char s1[SIZE], s2[SIZE];
static char s3 [] = "this program shows why ";
static char s4[] ="you need to watch strings";
/* neither of these arrays is long enough to hold their
strings */
Discussion
First of all note that the value of SIZE is too small for the strings involved.
Ideally it should be at least 26 to allow for the NULL character at the end of s4.
Notice also the addresses of the strings. The addresses given in brackets are the
starting location for the respective strings. These addresses are a key to
196 Mastering C Programming
understanding the output and the problem. In particular note the addresses of s 1
and s2. They are separated by 15 bytes. This means that if s1 contains a string
which is longer than 15 bytes it will overlap into the beginning of s 2. Now
look at the contents of s1 and s2. The idea was to copy 'This program
shows why' into s1 and 'you need to watch strings' into s2.
The output reveals that s 1 contains part of the required string. However, because
the string is longer than 15 bytes it overlaps into s2 so that when the string is
copied to s 2 it overwrites the end of s 1 . If you try this program out for
yourself then beware! You may get runtime errors occurring. The moral is: make
sure that the size of char arrays is big enough to hold the required strings. Also
use strlen () to check the length of strings and prevent problems such as the
above.
An array of strings is just an array, the elements of which are themselves arrays
of characters. As such they can be considered as two-dimensional (rectangular)
arrays or as ragged arrays. What is a ragged array? Consider the following
character array declaration.
cij =ailblj
where b is now treated as a row vector ( bJj = ( bjJ )T).
Since a and b are !-dimensional arrays they can be declared in the function
parameter list as a [ ] , b [ ] . However c is a 2-dimensional array and we must
therefore specify the number of columns (i.e. the second dimension of the array).
The reason for this is due to the way in which C allocates memory to a
198 Mastering C Programming
a[O] [0], a[O] [1], a[O] [2], a[1] [0], ••.••• a[3] [2]
Program 8.11
Notice that, although the function modifies the contents of the array c, we have
not used the dereferencing operator(*). The reason for this is that c represents
the address of the first element of the 2-dimensional array, which is precisely
what we want to pass to the function.
We saw in sections 8.4 and 8.5 how pointers were related to one-dimensional
arrays. How are they related to two-dimensional arrays? Consider the declaration:
The variable a is the name of an array and as such it is also a pointer. This
declaration can be thought of as an array of arrays of integers, that is three arrays
each consisting of an array of 5 integers. The first element of the
two-dimensional array is a [ 0 ] [ 0 ] , which is also the first element of the
sub-array a [ 0 ] . So we would expect that the address pointed to by a [ 0 ] [ 0 l
Pointers, arrays and strings 199
Program 8.12
I* pointer.c
A program to find out how pointers and 2-D arrays work
*I
main()
(
int a[3] [5];
printf ("\n
printf ("%s .
Pointers and two-dimensional arrays \n");
a \t \t = %u , TEXT, a); I* A *I
printf ("%s ..
a[O] \t = %u , TEXT, a [OJ); I* B *I
printf ("%s ..
a[O] [OJ \t = %u , TEXT, &a[O] [0]); I* c *I
There are a few points worth making about the program before we look at the
output from it. Firstly, since we are using the same piece of text a number of
times we have used a tdefine statement to make life easier! Secondly we have
used tabs so that the output will be lined up. Notice also the difference in the
print f ( ) statements between line I * c * I and the other two. In order to
get the address of a [ 0 ] [ 0 ] we need to use the expression & a [ 0 ] [ 0 ] , the
reason being that a [ 0 ] [ 0 ] refers to the contents at that address, whereas a and
a [ 0 ] are pointers. The output I obtained from this program was:
How do we use pointers with two dimensional arrays? One way forward is
suggested by the discussion in the last paragraph. We can set up a pointer which
200 Mastering C Programming
So much for the addresses. How do we obtain the value of an array element
of a two-dimensional array using pointers? In the case of a simple pointer
variable pf loa t the value of the float is *pf loa t . In the present case it
should be obvious that *p will not work, as this is just another pointer (to a
one- dimensional array). However we do in fact require the contents of the int
pointed to by this second pointer. So we might try * * p. This is indeed the
solution. So if p = a then
Then after p++, * *p will equal a [ 1] [ 0] . Now we have access to the first
element of the subarray a [ i] but it might also be useful if we could gain access
to the other array elements as well. To achieve this using pointers we need to
declare another pointer, this time of type int, to point to an individual integer
in array a. So a declaration of the form
int *ip;
followed by the assignment
ip = p [11;
will enable ip to point to the int value in a [ 1] [ 0]. Then all we need to do
to access the contents of a [ 1] [ 1] is to increment the in t pointer ip (i.e.
++ip or ip++). Finally to obtain the actual value of the int stored in this
element of the array we simply use * ip.
Pointers, arrays and strings 201
This chapter has provided us with many of the necessary data structures to begin
programming the Bridge Tutor program in earnest. We can use an array to
simulate a deck of cards. For the moment we need not worry about the exact
correspondence between array elements and a particular card. All we need to know
is that such a correspondence is possible.
Shuffling
The first process which we require is one to shuffle the cards. This reduces to the
problem of randomising array elements. There are a number of ways of
simulating this task, but haven't we come across a similar problem already? The
code program (Program 8.5) involved something almost identical. There we
effectively shuffled a pack consisting of 26 cards with each one corresponding to
a letter of the alphabet. So all we need do is modify the program to cater for 52
cards instead of 26 letters! Turn back to program 8.5 and see if you can work out
the necessary changes. (We can simply fill the array with integers from 1 to 52
to represent the 52 cards.)
Dealing
The function to deal the cards simply involves distributing the 52 randomly
distributed integers (cards) between four hands. In bridge the four hands (or
players) are given the names North, East, South and West. We could distribute
the cards between these four hands by giving the frrst 13 cards to South, the next
thirteen to West and so on. However we may as well try and be as realistic as
possible. This means that we deal cards in turn to South, West, North and East
until all 52 cards are dealt. Assuming that we have an array called deck [52]
and that each players cards are stored in arrays south [ 13 ] , west [ 13 ] ,
north [ 13 ] and east [ 13 ] then we can simulate a deal with a function of the
form shown in Program 8.13.
Program 8.13
int i, j;
Sorting
Another function which is required is one to sort the cards within each hand,
since we want to be able to display them in a sensible order, within each suit in
ascending or descending order. We will assume that we have an array of n
integers and we wish to sort these into order. There are many sort routines
available but as we are not dealing with large arrays we will use a bubble sort,
which is one of the simplest sort routines. A bubble sort works by comparing
adjacent items and exchanging them if they are out of order. Thus items 'bubble'
through the array. By using nested loops we can guarantee that all the items will
be sorted when we exit the final loop. A possible algorithm is given below.
Program 8.14
This function uses a modified version of the exchange function which we wrote
earlier. The new version is:
Program 8.15
I* function exchange()
This function swaps two ints if the
first parameter is less than the second.
*sorted is set to FALSE if a swap took place
int temp;
Note the way in which the array elements are passed to the function
exchange ().Since we may need to alter the contents of these elements we
must pass the address of each element to the function. (Think of a [ i J as a
simple variable.) These functions are a beginning to the bridge tutor program;
we will be extending them later on.
SUMMARY
EXERCISES
2. Write a program to read in up to twenty lines of text and display the longest
line.
3. Write a program to read in up to a hundred words and to keep track of the first
and last word in alphabetical order, as well as the longest word. The program
should display all three words.
4. Write a function to compute the area of a triangle, given the three sides. The
function should return the area which should be of type float. (Use the
Pointers, arrays and strings 205
5. Modify the function above to return -1.0 if the sides do not fonn a triangle.
8. Enter the program pointer. c (Program 8.12) and check your output with
that given. Modify the program by giving values to the array a and by adding a
pointer declaration of the fonn ( *p) [] ; . Add pointer indexing and print
statements to find the addresses and contents of pointers as discussed in section
8.11. To check your results you will also need to print out the address of each
arrray element.
9. Enter the bridge functions and the test program and test it.
10. Write a program which will generate 1000 random numbers and then carry
out some simple statistics on them. Use the program developed earlier
(statistical calculator) as a basis for computing the statistics.
" ...How can/ know what I think till I see what I say?" Graham Wallas
9.1 INTRODUCTION
Table 9.1 gives a comprehensive list of the functions available for input and
output to the standard i/o devices - normally the keyboard for input and the
screen for output. The functions fall into two main categories, those allowing
for formatted input and output and those permitting unformatted input and
output.
Character i/o
getchar ()
get a character none the character read c= get char () ;
from standard
input (default keyboard)
putchar (c)
write a character character (c) for character written, putchar ('a');
to standard output EOFonerror
output (default screen)
Line i/o
gets (*s)
get a string char * s - pointer s or .NUI.L if EOF ln gets(s);
terminated by to an arrays occurs or error while
a '\n'. The '\n' is getting the string
replaced with a '\0'
puts (*s)
write a string to char * s - pointer EOF if an error puts("bye!");
the standard output to an arrays occurs, else
plus a' \n' non-negative
Formatted i/o
For small programs, and in the examples which we have been using to illustrate
the constructs of C, using print f ( ) has presented us with no problems.
However because of the very flexibility of the function it takes up a significant
amount of memory and because of its complexity takes longer to execute than
other, simpler functions. Often the use of simpler alternatives can provide more
precise control and improve the speed. In many instances we have used
208 Mastering C Programming
The flexibility of printf () has already been mentioned and we have seen that
the argument list in the function call can be of arbitrary length. We have used
control strings of varying degrees of complexity employing most of the
conversioncharacterslistedinTable3.5(i.e.%c, %d, %e, %f, %g, %s).
In this section we are going to look at the remaining conversion characters and
then see how we can modify these conversion specifications to improve the
layout of text.
The three remaining conversion characters (which should be found in all versions
of C) are o, x and u (which we have already used from time to time). These
characters are used to print integers in octal and hexadecimal and unsigned
integers respectively. Thus Program 9.1 will produce the output:
25 as an octal integer is 31
25 as a hexadecimal integer is f9
25 as an unsigned integer is 25
Program 9.1
main()
{
int num;
num = 25;
printf("\n %d as an octal integer is %o ",num, num);
printf("\n %d as a hexadecimal integer is %x ",num, num);
printf("\n %d as an unsigned integer is %u ",num, num);
Input and output - more thoughts 209
%d, n 23,
%6d, n 23,
%-12d, n 23
%06d, n 000023,
%012d, -n -00000000023,
%4Ld, k 4567821,
%o, n 27'
%8o, n 27,
%08o, n 00000027,
%-8x, n f7
In Table 9.2 a comma is used to mark the right edge of the field width (see
below). The allowable characters between the % sign and the conversion
character are summarised in Table 9.3. Note that they must appear in the order
given, although not all (or any) need be present.
ANSI C explanation
• a flag
left justification
+ number always printed with a sign
space prefix with a space unless frrst character is a sign
0 in numeric conversion leading zeros used to pad out
# alternate output form:
o - frrst digit always zero
x or X - Ox or OX will prefix a non-zero result
e, E, f, g, G - output will always contain a decimal point;
for g & G trailing zeros not removed
• a length modifier
Note: The width or the precision, or both, can be replaced by*, in which case the*
is replaced by the next argument in the list (which must be an int).
Obviously care must be taken to ensure that the argument list matches the
conversion specification otherwise unpredictable results may occur.
A careful study of this table will reveal how these conversion strings operate.
Notice the way in which a number is rounded up: for example, the default
display prints pi as 3 . 1415 9 3, and with the %4 . 2 conversion we obtain
3. 14. Notice also the use of- to left justify a number in its field.
Input and output - more thoughts 211
f = 'f';
pi = 3.141592654;
c = 2. 99793e8; /* speed, of light (ms- 1 ) */
meO = 9.1083e-31; /* rest mass of the electron (kg) */
%e, c, 2. 997930e+08,
%16.le, c, 3.0e+08,
%-16.8e, meO, 9.1083000e-31
%f, meO, 0.000000,
The final set of format conversions involves strings and some examples are
given in Table 9.5.
We have already used some format specifications with scanf () and in this
section we are going to summarise the various possibilities. This function has
the form
212 Mastering C Programming
As with the printf () function which we have just been considering, this
function requires a control_string which should match the arguments in its
argument list. The control_string can consist of ordinary white spaces,
conversion specifications and ordinary non-white characters (not %), in which
case the next character in the input stream must match these characters.
ANSI C explanation
the number of conversions made and assigned. Notice also that, using scanf (),
a string may not contain any embedded spaces on input.
The input and output functions mentioned in the previous section are special
cases of more general i/o functions which can be used with any file. (A file can
be a normal file as stored on a floppy or hard disk, a peripheral device (such as a
printer), the keyboard or the screen.) The keyboard and screen are normally
identified by the variables stdin and stdout respectively (meaning standard
input and standard output). Thus, providing that these variables are not redirected,
to refer to another file, the statement
Similarly
will perform the operation of reading in from the keyboard two integers separated
by a comma, as will
Opening files
The first parameter in the file versions of these functions is a file pointer which
identifies the required input or output device. Two things are necessary before file
operations can be performed. Firstly a file pointer must be defined using the
special type name FILE. This is a derived type name which is defined in
stdio. hand is a structure which, once a file is opened, contains information
about the file pointed to. (We will be looking in detail at structures in the next
chapter.) Thus the declaration
FILE *fp;
declares a pointer of type FILE which may subsequently be used with file i/o
functions.
The second requirement is that the file must be open for access. This is
achieved by the statement
214 Mastering C Programming
fp = fopen(file_name, mode);
where file_name is the name of the file to which access is required, and
mode is a character string indicating the method of access. Possible values for
the mode parameter are given in Table 9.7.
a "b" may be appended to any of the above to indicate that data transfer
is in binary mode.
Binary mode is used with MS-DOS to provide another form of text file and
should not be confused with the more common meaning (e.g. an executable
binary file).
What happens if the ftle cannot be opened? For example, the file 'addresses'
might not exist. With your familiarity of functions in C you will not be
surprised to find that fopen () returns a value (integer) which is used to
indicate whether or not the function call was successful. If the attempt to open a
file is unsuccessful, then fopen () returns the value NULL (in fact 0) which is
Input and output- more thoughts 215
defined in stdio . h. With this additional knowledge we can rewrite the above
examples to ensure that a file has been opened successfully before any attempt is
made to access it Thus the first example can also be written as:
FILE *fp;
This version uses the library function exit () which terminates the program
when it is called. Conventionally a 0 value for the argument indicates that
normal termination occurred, whereas a non-zero value (typically 1) is used to
indicate that abnormal program termination occurred.
Closing files
Normally when a C program terminates all open files are automatically closed.
However it may be the case that a file is only used for a short time in an isolated
part of the program and can be closed once it is no longer required. This means
that the file will be available for other users, or even the same program later on.
The function which is used to achieve this task is called, as you might guess,
fclose () and it has the form
fclose(file_pointer);
fp = fopen(file_name, mode);
stderr
There is one further file pointer which we need to mention; this is the file
pointer s t de r r. This is used for writing error messages. The default file is the
screen but output can be directed to another file for later perusal. The file
pointers stdin and stdout, although normally identified with the keyboard
and screen, can be reassigned to other files (under unix and MS-OOS this can be
achieved by redirection and piping: see the operating system manual for your
computer). Thus the message printed using the puts () function call above
may be redirected to a disk file rather than to the screen. In order to ensure that
this does not occur we can replace the put s ( ) call with an f put s ( ) call
using the stderr file pointer. So
216 Mastering C Programming
Table 9.8 below gives a list of the file i/o functions, their equivalent forms
for stdio, and a brief explanation of their use.
ungetc (ch, fp) ungetc (ch, stdin) push back the last character
read from the file. This can be
re-read by using ch = getc
(fp);
fputs (s, fp) puts (s) fputs (s, fp) copies the
string s to the file pointed
to by fp
In this final section we are going to illustrate the use of some of the functions
which we have just introduced by working on a couple of examples. We will be
concerned with the formatted print options (fprintf () and fscanf ())and
Input and output - more thoughts 217
Let us begin by assuming that the user will initially be prompted to enter one
of the characters 1 w 1 , 1 r 1 , or 1 a 1 , representing write, read and append,
respectively. Any other entry is invalid. Once the option has been selected the
next process is to open the file in the appropriate mode. At this stage there are
many options regarding error trapping. For instance the file might not exist
when the read option has been selected, or it may already exist when the write
option has been selected, and so on. A limited amount of trapping can be
achieved by noting the value of the file _pointer which is used to access the file.
If this identifier returns a NULL when an attempt is made to open it in read
mode, then the file does not exist. So a first step, even before opening for
writing, might be to attempt to open the file for reading and note whether the
file_pointer returned is a NULL. If it is not NULL then the file already exists
and read or append can be carried out. If on the other hand the write option had
been selected then a prompt to check if the file is to be overwritten would be
sensible. The basic structure of the program has the form
declarations
initialise variables
check file status
display menu
get choice
use file_status to give futher prompt if necessary
exit if error, or don't want to overwrite existing file
process choice
read file and display, or
get entries from keyboard and write, or
get entries from keyboard and append
The last two entries in the pseudocode above are very similar. In fact, provided
that the file is opened in the correct mode, the same process to display and write
can be used for both options.
We now need to think a little about the data structure required for this
218 Mastering C Programming
Program 9.2
#include <stdio.h>
#define TRUE 1
#define FALSE 0
main()
{
FILE *fp /* declare a file pointer */
static char name[20], phone[15];
int i, file exist;
char c, choice;
I* enter choice */
printf(" y or n : ");
if (choice== 'n')
exit(O); /*terminate program*/
else
c = •w•; /* create and open for writing *I
We have used the fclose () function here before exiting from the file although
it is not strictly necessary, as on normal exit from a C program all open files are
220 Mastering C Programming
closed. However it is good practice to close files as soon as they are no longer
required in a program as, for one thing, they are then available for other
programs to use.
Program 9.3a
/* get_str() function
This reads characters from the keyboard
into a string. The string is complete
once a newline character is read.
The last character read is returned.
*I
char c;
Next we need to find a means of writing this string to a file. This can be done
just by using the fprintf () function we used previously. This is possible
since strings which include spaces can be written.
The function to read characters from the file is just the above function
(Program 9.3a) with get char () replaced by getc ().For example:
c = getc(fp)
However remembering that we can read from the keyboard by using stdin we
Input and output- more thoughts 221
can utilise the same function for both reading from the keyboard and reading
from a file. We will need to pass the relevant file pointer via the function
parameter list. Thus the modified function becomes:
Program 9.3b
char c;
This function then replaces the scanf () calls with a statement of the form:
The changes which need making to Program 9.2 are listed below.
Add:
int get_str(FILE *file, char *string);
Add the get_s t r ( ) function (Program 9 .3b) to the end of the main program.
There are a number of ways of choosing the file we are reading from or writing
to. So far we have simply used a string constant to specify the file. One obvious
alternative is to use a string variable the value of which can be entered by the
user. Thus with a declaration of the form
char *fname;
scanf("%s", fname);
we can determine the name of the file we wish to open at run time.
phonedb phonlst.txt
i f ( argc ! = 1 ) {
printf("\n **Incorrect number of arguments** \n");
exit(O);
One final point worth mentioning before we leave file input and output
concerns the standard input/output files stdin and stdout. We saw earlier
that the keyboard is usually designated as stdin and the screen as stdout. On
occasions it is useful to reassign one or other of these to some other device. The
most common one is to change the standard output device temporarily to the
printer so that all printf () , put char () functions etc. output their data
streams to the printer. This can be achieved by using the function freopen ().
This function has the form
where filename is the new filename, mode is as shown in Table 9.7, and
filepointer is normally one of stdin, stdout or stderr. Thus to
change the standard output file from the monitor to the printer we could use the
statement
and later on in the program the destination device could be changed back to the
screen with
These device designators apply to the MS-OOS operating system. They may be
different if you are working in a different environment
224 Mastering C Programming
We have been looking at the line editor from time to time in this book. You
should now have collected a number of functions and program fragments which
can be used in the program. We haven't, until now, discussed in any detail the
way in which the data are to be stored. However we have now covered enough
material to allow us to look at this problem. In addition we are in a position to
program the file input and output.
Text storage
There are a number of ways of storing the text in memory during editing. We
could use arrays, lists or a combination of the two. We mention lists in Chapter
11 and you may like to try adapting this program using linear linked lists once
you have worked through that material. However at this particular juncture the
simplest option is to use a two-dimensional array of type char. We use a
two-dimensional array so that one dimension addresses a line of text and the
other a character within that line. We now need to think about the size of the
array. Assuming we have a standard screen display, then 25 lines can be
displayed at once. So we will limit the number of lines to 25. We suggested in
Chapter 1.7 that a line length of up to 75 characters would be sufficient. {To
take into account the display of line numbers on the screen, for example.) So we
can declare an array for this task as:
Let's now consider the function to read the text from a file (for the moment
we will assume a fixed name for the file i.e. data. txt). Before we can read the file
we need to open it and there is the possibility that it may not exist, so we need
to check for this eventuality. The pseudocode can be written down straight away.
We now need to expand on the read text from file part of this algorithm. The data
will be read into the array text[line][column] until the End of File (EOF) is
reached. Whenever a newline character is read we must increment line and reset
column to zero. In addition it might be worthwhile checking that column does
not exceed 75, e.g. a newline may be missing from the file. If this is the case we
Input and output- more thoughts 225
will also want to increment line and set column to zero. Finally we should keep
a check on the value of line to see that it does not exceed 24. (Remember that
arrays are indexed from 0, so the line range is 0 to 24 and the column range is
from 0 to 75, including a newline character.) With this information we can
expand on our original algorithm.
The function can now be written from this pseudocode. However, before we do
that, notice one or two points about the algorithm. We have used symbolic
constants MAXLINES and MAXCOLS, rather than 25 and 75, as this makes
future alterations to the function easier. We have also declared the array text
and the integer next as external to the function. Since both of these variables
will be used in most functions it is sensible to do this. However to make the
program and functions clear we must explicitly note this in our function
definitions by use of extern. Finally next holds the current line number of
the next line available for appending (in the range 0 to 25) - this will normally
be set to zero on entry to this function. The function to achieve the reading of
text from a file is given in Program 9.4 below. Read through the code and see
that you understand how it works. The exercises at the end of the chapter enable
you to write other functions and a simple program to try them and this one out.
226 Mastering C Programming
Program 9.4
/* function read_file()
**
** this function reads text from file data.txt
** next is set equal to the number of lines read + 1
** unless the line limit is exceeded in which case
** next is set equal to MAXLINES
*I
void read_file(void)
FILE *fp;
char c;
int col = 0;
else
text[next] [col++] c; /*normal text not EOL */
/* or '\n, */
if( col != 0 && c == EOF ) {
text [next] [col] = NL;
next++; /* update next if EOF and no '\n' */
fclose (fp);
SUMMARY
In this chapter we :
EXERCISES
l.Write a function called prompt ( s) which writes the string s to the standard
output file but does not append a newline. Once you have written it, write a
suitable test program, then compile and run it. This will be much simpler than
printf () and will not have the disadvantage (which in this case puts () has)
of appending a newline.
2. Enter the telephone database program and test it with some real data. Modify
it so that other files than phonelst.txt can be used. Try the various methods
discussed above.
3. Write a function to write the text from array test [] [ J to the file data.txt.
5. Modify your program and functions in 4 to use a variable file name for the
data- using command line arguments (argc and argv).
6. Modify the telephone database program to allow for the editing of existing
entries and for the deletion of entries.
Typedef, structures and
unions
"In short, the notion of structure is comprised of three key ideas: the idea of
wholeness, the idea of transformation and the idea of self-regulation."
Jean Piaget
10.1 INTRODUCTION
In this chapter we will be examining ways of extending the data types which we
have covered in earlier chapters. The three keywords struct, union and
typedef enable a great variety of data structures to be created. The ability to
create structures to represent complex arrangements of data is a very important
part of program design. Often half the battle in solving a problem is getting the
data representation correct. Before you work through this chapter you should
make sure that you are familiar with the material covered in the earlier chapters.
Many of the ideas we will be discussing require a good understanding of the
earlier material. In particular you should be happy with arrays of various types
and pointers.
10.2 TYPEDEF
The keyword t ypede f provides a means of creating new names for data types.
It can be useful for improving the readability of a program by providing better
self-documentation. Thus we can create new data type names for frequently
occurring types. However its use need not be confined to simple data types such
as int, float or char. We can use a typedef declaration to associate a
name with a more complex combination of data types such as might be
represented by structures. We will look at this aspect in Section 10.4. Finally
typedef is often used to guard against problems arising through porting: that
is when a program written for one system is used in another environment. In
such cases the exact representation of the basic data types may differ. If this is
the case and if the size of a particular data type is crucial to the correct operation
of the program, then t ypede f can be employed to associate a new name with
this data type. When the program is moved to another system only the
typedef declaration will need to be altered.
Typedef, structures and unions 229
Each of these declarations can be thought of as adding another data type name to
the existing list. Using typedef does not replace the existing data type by
the newly declared one. It simply adds another name to the list of available
types.
Having deftned String as a data type pointer to char we can then declare
variables directly using the new name String (rather than using the common
form char *).For example:
The new type defmition can therefore be used anywhere a declaration is needed -
230 Mastering C Programming
provided of course that the use of the new name follows the type de f
declaration itself.
Notice that in the above definitions we have used an upper-case letter for the
initial character of the new data type names. This is not obligatory but if it is
adhered to it does help in making it clear which names are variables, which
symbolic constants and which are names created by typedef.
Program 10.1
int i, j;
Finally we can generalise the function even more by replacing the vectors by
square matrices (ml and m2). However we need to modify the structure of the
Typedef, structures and unions 231
inner for loop by adding yet another loop since now a matrix element of the
product is the sum of the products of elements in ml and m2. Thus we obtain:
Program 10.2
int i, j, k;
One other useful matrix function is the determinant. For a 2 x 2 matrix (a) the
determinant (a scalar) is defmed by
We can use this definition to write a determinant function (float det (a))
which returns the determinat of a 3 x 3 matrix.
Program 10.3
*I
float det(Matrix a)
10.3 STRUCTURES
So far we have been dealing with data of specific data types. Arrays were
introduced to allow data of the same type to be accessed by means of a single
variable (i.e. the array name). What happens if we want to set up data such that
each item consists of a number of different types of data? A common example
where this might occur is in the creation of a record in a database consisting of
various fields which hold the name, address and telephone number of various
individuals or establishments. One solution to this problem is to set up a series
of arrays of type char, one for each item (or field). So, for example, we might
have:
This statement declares three arrays, each consisting of 100 elements, each of
which is a one-dimensional array of type char. In order to access any of the
details of a particular entry we simply use array indexing, or pointers. Thus the
program fragment
will print out the name, address and telephone number on successive lines.
However this, as it stands, is not very satisfactory as the address may well be up
to 100 characters long, and that will certainly make a mess of our display! So a
better solution might be to split the address array up into four further arrays (e.g.
address1, address2, town, postcode). We might also want to break the name into
its constituent parts (e.g. initials and surname). If we continue on in this manner
it won't be long before we forget what arrays hold what and programming
becomes a slog rather than a joy! What we need is some means of simulating the
field structure found in databases where a single record is associated with a
particular collection of fields. Structures enable just such a collection of
variables to be grouped together and identified by a single variable.
Typedef, structures and unions 233
struct personal_data {
char surname[20];
char initials(4];
char address1(20];
char address2[20];
char town(20];
char post_code(S];
char phone(12];
};
Note the semi-colon following the right brace. This is one of the few places
when it is legal, and essential, to follow a brace with a semi-colon.
This new structure definition can now be used to declare a variable, e.g.
which will declare a variable nl of type struct personal data and will
set aside storage for all the elements included in the structure template. In this
particular example it will be useful to declare an array for this variable so that we
can store more than one of our friends' details.
Having defined a structure the next problem is how to enter information into it.
One method is similar to the initialisation of arrays, with the same proviso that
only external or static variables can be initialised. Thus, in the following
program fragment, the variable best_ friend must be preceded by the key
word static to allow it to be initialised.
Program 10.4
main()
{ I* structure initialisation *I
static struct personal_data best friend =
"Campbell",
"N.D.",
"Shangrila"
"2 North Road",
"Treebridge",
"TD9 12XT"},
"0567 2237"
};
nl.surname = "Jones";
This last example illustrates an important point concerning the use of the
member operator with arrays. The name address_book [ 0] is the structure
variable name and is in fact a pointer to the first element of the array
address book and it is the member within this variable which we wish to
access. Suppose that we wish to search through our address book and locate all
Typedef, structures and unions 235
our friends whose telephone number begins with "0 41 "; then we could use an
expression of the form:
Program 10.5
This has a form very similar to our earlier structure definitions but in addition a
number of variable names are added between the closing brace and the final
semi-colon. This is useful in that it combines two processes into a single
process. Provided that the structure definition is outside rna in ( ) , the variables
discl, disc2 and disc3 will be automatically external variables and can
therefore be initialised within the program. Note that this can be condensed still
236 Mastering C Programming
struct {
char composer[20];
char initials[S];
char composition[40];
char *other;
char conductor[20];
char orchestra[30]
float price;
discl, disc2, disc3;
Although this form is quite neat, it has the big disadvantage that, if this
structure is to be used to declare other variables, for example in another function,
then we have no means of doing this other than by typing out the complete
template yet again. Consequently this form should only be used when the
variables are required to be external , or are only needed in one function.
One final point is that a structure variable can be initialised, declared and the
structure template set up all in one go. So, for example, if we wish to set up a
structure like the above and initialise discl we might have:
struct class_disc {
char composer[20];
char initials[S];
char composition[40];
char *other;
char conductor[20];
char orchestra[30]
float price;
discl= {
"Shostakovitch",
non,
"Symph. #7 inC maj. -The Leningrad",
"Kabalevsky - Cello Concerto #2",
"Svetlanov",
"USSR Symphony",
10.95
};
The structures we have looked at so far have used a tagname, preceding the
structure template definition, to enable variables to be declared as structures.
Unless the variables are declared and the structure template is set up at the same
time, we need to use both the keyword struct and the tagname when declaring
new variables. The typedef keyword can be used to shorten this process, as is
Typedef, structures and unions 237
typedef struct {
float real; /* real part */
float imag; /* imaginary part */
}Complex;
This definition sets up a structure called Complex and defines it as a new data
type (made up of two members rea 1 and ima g which represent f 1 oat
variables). We can now use Complex to declare variables with this structure
elsewhere in the program. So
Complex x, y, z;
will declare three variables each consisting of a rea 1 and an imag part.
We can now use this definition in a program which will add together two
complex numbers.
Program 10.6
#include <stdio.h>
typedef struct {
float real; /* real part */
float imag; /* imaginary part */
Complex;
main()
{
Complex x, y, z;
It is a simple matter to write a function to carry out the complex addition. There
are three main approaches to this problem: pass the individual elements, pass a
238 Mastering C Programming
Program 10.7
We will look at a third way of using structures with functions in section 10.6.
For the moment we turn to the topic of structures containing structures.
A structure can not only contain the standard data types as members but it can
also contain other structures. Let's take another look at our address book
example, which we explored in Section 10.3. This structure template
personal data was made up of seven members, the first two of which relate
to an individual's name. With the ability to use structures nested within
structures we can go back to our original attempt which used just three members
(name, address and phone). So we begin by setting up the main
Typedef, structures and unions 239
Next we can set up the Name template containing the surname and initials of an
individual.
Notice that we have used typedef here to simplify the naming of variables,
and we have also capitalised the initial letter of the various structure definitions
to make it clear that these are derived types and not simple variables.
The new version which we have arrived at emphasises the form of the data
structure more clearly than the earlier version, which did not use nested
structures. (You could think of this type of data structure as being generated by a
form of stepwise refinement. First of all the main data categories are decided
upon (e.g. Name, Address, phone) and then they are refined to greater detail (e.g.
surname, initials)). One important point to notice about this particular template
is that the same name is used for both a typedef (Address) and for a
structure member (address). This is permissible inC since this language is
case sensitive, so that although on the surface the two are the same, because the
initial letter of the struct name is capitalised they are treated as different
identifiers.
240 Mastering C Programming
Now that we have this fancy structure how do we access its members?
Assume we want to initialise the first two entries in our address book. We can
use the following defmition
Address_entry address_book[2]
{
{"Jones", "A.J"},
{"45 Stoneybank","","Treebridge","TD9 5XT"},
"0567 2451"
{"Smith", "P.S"}
{"Hollybush","Devon Road","Treebridge","TD95XR"},
"0567 3476"
There are a few important points to note about the form of this definition.
• There is no need to use the keyword struct since Address_entry includes the
struct keyword in its definition through the use of typedef.
Now that we have something to work on, the next step is to access individual
members of our new structure. We can get AJ's telephone number in the usual
manner:
and to verify that this is indeed the correct entry we can use
int i;
Typedef, structures and unions 241
Name individual;
i = 0;
individual= address_book[i].person;
printf("\n The entry for %dis for %5s %-12s \n", i,
individual.initials, individual.surname);
Before leaving the topic of structures we must take a look at the way pointers
can be used with them. We shall explore very briefly pointers to structures and
accessing members using pointers. There are a number of reasons for raising this
topic. Firstly, just as it is easier in many applications to manipulate pointers to
arrays rather than the arrays themselves so are pointers to structures easier to
manipulate than the structures themselves. Secondly, in the old standard and
therefore in some of the earlier implementations of C it was not permissible to
pass structures to functions; however a pointer to a structure could be passed.
Program 10.8
Notice the use of brackets around the dereferencing operator ( *) and structure
variables x, y and sum. This is necessary because the member operator (.)has
higher precedence than the dereferencing operator and thus without the brackets
the expression *sum. real, for example, would be interpreted by the compiler
as* (sum. real).
In order illustrate the use of pointers with structures we can return to the
previous address book example. We declare a pointer of type Address_entry,
i.e.
Address_entry *aptr;
242 Mastering C Programming
The pointer at present points nowhere, so we need to assign a value to it. Let's
make it point to the first entry in our address book.
aptr = &address_book[O];
Program 10.9
#include <stdio.h>
typedef struct {
char surname[20];
char initials[4];
Name;
typedef struct
char addressl[20];
char address2[20];
char town[20];
char post code(8];
-
Address
typedef struct
Name person;
Address address;
char phone[l2];
Address_entry;
Address_entry address_book[]
{"Smith", "P.S"}
{"Hollybush","Devon Road","Treebridge","TD95XR"},
"0567 3476"
};
Typedef, structures and unions 243
main()
int i;
Address_entry *aptr; /* pointer to structure */
aptr = &address_book[O];
printf("The address of address - book[O] is %u\n",
&address book[O]);
-
printf ("The address of address _book[l] is %u\n",
&address book[l]);
-
printf("The address of aptr is : %u \n", aptr);
printf("The address of ++aptr is : %u \n", ++aptr);
aptr &address_book[O]; /* reinitialize aptr */
for ( i = 0; i < 2; i++) {
printf("%s's phone no. is : %s\n",
( (*aptr) .person) .initials, (*aptr) .phone);
++aptr; /* move to next entry */
Voila! it works as we expected. The last few lines of the program show how we
can access a member using a pointer. We saw above one method which involved
using the dereferencing operator and so in this example, to access the telephone
number of A.J. Jones we can use the statements
aptr = &address_book[O];
phone no= (*aptr) .phone;
complete expression is a variable with a data type the same as that of the
member of the structure pointed to. The above two examples can now be
rewritten as
respectively. We will be using forms like these in the next chapter when we
examine lists and list processing.
Notice in the output to Program 10.8 that the sum of all the bytes used by
the Address_entry structure is 108, and 404 plus 108 is 512, i.e. the
address of the second entry in the address_book array. Two points are worth
stressing, one rather obvious, the second not so obvious. The first point is that
the addresses will obviously be machine-dependent and so if you run this
program it will be extremely unlikely for you to obtain the same addresses. The
second point to note concerns the size of the structures. In this particular
example the size of the structure is the sum of the size of its members. (The size
of Narne is 28, the size of Address is 68 and the size of phone is 12.)
However this need not be the case, owing to the way in which different objects
are stored. The correct way to obtain the size of a structure is to use the
sizeof () operator. Thus
This ends our look at structures; we will be using them again in the next chapter
when we investigate their use with lists.
10.7 UNIONS
Unions closely resemble structures in that they are a derived type. Their syntax
is the same and the method of accessing a member of a union is the same. The
difference is that the union members share storage and consequently they can be
used when storage is at a premium. Another use for unions is when mutually
exclusive options of different data types are used for a single variable. Storage
allocation is such that enough memory is set aside to take the largest of the
union members (there can be more than two). For example a month can be
expressed either as an integer or as a string. The program below sets up such a
union and performs simple input and output using it.
Program 10.10
union month
int number;
char string[lO];
}; /* definition same form as that for a struct */
main()
union month m;
m.number = 6;
printf("\n The month number is : %d \n", m.number);
m.string = "August";
puts(" \n With a new assignment to m.string ");
printf("\n The month string is : %s \n", m.string);
Suppose we have another structure which holds the date in the form
where the two forms 21 January 1989 and 21 /1 I 89 are equally permissible. We
can add an extra member to the structure to indicate in which form the month
was last stored. So we could have a structure definition like this:
typedef struct {
int day,
union month mm,
int yr,
char short_long /* S for short form ( 21 /1 I 89 ),
}Date; L for long ( 21 January 1989 ) */
246 Mastering C Programming
This definition and the one for month might be used in a program to allow either
format to be input.
Program 10.11
#include <stdio.h>
main()
date.short_long = date_flag;
else {
printf(" The date was : %d %d %d\n", date.day,
date.mm.number, date.yr);
SUMMARY
EXERCISES
2. Using the typedef statements and the matrix functions already written
write the following functions:
structure struct {
char c;
float fnum,
int aint[lO];
5. Construct a structure template to hold the time of day using hours, minutes,
seconds and "am" or "pm" for a twelve-hour clock.
6. Write a program, using structures, to output the day number within the year
of a date entered from the keyboard. Allow dates to be entered only in the form
25-Jan-1978. Restrict this program to non-leap years only.
By interpreting this definition modify the program above to work for leap years
as well.
8. Implement the address book example using nested structures and check that
you understand fully how it works.
10. Modify the Complex function c_add2 () (Program 10.8) so that it uses
structure pointer operators. Test the function with your existing program.
struct cycle {
int rear; /* number of teeth on the rear sprocket *I
int chain; I* number of teeth on the chain wheel *I
float diam; /* diameter of the wheels (inches) *I
float dist; /* distance travelled for one revolution*/
}; I* of' the pedals *I
Write and implement a function which takes a pointer to the cycle structure
and computes the value of dist from the structure members rear, chain and
diam and puts the result (in metres) in the dist member (cf. Chapter 3,
exercise 9).
typedef struct {
char surname[20];
char first_name[20];
Name
struct life
char place[20];
int born;
int died;
};
250 Mastering C Programming
struct sage {
Name philosopher;
struct life biog;
char interests[SO];
};
sptr = &phil;
a. Write a function which takes the address of a sage as an argument and prints
out the contents of the structure in the form:
Ludwig Wittgenstein was born in 1889 in Vienna and died in 1951. His main
philosophical interests included logic, epistemology and language.
printf("%d", pl.biog,birth};
printf("%s", (*sptr) .interests);
printf("%s", sptr -> biog.place);
printf("%s", sptr -> philosopher.surname + 4};
13. Design a structure for a stock control program. The structure should contain
a code number, the name of the stock the quantity and the units (as separate
entries), the cost per item and the stock level at which to reorder. Use nested
structures as necessary.
@_L_is_t_s_a_n_d_l_is_t_p_r_o_c_e_s_s_in_g_ __
11.1 INTRODUCTION
The idea of a list follows on from structures which we dealt with in the last
chapter. A list is made up of elements which contain data and a pointer to the
next element in the list. A list element is often represented diagrammatically as
in Figure 11.1.
The contents of a list element can consist of any data types dealt with so far,
including structures and unions and even lists. However in this chapter we will
be confining our discussion to an element consisting of a single character and a
pointer, so that we can concentrate on the central aspects of lists and list
processing and not get bogged down in the details of the data held in the list
element.
252 Mastering C Programming
Since the pointer in a list element is a pointer to the next element it will have
a data type given by the data type of the element Thus each element in a list is a
self-referential structure. The basic list element can therefore be defined as
follows:
Program 11.1
The declarations above are sufficient to describe the general element of a list.
However before we can proceed with using lists we need to consider two
important questions concerning the structure of a list. Firstly where does a list
start, and secondly where does it end? A list begins with a pointer to the first
element in the list (Figure 11.2).
c=J----·~~~--~--~--~~~
Head
This first pointer is often referred to as the head (the end of the list being the
tail!) and is simply the address of the ftrst element in the list. A single variable
of type L _member can be declared in the usual way, for example:
Lists and list processing 253
L member element;
L member group[20];
What about the end of a list? How do we know when and where the list ends?
Conventionally a NULL pointer is used as the last pointer in a list. It is usual to
#define NULL at the start of the program, or in a list header file. Thus
#define NULL 0
will set up the desired symbolic constant so that assigning a list element to
NULL can be achieved in the usual way. For example,
In a structure of this form all that is available, and in fact need be available, to
the programmer is a pointer to the frrst element in the list (i.e. to the head of the
list). Having this, together with a few functions, enables us to set up a system
for generating and processing linear linked lists. A linear linked list is a list in
which each element points to, at most, one other element - if it points to no
element (i.e. to NULL) then this is the end of the list.
will allocate sufficient storage for a single element of the list, as defined above,
and will assign p to the first byte of allocated memory space. Notice that we
have had to cast malloc () to a pointer to L_member. This is because the
function normally returns a pointer to char and using the appropriate cast
prevents the type mismatch.
where temp and pl are both simple variables whilst next () is a function
which returns a pointer of type L_member.
Since a string will be terminated by the ' \ 0 ' character we can use this to check
for the end of the string and return NULL. All other cases continue the process of
allocation, assignment and moving to the next character. Processes of this type
lend themselves to solutions involving recursion and the following pseudocode
Lists and list processing 255
function string-to-list(string)
if end of string then
return NULL
else
allocate storage
copy current character to the memory just allocated
set pointer-to-next to string-to-list(string+ 1)
return pointer-to-next
end if
Before looking at the code for this function let us consider the basic operation
using the string 'to' as a sample. This will help to explain how recursion works
in this particular case and provide a further insight into the whole concept of
recursion in general. (We met recursion in Chapter 7 when we developed the
simple calculator.) Let us assume that the function is called in the following
way
phead = string_to_list("to");
where phead is a pointer assigned to the head of the list which, if the process is
successful, will contain 1 t 1 1 o 1 1 \ 0 1 • The process can be set out as shown
in Figure 11.4.
string_to_list ( "o")
string_to_list("")
Program 11.2
Link p;
if( *s '\0')
return (NULL); I* end of string reached *I
else I* allocate storage and assign *I
p = assign(s); I* character to new member *I
p -> pn = generate(s + 1);
I* recursive call assigning next *I
I* character to next member in *I
return (p); I* the list *I
Link p;
return((Link) malloc(sizeof(L_member)));
Discussion
The first function (generate ())follows the algorithm quite closely; however
it uses another function (assign ())to allocate storage and assign the current
character to the newly created member. This function calls another function
(allocate ())which simply allocates storage. The code is written in this way
because both of the latter operations are frequently needed in list processing and
therefore it is sensible to turn them into functions.
Notice in assign () that we have included an error trap to check that there
is room to store the new element. Although in this particular problem such an
error is very unlikely to occur it is sensible always to check that storage
allocation has been successful before continuing. One other point to note is that,
after assigning the first character of the string to the Contents of the new
element, the pointer to the next element is set to NULL. This ensures that a
well-formed list is generated. This is again a sensible precaution since, if the
pointer is not set outside the function, its contents will be unknown and
therefore data will be written to memory which has not been made available.
Program 11.3
void list_print(Link p)
if ( p == NULL)
printf("NULL: %c (~6u) \n", p -> c, p);
else (
printf(" %c (%6u) --> ", p -> c, p);
list_print( next(p));
258 Mastering C Programming
Link next(Link p)
return( p -> pn );
Now that we have written the functions, all that we require is a program to
use them and some suitable data. The program below (Program 11.4) is one such
example.
Program 11.4
tinclude <stdio.h>
tdefine NULL 0
/* insert functions
generate(): Program 11.2
assign () : Program 11.2
allocate(): Program 11.2
list _print(): Program 11.3
next() Program 11.3
in here.
*I
Exercise
Enter the above program, together with all the functions, and try it out.
The program above is only of limited value. However we can extend it without
too much trouble and in the process develop some other useful list processing
functions. These functions, although written for the simple list example, can be
easily adapted for use with more complex structures. In such cases all that is
required is to replace char in the Contents type definition by another data type
or structure. Then whenever reference is made to the value stored in a member
you must ensure that the correct operations are carried out. Consider a list which
holds a string and an integer. The type definition of Contents would be
typedef struct {
char name [ 10);
int number;
Contents;
The statements to copy the contents of a single element to the list (e.g. in
assign() above, Program 11.2) would be
instead of
p -> c = q -> c;
as at present. You may like to investigate the use of this structure with lists
once you have worked through this chapter.
So far we have functions to generate, and print lists. We also require some
means of entering a list from the keyboard. In our present example this simply
means reading a string of characters and then using generate to convert them into
a list. However if we wish to include spaces in our list a simple scan f ( )
260 Mastering C Programming
Program 11.5
/* function to read characters from stdin and generate a list.
*I
Link get_list(void)
{
char in[BO];
Exercise
Incorporate these two functions in the main program and try them out.
Now that we have introduced the basic concepts of lists and some of the
fundamental functions required for processing lists we can investigate what other
functions might be required. Operations which need to be carried out on lists
include: add a new element, insert one list into another, delete an element, fmd
an element, count the number of elements and order a list. Others may arise in
particular circumstances, depending upon the nature of the data stored in the list.
Functions which carry out these operations can be generated by writing a number
Lists and list processing 261
All of these functions require that a list be traversed or stepped through, either
partially or completely, to the end of the list. The simplest example is the
function to count the number of elements in the list and so we will begin by
writing this. All count () has to do is to move through the list one element at
a time and keep a running total of the number of elements visited. This function
can be written in two ways; recursively or iteratively. Both versions are given
below (Program 11.6 and Program 11.7).
Program 11.6
/* recursive count */
int count(Link p)
i f ( p == NULL
return(O);
else return((count(next(p))) + 1);
Program 11.7
/* iterative count */
int counti(Link p)
int total = 0;
while (p) {
total++;
p = next (p);
return(total);
Notice the base case in the recursive version. When a NULL is encountered the
function returns 0 since we do not wish to count this element. Otherwise one is
added to the return value. Since nothing is returned until the end of the list is
reached this results in the number of elements being returned to the calling
environment.
262 Mastering C Programming
The find () function can again be written in both recursive and iterative
forms. We will write a recursive function andleave the iterative version as an
exercise for the reader. This function requires some parameters: a pointer to the
list to be searched and a value to compare, or a pointer to a value to compare.
Which of these two options should we choose? Both have their advantages. The
first option is the clearest for a simple list whose contents is just a single value
(e.g. a char, int or string). However the latter option is required when a more
complex structure is being processed, although it may be less clear when used
with a simple list. Despite this slight disadvantage we will use the pointer
version as then the function can be more readily modified for use with more
complex structures.
The output for this function should be a pointer to the position in this list
where the element lies or NULL if it does not occur in the list. We can use this
latter value to check if the search has been successful. The pseudocode takes the
form:
find (p, q)
if p is NULL then
return NULL
if Contents of p = Contents of q then
return p
else
fmd(next(p), q)
Program 11.8
Link find(Link p, Link q)
{
if( !p ) /* End of List p */
return(NULL);
if( p -> c == q -> c
return (p); /* found */
else
find(next(p), q);
Lists and list processing 263
Insertion
The insert function requires a little more thought so that all possible cases are
covered. Consider the list L shown below (Figure 11.5). Possible insertion
points are indicated in the diagram.
a b c d
Now let us look at the other cases. Insertion at b and c are identical in
principle and are represented by the general case of insertion in the middle of a
list. In such cases the link from the previous element has to be broken and
redirected to point to the new element. The pointer of the new element (or list)
must point to the next element in the original list. Figure 11.6 illustrates this
process.
The last step in this process involves traversing the list and returning a link to
the last element. This is another of the list processes which is required quite
264 Mastering C Programming
Before insertion
... , ... , I
Lx Ly
L ...
N
D ... I
Nl
... , I N2
NULL'
After insertion
Lx Ly
I I I ...
~ I~ ~I
L
No ;p
Nl N2
Program 11.9
Link get_tail(Link p)
i f ( !next (p) )
return (p);
else
get_tail(next(p));
Exercise
Check that this function works correctly by going through it on paper, then
implement it in the existing program and test it.
Inserting a new last element in a list is straighforward (we assume that the
Lists and list processing 265
new list, to be inserted, tenninates correctly). All that is necessary is for the
Link from the last element of the old list to point to the head of the new list.
The complete insert_list () function is given in Program 11.10.
Program 11.10
Deletion
The next function which we need to consider is the delete function. This process
can be represented diagrammatically as in Figure 11.7.
Deletion involves the modification of links and the releasing of storage back
to the system. We deal with the changes in the links ftrst of all. Recall that the
ftnd function returns a pointer to the element found Thus if we search for 'D' in
the above list the pointer returned will be L2. At frrst sight all that needs to be
done is to free memory at this address and change L2 to L3. However, in order to
do this we require the address of the previous element (i.e. Ll) which is not
available to us. One solution is to copy the next element (L3) into the one to be
266 Mastering C Programming
deleted and then free the storage previously allocated to the element pointed to by
L3. Figure 11.8 provides a visual representation of these stages.
Ll L2 L3
... ,~;~---~~~-_;-~~~~~~~
Element to be deleted
After deletion
r--------------------
Ll L2
L3'
D L-1_..._::.t---1~~1__ ..._....J
Deleted element
Ll L2 L3 lA
~I D ~I s ~I
Copy L3 -> pn to L2 -> pn
Ll
r----r-----,
~I s
L2
I~ s
Still have link to lA
L3
I
lA
... I
l
+
Copy Contents of
+
Link broken
L3 to L2
Ll L2 L3 lA
~I s ~I
Free storage at L3
return that area to the system, making it available as storage for other variables.
We are now in a position to sketch out the stages in the element deletion
process. (Assume that p points to the element to be deleted.)
This function has no special cases and so we can write down the code
immediately.
Program 11.11
Link ptemp;
ptemp next (p) ;
el_copy(p, next(p));
free (ptemp);
In this function we have used yet another simple function (el_copy ())which
copies the element at q top.
Exercise
1. Enter the insert, delete and associated functions and try them out in your list
program. One problem with the present insert function is that the list being
inserted can no longer be referenced as a separate list once it has been inserted
into the new list (see Figure 11.6). This can be overcome by copying the list to
be inserted into a temporary list before carrying out the insertion - write a
copy_list function and incorporate this in the insert function.
2. Write a list_delete function which will delete a list from p to the end of the
list (inclusive). Make sure that if the list to be deleted is a sub-list (i.e. the latter
part of a list) then the former part of the list is terminated correctly.
268 Mastering C Programming
Ordering
The final list processing function we are going to look at is one to order a list.
We will assume that we have a simple list consisting of characters and that we
wish to arrange them in ASCII order. Once the function is written it can be
modified to deal with other types of ordering and with more complex structures,
using a key field for example.
X NULL
One way to achieve an ordering of the list is to traverse the original list and
place each element in tum in the correct place in a second list. Thus we would
have the end result shown in Figure 11.10.
X = NULL
We begin with a pointer N, whose Contents and Link are set to NULL. This
represents the initial set-up for the ordered list. The Contents of the first element
of list L are then copied into this initial element. We can now use a recursive
function to order the rest of the original list. The process involves comparing the
current element of L with each element of N until the position for insertion is
found. The element from L is then inserted into N and the process repeated until
the end of list L is reached. At this point, the ordered list resides in N.
This process can usefully be broken into two sections. The first involves the
main function which selects the element to insert, calls a function to find the
position in the ordered list at which to insert the element, carries out the
insertion and finally gets the next element. This function, which we will call
Lists and list processing 269
order () , will require two parameters, the address of the start of the unordered
list and a pointer to the start of the ordered list
The function position () which finds the correct place in which to insert
the new element is of type Link since it is required to return a pointer to the
element after which the new element is to be inserted. As before, with
insert (),we will use NULL to indicate insertion at the head of the list.
Program 11.12
Exercise
SUMMARY
CONCLUSION
This concludes our tour of C. I hope you have found the book useful and that it
has whetted your appetite for C programming. Programming can be fun,
programming even should be fun. So go ahead and play. Good luck!
Q Appendix A: the ASCII codes
1 "A SOH 33 ! 65 A 97 a
2 "B STX 34 " 66 B 98 b
3 "C • ETX 35 # 67 c 99 c
...•
4 "D EOT 36 $ 68 D 100 d
5 "E ENQ 37 % 69 E 101 e
6
7
"F
"G
• ACK
BEL
38
39
&
I
70
71
F
G
102
103
f
g
8 "H \b 40 ( 72 H 104 h
9 "I \1 41 ) 73 I 105 i
10 "J \r 42 * 74 J 106 j
11 "K VT 43 + 75 K 107 k
12 "L \f 44 76 L 108 1
13 "M \n 45 -' 77 M 109 m
14 "N so 46 78 N 110 n
15 "0 SI 47 I 79 0 111 0
16 "P DLE 48 0 80 p 112 p
17 "Q DCI 49 1 81 Q 113 q
,
18 "R DC2 50 2 82 R 114 r
19 "S !! DC3 51 3 83 s 115 s
20 "T DC4 52 4 84 T 116 t
21 "U § NAK 53 5 85 u 117 u
22 "V SYN 54 6 86 v 118 v
23 "W ETB 55 7 87 w 119 w
24 "X CAN 56 8 88 X 120 X
25 "Y EM 57 9 89 y 121 y
26 "Z SUB 58 90 z 122 z
27 "[ ESC 59 91 [ 123 {
'
28 "\ FS 60 < 92 \ 124 I
29 "] GS 61 = 93 ] 125 }
30 "" RS 62 > 94 " 126 -
31 "- us 63 ? 95 - 127
Q Appendix B: the line editor
This appendix contains the complete line editor which we have looked at from
time to time throughout the book. Some of the constructs will be familiar to
you as we have used similar examples before, others will be less familiar.
However you should be able to understand how each function works and how it
interacts with the other functions and with the main program. The complete
listing is given with comments interspersed between the functions explaining
any interesting or unusual features.
Some of the exercises in the preceding chapters have asked you to write
functions for the line editor. You will find solutions to these particular exercises
at the appropriate points in the program listings. If you have attempted the
exercises and have reached a different solution to that given here don't worry,
there is more than one solution. If yours is markedly different read through the
function given and make sure that you understand how it works.
I* function prototypes *I
char getnextchar(void),
read_command(int *pn1, int *pn2, int *valid);
int check_command(char em, int n1, int n2), isadigit(char c),
save_text(void);
void process_option(char com, int *n1, int *n2),
helpscreen(void), insert(int line),
delete(int n1, int *n2), print(int start, intend),
274 Mastering C Programming
/* external declarations */
int next = 0; /* next available line for appending, i.e.
current last line + 1 */
char text[MAXLINES] {MAXCOLS]; /*array to hold edited text*/
main()
puts("\n\n");
puts(" T H E C E D I T 0 R");
puts(" =======================II) ;
while(command != 'q') {
printf("\n :> "); /* command prompt */
command= read_command(&n1, &n2, &valid_com);
if ( !valid_com)
puts("*** Incorrect syntax***");
else
process_option(command, &n1, &n2);
The main program is quite short, it displays the opening screen, calls the file
read function (read_file ())and then uses a while loop to process the
editing commands. Notice the use of #define instructions to improve
readability and aid future modification. Notice also that all functions are
prototyped at the beginning of the program.
Functions
The functions appear in the following pages approximtely in the order in which
they are used in the main program. Each function is numbered and named for
easy reference. The first function (Program B.2) is the read_command ()
function which was given as an exercise in Chapter 8 (Exercise 11). This reads
in and parses the command line.
The line editor 275
/* function read_command()
**
** this function parses the command and obtains the line numbers
** *pn1 is a pointer to the first line for editing
** *pn2 is a pointer to the last line for editing
** valid is a flag to indicate correct syntax
** on exit the editor command character is returned.
*I
char read_command(int *pn1, int *pn2, int *valid)
break;
else
*pn2 *pn1; /* only one line to edit */
break;
case ' , ' : /* command of form ,n2x */
c1 = getnextchar();
if( c1 == '$') {
*pn1 = *pn2 = next - 1;/* edit n1 to last line*/
c1 = getnextchar();
else if (isadigit (c1)) {
*pn1 = *pn2;
*pn2 =get int(c1) - 1;
c1 = getnextchar();
else
*valid = FALSE; /* invalid second number */
break;
default : /* no numbers, just a command */
*pn1 = *pn2; /* edit one line only */
break;
This function carries out all the necessary operations for reading and parsing the
command line. It consists of a single switch statement with nested if state-
ments to take care of the various valid command sequences. The table on page 18
(Table 1.1) should be used to verify the logic of this function.
The library function f f 1 us h () is used to flush the input buffer and thus
remove any spurious characters. This means that the next character entered, after
a newline, is read by the read_ corrunand () function. It also guarantees that
unwanted characters are not inserted into the command line following an insert or
append command. The majority of invalid line combinations are trapped and
corrected by the series of if statements at the end of the function. These error
traps make some assumptions about what are invalid combinations, e.g. that nl
should be less than n2. However they do not trap and correct commands
consisting of a single comma and a command, e.g. ,d. This prevents the
accidental deletion of a line of text.
The line editor 277
The next few functions are some basic ones which are used by the above
function (Program B.2). They are getnextchar (), isadigit () and
get_int (). These were discussed in Chapter 6.5 (Programs 6.8, 6.9 and
6.10) and are included here (Program B.3) for completeness.
/* function getnextchar()
**
** this function skips spaces read from stdin
** it returns the first non-space character read
*I
char getnextchar()
char c;
return(c);
/*function isadigit()
**
** this takes a character as an argument and returns
** TRUE if it is a digit and FALSE otherwise
*I
int isadigit(char c)
return(TRUE);
else
return(FALSE);
/*function getint()
**
** this function reads charcaters while they are digits
** and converts them to a decimal integer.
** This integer is returned on exit.
*I
int getint(char c)
The only comment necessary concerning these functions relates to the last one
(i.e. getint ()).This function continues reading characters while they are dig-
its. However once a non-digit is read, it is required by the read_command ()
function (Program B.2). The present function cannot return the character as it is
already returning the converted integer (i) so another solution has to be found.
The one we have used here is to employ the library function unget c ( ) . This
places the character just read back into the input buffer - it can then be read by
the next character read instruction. Another solution is to alter the function so
that the value of parameter c is changed from within the function. You may like
to try modifying get_int () and read_command () using this method.
The next function is the one to check that a valid command character has been
entered. This was dealt with earlier, in Chapter 4 (Program 4.3), and so requires
no further discussion.
/* function check_command()
**
** this function simply checks for valid commands
** if command is insert then it also checks that nl = n2
**
** TRUE is returned if the command is valid, FALSE otherwise
*I
int check_command(char em, int nl, int n2)
The next function (process option ())is the heart of the program, it
processes the commands once they are entered. Apart from adjusting the values
of the line numbers {*pnl and *pn2), when relevant, the main task of this
function is to transfer control to the appropriate function to enable the required
task to be performed. The structure consists of a switch statement, with a
The line editor 279
case statement for each valid option. (See Chapter 4, page 100 for the basic
pseudocode.)
switch(com)
case 'a' : /* append to end of existing text */
*pn2 = next;
insert(*pn2);
break;
case 'd' : /*delete line(s) */
delete(*pnl, pn2);
break;
case 'h' : /* help */
helpscreen();
break;
case 'i' : I* insert line ( s) in middle of text *I
insert(*pnl); /*insert before line nl */
break;
case 'p' : /* display selected lines */
if( next > -1 )
print(*pnl, *pn2);
else
puts("\n ***Editor is empty***");
break;
case 'q' : /* quit the editor */
break;
case ' r': /* read the file data.txt if it exists */
*pnl = *pn2 = next = 0;
init_text (); /* fill array with NULLS */
read_file ();
i f ( next != 0
*pn2 = next - 1;
print (0, *pn2); /* display text just read*/
break;
case 's' : /* save all the text to file: data. txt *I
i f (! save_text ())
280 Mastering C Programming
/* function delete()
**
** this function deletes lines n1 to n2 inclusive
** next and n2 are updated accordingly
*I
void delete(int nl, int *pn2)
/* function helpscreen()
**
The line editor 281
The next function (Program B.8) displays the text on the screen. Once again it is
reasonably straightforward. The only test is to see if any text is available for
displaying. The simplest way to determine this is to test the value of next -
this is set to zero if the array text [] [] is empty. (See Chapter 9, Exercise 4.)
I* function print()
**
** this displays on the screen the specified text
** between lines n1 and n2 inclusive
*I
void print(int n1, int n2)
if( next == 0) {
puts("*** Editor is empty***");
return;
line = n1;
printf("%2d: " line+ 1);
for( col= 0; line<= n2; col++)
putchar(text[line] [col]);
if(text[line] [col] == NL )
line++;
col = -1;
if( line<= n2 )
printf("%2d ", line+ 1);
The insert function which follows (Program B .9) is the most complex of the
functions in the line editor. This function reads in characters from the keyboard
and stores them in the character array text [] [].A number of conditions have
to be checked: for example, is the editor already full?, has a newline (or return)
been entered? has the maximum number of columns been reached? The main
structure of the function consists of two while loops. The inner one reads a
line of text, i.e. until a return is entered or the maximum number of columns is
reached. The outer one reads lines of text until a terminating character is entered
at the beginning of a line (STOP) or the maximum number of lines is reached
(MAXLINES).
Additional tasks involve the in-line editing capability and how to adjust data
in the array when text is being inserted rather than appended. The first of these
tasks is achieved by using the library function getch () instead of get char ().
The former function is an unbuffered read, which means that the character read is
immediately available for use. On the other hand getcha r () is a buffered read,
which presents the problem that there is no easy way of telling when text has
overlapped into the next line. Using getch () means that the echoing of the
entered character has to be specifically programmed and also has the added
complication that the ENTER or RETURN key is a carriage return (' \ r ' )
rather than a newline (' \n ').The in-line editing is achieved by noting that the
backspace key ( ' \ b ' ) can be recognised. This fact can be used to enable
on-screen editing to take place in the line being entered. However, we need to
remember to update the array at the same time. Look carefully at the code for
reading a line, including the use of backspace, and check that you understand how
it works.
The problem of adjusting text already in the array when inserting rather than
appending is solved by use of another function, move text () . The code for
The line editor 283
/* function insert()
**
** this function inserts line(s) of text before line ln
** unless ln equals next in which case the text is appended.
** unbuffered input is used to allow for
** limited in-line editing - e.g. backspace can be used
** to delete characters entered on the current line
** entry is terminated by typing * at the start of a line -
** the symbol can be changed by altering the *define instruction
**
*I
void insert(int ln)
line = ln;
if(next >= MAXLINES) {/* check array is not already full */
puts("\n ***Editor is full***");
next = MAXLINES;
return;
i f ( ch == CR)
text[line) [col++] = NL;
/* replace CR with NL */
else
putchar(ch);
text[line] [col++] = ch;
As was mentioned above the movetext () function moves text up the array to
release room for the new line being inserted. It is only invoked if the first
character of the new line is not the terminating character (i.e. sTOP) and if there
is room to add more lines (i.e. as long as line is less than next and next is
less than or equal to MAXLINES). This function starts at the current last line of
the text in the array and shifts the text up into a new line until the position
where the line is to be inserted is reached. So this uses the decrement operator to
count down from next to ln (i.e. line--).
/*function movetext()
**
** this function moves text up the array by one line
** it starts at the end of the array and works down to ln
*I
The line editor 285
The remaining functions concern file i/o and initialisation of the char array
text [] [].The read_ file () function (Program B.ll) was discussed
briefly in Chapter 9 and the reader should refer to the relevant section for further
details (see pages 224-226 and Program 9.4).
FILE *fp;
char c;
int col = 0;
fclose (fp);
I* function save_text()
**
**this function saves the text stored in the array text[][]
** to the file data.txt
** it returns TRUE if saved successfully, FALSE if not
*I
int save_text(void)
fclose(fp);
return (TRUE);
The final function (init_text ())is used to fill the array text [] [] with
NULLs. This is not essential but it does prevent spurious characters appearing in
the array. Again it consists simply of two nested for loops.
/*function init_text()
**
** this functions fills the array with NULLS
*I
void init_text(void)
This brings us to the end of our discussion of the Line Editor program. Read
through each of the functions above and check that you understand how they
work. Try typing them into your own machine and then test out the complete
program. It is not guaranteed to be bug free, but it has been tested fairly
rigorously. As already noted, there are a number of improvements which could
be made and the logic could be tidied up in places. Use the program as a basis for
experimenting and see what improvements you can make.
0 Appendix C: the bridge tutor
In this appendix we present some of the functions for the basic bridge tutor.
These are limited to the easiest options of shuffling, dealing, displaying, sorting,
counting points and bidding (opening bid only). Some of these functions have
already appeared in various places throughout the book. They provide another
example, along with the calculator and the line editor of a working program.
Although in this case the options are limited you could modify the program to
simulate a simpler card game or if you are familiar with bridge you could extend
it to provide the points count and the opening bid for a specific hand instead of a
random hand. Further extensions might be to continue the bidding and playing
bridge- the computer against you and dummy! You should read through these
functions and make sure that you understand how they operate and then
incorporate them in the menu driven main program (Program 6.7 or, the slightly
modified version, Program C.l). Many improvements and refinements can be
made on them and it would be well worth you while trying to improve some of
them.
As with the line editor program we will take each function, or group of
functions, in turn and comment on the structure and any points of interest. The
first piece of code is the main program. This is a slightly modified version of
Program 6.7. The main changes include the prototyping of all the functions used
in the latest version and an option to allow the main menu to only be displayed
when requested. (This helps when checking the program operation.)
Program C.l
#include <stdio.h>
#define FALSE 0
#define TRUE 1
/* prototype functions */
void main_menu(void), deal(void), shuffle(void), display(void),
bubble(int a[], int n), print(int a[], int size),
exchange(int *p, int *q, int *sorted),
o_bids(int points, int balanced);
char valid_c (void), * print_bal (int balanced)_,
* bid_suit(int balanced, int min, int max, int cards[]);
int execute_option(char ch), count_points(int a[]),
balanced(int a[], int *min, int *max, int cards[]);
main()
{
char c = •m•; /* display menu to begin with */
do {
if ( c == 'm' )
main_menu(); /*only display menu if needed*/
c = valid_ c () ;
while(execute_option(c));
**
~* This displays a menu on the screen.
'r*/
woid main_menu(void)
puts("\n\n");
290 Mastering C Programming
puts(" ********************************'');
puts(" BRIDGE TUTOR") ;
puts(" ********************************\n'');
puts(" 1. shuffle");
puts(" 2. deal");
puts(" 3. display the hands");
puts(" 4. count the points");
puts(" 5. bid");
puts(" 6. play");
puts(" m. display this menu");
puts(" Q. quit\n\n");
puts("Press a digit (1-6), m, or Q to Quit.");
/* function valid_c(void)
**
** This function checks for a valid command -
** only valid commands are accepted.
** To improve presentation unbuffered input is used
**with no echoing (i.e. getch()).
** On exit the function returns the (valid) character entered.
*I
char valid c(void)
char c;
do {
c = getch ();
} while( c != 'Q' && c != 'm' && (c < '1' II c > '6'));
return(c);
switch (c) {
case r 1 r :
printf("\n Executing shuffle");
shuffle();
break;
case r 2 r :
printf("\n Dealing the hands");
The bridge tutor 291
deal();
bubble(north, 13); /* sort each hand into suit */
bubble(east, 13); /* & denomination order */
bubble(south, 13);
bubble(west, 13);
break;
case 1 3 1 :
printf("\n Displaying the hands ");
display();
break;
case 1 4 1 :
puts ("\n Counting the points ");
puts(" The points are .... ");
printf("\t NORTH %d\n", count_points(north));
printf ("\t EAST %d\n", count_points(east));
printf("\t SOUTH %d\n", count_points (south));
printf("\t WEST %d\n", count_points(west));
break;
case 1 5 1 : /* only simple opening bids implemented */
printf("\n Bidding");
bal = balanced(north, &min, &max, cards);
printf("\n NORTH: %s ", print_bal(bal));
points= count_points(north);
o_bids(points, bal);
i f ( ((points == 13 II points == 14) && bal) II !bal)
printf(" %s ", bid_suit(bal, min, max, cards));
break;
292 Mastering C Programming
case '6' :
printf ("\n Playing bridge - maybe ");
break;
case 'm' : I* display the menu *I
break;
case 'Q' :
return(FALSE);
return (TRUE);
The next set of functions (Program C.3) concern the shuffling and dealing of the
cards. These were discussed in Chapter 8. The shuffle () function is a
modification of the function to generate a random character and should be easy to
follow. The deal () function simply distributes the cards between the four
hands north, east, south and west and is identical to Program 8.13. Note that on
each run of the program the frrst call of shuffie will produce a particular sequence
of random numbers, the second call a different set, and so on. So that new hands
can be obtained by repeating the shuffie a different number of times (i.e. select
option 1 3, 7, 8 etc. times before selecting option 2 to deal).
I* function shuffle()
**
** This function simulates the shuffling of a deck of cards. It
** uses the library function rand() to produce a pseudo-random
** number.
*I
void shuffle(void)
I* function deal()
**
** this function distributes the cards in deck to
The bridge tutor 293
The next two functions (Program C.4) are used to display the cards on the
screen. The first one is very simple. It just displays a prompt and then calls the
print ( ) function for each of the four hands of cards. The second function is
worth discussing a little further. We will do so once you have read it over.
I* function display()
**
** this function displays all four hands, it uses the
**function print() to improve the display
*/
void display ()
/* function print()
**
** this function provides a simple screen display
** of a hand of cards. It uses the ASCII characters
** for each suit. These will be available in most
** implementations but may use different codes.
*I
void print(int a[], int n)
294 Mastering C Programming
int i, suit = 3;
static int suit_symbol[] = {6, 3, 4, 5}; /* • ¥ + •*I
static char *face_value[] =
2'',''3'',''4'',''5'',''6'',''7'',''8'', ''9'',''10'',''J'',''Q'',''K'',''A''} ;
{ '1
The first point to note about the print ( ) function is the way in which the
integer operators I and %are used on the integer array a [ ] to detennine the suit
and face value of the cards. The array will contain 13 integers in the range 0 to
51. Using integer division by 13 we can obtain another integer in the range 0 to
3. Assuming that the integers 0- 12 represent clubs, 13 - 25 diamonds and so
on, we can associate each of these digits with a particular suit.
In order to find the face value of a card we can use the modulo operator(%).
The integer obtained will be in the range 0 to 12. This time we associate each
integer with a card value. So 0 corresponds to a 2, and 12 to an Ace. These
integers can therefore be used as indexes to the array face_ value [ ] . The
various conditional statements in the function govern when the suit symbol is
printed. In this case the suit is printed after the cards it contains. You may like
to try modifying the function to display the suit first. This last part requires that
the array be in order before the function is invoked.
Finally we should point out the use of the ASCII code to obtain the symbols
for each suit. Most implementations will provide a code for each of these
symbols although their exact value may vary. If your implementation does not
include these symbols in its character set then you can modify the function by
replacing the symbols with the initial letters of the suits.
Program C.5 contains the code necessary to perfonn the sorting of the integer
arrays just mentioned. The two functions bubble () and exchange () were
discussed in Chapter 8 (Program 8.14 and Program 8.15) and should require no
further explanation.
/* function exchange()
** This function swaps two ints if the
** first argument is less than the second.
** *sorted is set to FALSE if a swap takes place.
*I
void exchange(int *p, int *q, int *sorted)
int temp;
The remaining functions used in the bridge program concern the evaluation of a
hand, by counting the points, and the choosing of what bid to make.
The first function (Program C.6) provides one answer to exercise 6 in Chapter
4. Refer back to that exercise if you are not familiar with how the point count is
arrived at.
/* function count_points()
**
** this function uses the standard method of counting points
** in bridge. A, K, Q & J count 4, 3, 2 and 1 point
** respectively. Additional points are added for distribution.
** The function returns the total point count.
*I
296 Mastering C Programming
/* distribution count */
for( i = 0, suit = 3, cards 0; i < 13; i++) (
while ( a [ j] /13 < i ) (
suit--;
cards = 0;
The local variables used by this function (i, suit, cards and points) are
all integers. i is used as an index to the array a [ ] holding the hand to be
analysed and suit is an integer representing each of the suits in turn (from
Spades down to Clubs). The variable cards is used to keep track of the number
of cards in each suit and points is a variable holding the point count
The first for loop adds up the points based on distribution. This uses a
while loop and integer division to check when the suit changes. (Again the
array must be in order before this function is invoked.) An if statement is not
sufficient here since a suit may be missing altogether (i.e. void), in which case
the conditional statement which computes the distributional count would not be
executed.
The second for loop uses the modulo operation discussed above but this
time to identify the face value of the cards. A series of if else
statements are then used to add the appropriate number of points, if any, to the
running total.
The bridge tutor 297
/* function balanced()
**
** this returns TRUE if the hand is balanced
** i.e. 4-4-3-2, 4-3-3-3 or 5-3-3-2 with 5 in Clubs or Diamonds
*I
int balanced(int a[], int *min, int *max, int cards[])
return (TRUE);
return(FALSE);
/* function print_bal()
**
** this function just returns "balanced" or "unbalanced"
*I
char * print_bal(int balanced) /* note function returns a */
/* 'pointer to char' */
if(balanced) return("balanced ");
else return("unbalanced");
The logic of this function is quite straightforward and you should find no
difficulty in understanding its operation.
/* function o_bids()
**
** This function calculates an opening bid based on the points
** and the basic make-up of the hand (i.e. balanced/unbalanced).
** This does not take account of the number of playing tricks
** in the hand and it requires a minimum of 13 points for a bid
*I
void o_bids(int points, int bal)
switch( points ) (
case 13 :
case 14 : printf("One in a suit ");
break;
case 15
case 16
case 17 i f ( bal )
The bridge tutor 299
The final function developed for the bridge program is given in Program C.9.
This is used to determine which suit to bid, if any, based on some standard
principles of bridge. In outline these are as follows. If a hand is unbalanced with
more than six cards in one suit then you should bid the longest suit. If two suits
each have six cards then bid the higher ranking of the two. (The suits are ranked
in ascending order as Clubs, Diamonds, Hearts and Spades.) In a hand containing
a maximum of five cards in a suit that suit should be bid. If two suits contain
five cards each then, unless the suits are Clubs and Spades, the higher ranking of
the two suits should be bid. In the case of Clubs and Spades each with five cards
you should bid Clubs.
With balanced hands we only need to deal with a point count of 13 or 14.
(Hence the conditional statement in the bidding option of the
execute_option () function, e.g. if ( ((points == 13 II points ==
14 l && ball 1 1 ! ba 1 ) . ) In other cases the choice of bid is determined by the
point count (see the Table on page 102). The suit to bid in the case of a balanced
hand containing 13 or 14 points depends upon a variety of factors. If a five-card
suit is present (which will be either Clubs or Diamonds) then that suit should be
bid. With a 4-3-3-3 distribution bid Clubs. With a 4-4-3-2 distribution a number
of possibilities arise. If Spades and Diamonds both have four cards then you
should bid the suit 'below' the 2 card suit, i.e. Spades if Clubs has only two
cards and Diamonds if Hearts has only two cards. If Clubs is one of the four-card
suits and either Hearts or Spades is the other then bid Clubs. Finally we are left
300 Mastering C Programming
with the touching combinations, i.e. when Clubs and Diamonds, Diamonds and
Hearts or Hearts and Spades both have four cards. In such cases the opening bid
should be that of the higher ranking of the two suits.
The above notes should allow you to check the logic of the bid_sui t ()
function. It relies on simple control structures, basically for loops and if
statements. The return value is a pointer to a string holding the name of the suit
to bid.
/* function bid_suit()
**
** This function returns a string indicating the suit to bid.
** It takes as parameters the minimum no. of cards in a suit,
** the maximum no. of cards in a suit,
** a flag indicating balance and an array holding the no.
** of cards in each suit for the hand concerned.
*I
int bid_suit(int bal, int min, int max, int cards[])
int i, j;
static char *suit[] =
{"Clubs ","Diamonds"," Hearts"," Spades"};
/* determine suit to bid in balanced hand - used for 13 */
if( bal ) { /* & 14 points */
/* 4-4-3-2 distribution */
/* check if touching - i.e. CD, DH or HS */
for( i = 0; i < 3; i++)
if( cards[i] ==max && cards[i+1] ==max
return(suit[i+1]);/* bid higher ranking*/
/* suits not touching */
if(cards[O] ==max && (cards[2] ==max I I cards[3] ==max))
return(suit[O]); /*two suits same length: 1 Club*/
/* Spades and Diamonds same length */
if( cards[1] ==max && cards[3] ==max )
if( cards[O] ==min )
return(suit[3]); /*spades*/
else
return(suit[1]); /*diamonds*/
/* 4-3-3-3 distribution */
if( max== 4 && min== 3 )
return(suit[O]); /* clubs */
/* 5-3-3-2 distribution */
for( i = 0; i < 4; i++ )
if( cards[i] ==max )
return(suit[i]); /* 5-card suit*/
else { /* unbalanced */
The bridge tutor 301
i f ( max == 5 ) {
/* clubs and spades */
if( cards[O] ==max && cards[3] max )
return(suit[O]); /*clubs */
for( i = 0; i < 3; i++ )
for ( j = i + 1; j < 4; j++)
if( cards[i] ==max && cards[j] ==max)
return(suit[j]); /*higher suit*/
for( i = 0; i < 4; i++)
if( cards[i] ==max )
return(suit[i]);/* longest suit*/
else {
for( i 0; i < 4; i++)
for(j=i+l; j<4; j++)
if( cards[i] ==max && cards[j] ==max)
return(suit[j]); /*higher suit*/
for( i = 0; i < 4; i++)
if( cards[i] ==max)
return(suit[i]); /*longest suit*/
This concludes our look at the bridge program and its functions. If you are not
familiar with bridge you should still be able to get some useful ideas from the
listings and the discussion. If you are an expert bridge player you will no doubt
find fault with some of the functions and hopefully be able to rectify them and
add new ones.
OAppen dix D: further reading
Jeffrey Esakov and Tom Weiss (1989). Data Structures -An Advanced Approach
Using C, Prentice-Hall International Editions, Englewood Cliffs, N.J.
James F. Korsch and Leonard J. Garrett (1988), Data Structures, Algorithms, and
Program Style Using C, PWS-KENT Publishing Company, Boston.