TEACH C.EX1                                          A. Sloman, May 1988
                                  Updated by Steve Easterbrook, Nov 1992

C PROGRAMMING - FIRST SET OF EXERCISES
--------------------------------------

Note - most of the exercises in this file are for you to try on your own.

CONTENTS - (Use <ENTER> g to access required sections)

 -- Reading
 -- Possible things to learn
 -- Use a special directory
 -- Compilers & ANSI C
 -- A first exercise
 -- Things to note in examples.c
 -- Play with the examples.c file
 -- Change some of the arguments to printf
 -- Conditional expressions and statements
 -- Simple and compound statements
 -- Exercise, define test5(x,y)
 -- Loops in C
 -- Exercise define addup(start,incr,fin)
 -- Assignment: define test6(start,incr,fin)
 -- Assignment: fix printslope and slope
 -- Using a cast to transform a type
 -- Using macros
 -- SEE ALSO

-- Reading ------------------------------------------------------------

Main text henceforth referred to as K&R:
B.W. Kernighan & D.M. Ritchie
    The C Programming Language
    Prentice Hall 1978

There are other books listed in TEACH * C.refs, and more will no doubt keep
turning up in bookshops. If you use other books you should still be able to
do these exercises. Please let your course tutor know which you find
specially good or bad.


-- Possible things to learn -------------------------------------------

Here is a list of possible things to learn about C. You'll only manage a
subset in the time available. In general, there is no RIGHT subset, but
the items marked with an asterisk form a kind of 'core' syllabus for an
introductory C course.

*- The syntax of C (See summary in HELP * C_SYNTAX)
*- Conditionals, loops, switch, procedure calls, arguments, results
*- Arrays (including strings) and structures
*- Pointer manipulation and accessing fields of structures
*- Data-types available and ways of defining new data types
*- Identifier declarations
*- Logical and arithmetic operations
*- The use of printf for formatted printing and scanf for input
    see "man printf" "man scanf"
*- Allocation of store in C - local (automatic) and global (static,
    and external)
*- The use of the standard input/output routines
    getc, putc, getchar, putchar. See "man getchar" "man putchar"
    Also "man 3s intro"
*- File manipulation routines
    close, creat, open, read, write. See their man files
-  Store management
    malloc and other things described in "man malloc"
-  C Library facilities available and described in
    man 2 intro (system calls, file handling, etc.)
    man 3 intro (mathematical and other libraries)
-  How to write portable C programs: see "man stdio"
-  More advanced facilities
    man 3m intro        - mathematical functions
    man 3n intro        - network facilities
    man 3r intro        - remote procedure calls (RPC)
    man 3x intro        - other facilities, e.g. screen management
*- How to run C programs
    (See HELP * CCOMP, and "man cc" - available as HELP * CC on TSUNA)
*- The use of 'cc -c' to produce .o files (object files)
-  The use of 'ld' to link object files
*- The use of header files (.h files) and #include
    Standard header files available in /usr/include/*.h
    E.g. #include <math.h> gets /usr/include/math.h

-- Use a special directory --------------------------------------------

You are advised to make a sub-drectory for your C programming exercises.
E.g. use the command  "mkdir c". If you do all your C work in that
directory you will not risk files produced by the C compiler overwriting
other files that happen to have the same name.

-- Compilers & ANSI C -------------------------------------------------

You should be aware that there are two versions of the C programming
language in common use. The version used throughout this teach file is the
'original' version, as described in the first edition of K&R. The second
edition of K&R describes ANSI C, which is a (relatively) new standard
definition of the language. This latter version should eventually supercede
the original, but it will take a long time for the old version to die out
completely.

There are many changes in the ANSI version, but most of them
are trivial or rather esoteric, and not to be worried about at this stage.
There is a full list of changes in an appendix of the second edition of
K&R. The most important change is the syntax of function definitions. In
ANSI C the types of parameters are defined within the brackets, rather than
separately. Hence the definition for the max function described below would
be written:
    max (int x, int y)

Compilers for both versions are available on tsuna & tsunb. For the
original C, use the compiler 'cc' as normal. If you want to use ANSI C,
there is a compiler called 'gcc' available. Beware that the UNIX 'make'
command and the ved command 'CCOMP' both use 'cc' by default.

-- A first exercise ---------------------------------------------------

Make a copy of the file ~steveea/examples.c

Compile it with the unix command

    % make examples

This will look for a file callex examples.c, compile it and produce a
new executable file called "examples". Use "ls -l" to see that it
exists, that (for you) it has mode "rwx" i.e. is readable, writeable,
and executable. You can then run it and see what output it produces,
i.e.

    % examples

Now compile and run the program inside VED using the <ENTER> ccomp
command. Read HELP * CCOMP to find out what it does.

I.e. edit the file, examples.c, then do
    <ENTER> ccomp

The first time you use ccomp in any session there will be a delay while
it is autoloaded. The output (if any) is read into a new VED file.

Compare the contexts of examples.c and the output produced and try to
understand how the program produces the output.

-- Things to note in examples.c ---------------------------------------

1. A line starting with "#define" defines a macro, ie. something to be
substituted in the text during the pre-processing stage. So "max"
is defined as a function (i.e. a procedure, in POP-11 terminology),
whereas "MAX" is defined as a macro. The lecture explained the
difference.

2. There is no special keyword telling you that a function is being
defined. The mere occurrence of
    max(x,y)

outside of any pre-existing context tells the compiler that a function
called "max" which takes two arguments, is being defined. For a
function of no arguments, empty parentheses are used, e.g.

    foo()

Later you'll learn that the type of the result produced by the function
can be specified before the name, e.g.

    int max(x,y)

says that the function max produces a result of type int. In fact type
int is assumed as the default for all functions. Try making it explicit.
I.e. put "int" before "max" and check that the file can still be
compiled and run successfully.

3. The arguments to the function must have their type specified in the
function definition. This is done immediately after the function header,
e.g.
    int x,y;

This is a declaration. In the definition of "max" try changing "int" to
"long" and see if it affects anything in the program. It shouldn't since
nothing in examples.c uses the difference between short and long
integers.

4. The various functions defined in the file are invoked via the
function called "main" defined at the end of the file. When a C program
starts running it invokes the function called "main". So every program
must have one function with that name. You can't, as in Pop-11 just
define a function called "fred" then write "fred();" and expect it to
run.

So a C program consists of a collection of declarations and function
definitions including a function called "main" which is automatically
run when the program starts.

-- Play with the examples.c file --------------------------------------

Try changing examples.c in various ways, e.g. with different numerical
arguments or different things to be printed out in the printf commands.
Then recompile and re-run it, using either "make" or <ENTER> ccomp.

Also try changing the function -main- at the end of the file so that it
invokes the other functions with different arguments, and check that it
works as expected.

Try to generate compile time error messages, as follows (do only one
of these at a time then compile the file):

    Insert or delete a semi-colon or comma
    Insert or delete one or more of { } ( )

N.B. If you have any problems and cannot understand what is happening,
try the following
    a. ask another student for help
    b. try the demonstrator (announced in a mail message)
    c. make a note of the problem and bring it to the tutorial

-- Change some of the arguments to printf -----------------------------

-printf- is a very useful function. It takes a string (delimited by
double quotes, unlike POP-11 strings), and possibly some additional
arguments. It prints the string. If there are additional arguments
they are printed in the middle of the string at locations indicated by
the occurrence of percent symbols "%". The % is followed by one or more
characters indicating how the thing is to be printed, e.g. as a decimal
integer (%d), as a floating point number (%f) as an octal number (%o),
and so on.

The unix man file "man printf" tells you all about the options.

Try adding some extra printf statements into -main-, e.g. filling in
something appropriate into the arguments in the following:

    printf("\nThe sum of %d and %d is %d", ... , ... , ...);

    printf("\n%f divided by %f is %f, ..., ..., ...);

(use "+" for addition and "/" as in POP-11).

The "\n" represents a newline character, as in POP-11 strings. (See
HELP *ASCII).

Try out the field specification options, as in the definitions of
-printslope- and -test3- in the examples.c file. E.g.
    %3d     means print a decimal integer in 3 spaces
    %8.4f   means print a float with 8 columns before and 4 figures
            after the decimal point.
    %-10d   means print the integer left-justified within a 10
            column field.
    %%      means actually print a (single) percentage sign!

There are many more options in printf!

-- Conditional expressions and statements -----------------------------

Roughly, an expression denotes an object, the result of evaluating the
expression, or else a location where something is to be stored, whereas
a statement (or imperative) tells the computer to do something. An
expression can be used as argument to a function, or on the right or
left of an assignment operator.

The notion of statement and expression are not totally distinct, since
often the evaluation of an expression involves DOING something. In C an
expression can be turned into a statement by following it with a
semi-colon.

For a definition of the syntax of expressions and statements in C look
at TEACH C_SYNTAX, or the C reference manual at the back of K&R, which
ends with a syntax summary (section 18).

Note the peculiar syntax for conditional expressions in C. In the
definition of function "max"  in the examples.c file, the expression

    <condition> ? <expression1> : <expression2>

has the value of expression1 if the condition is is not FALSE, otherwise
expression2. In C, the integer 0 counts as FALSE, and everything else as
TRUE.

Unlike the conditional expression, the conditional statement in C is (a
bit) more readable:

    if (expression) statement
or
    if (expression) statement1 ELSE statement2

Note that there is no "then" or "endif" and that the parentheses around
the condition expression are essential.


-- Simple and compound statements -------------------------------------

If you want the "then" or "else" statements to be compound, i.e. made up
of a sequence of statements (instructions) then you have to use curly
braces around the compound statement, e.g.

    if (expression) {statement1; statement2; statement3;}

This is a general rule in C. There are many places where you can have
EITHER a simple statement like

    x = 3;       or      printf("my string");

OR a compound statement, such as

    { x = 3; printf("The value of x is %d",x); }

No semi-colon is needed after the right brace "}" in a compound
statement. However, you do need a semicolon before it, if the last
sub-statement is not compound.

-- Exercise, define test5(x,y) ----------------------------------------

Add to examples.c a function called "test5". It should take two
arguments, both integers, x and y (remember to declare them).

If x is bigger than y it should execute two printf statements saying so
and two other statements if y is bigger. I.e. use the format

    if ( ... )
        { printf( ...); printf( ...); }
    else
        { ...; ...; }

(by filling in suitable bits of C in place of the dots).

If x is bigger, then -test5- should first print out, on a line by
itself:

    The first number is bigger

followed by, on the next line,

    namely, ...

with the bigger number instead of the dots. If y is bigger it should
print out something similar, also using two separate printf statements.

To make the program run -test5-, add instructions to do so with,
different pairs of numbers, into the definition of "main" at the end.
Compile and run it and make sure it does what you expect.

-- Loops in C ---------------------------------------------------------

In addition to the form for conditional statements, there are similar
forms for various loop statements in C, e.g.

    while (expression) statement
    do statement whle (expression)
    for (expression-1; expression-2; expression3) statement

In each case where a statement is specified you can use either a simple
statement terminated by ";" or a compound statement in braces.

There is an example of a for statement in examples.c, in the function
-test4-. In "for" statements expression-1 is the first thing to be
done. It is optional. It is often used to initialise some variable that
will take different values each time round the loop. In the definition
of -test4- it is

    n=p

which in C is not an equality test but an assignment, equivalent to
the Pop-11 p -> n. That is, in C assignments go from right to left.
(For equality use n == p. In fact C has a whole variety of assignment
operators which both do an assignment and also do something else, like
increment or decrement the variable on the right.)

Expression-2 is a condition, which is tested before the loop statement
on each iteration. If it evaluates to anything other than 0 (false), the
loop continues. Expression-2 is optional and defaults to 1, i.e. true.
So if Expression-2 is missing that is equivalent to an infinite loop.

Expression-3, which is also optional, indicates what is to be done after
each execution of the loop statement to prepare for the next one. In
examples.c, Expression-3 is "n++" which means "increment the value of n
by 1". I.e. it is roughly equivalent to "n = n + 1", which, in POP-11
would be "n + 1 -> n". The n++ is not exactly equivalent to the
assignent, since in C n++ is an expression that returns the value of n
before the increment, whereas the value of "n = n + 1" is the new value
of n. In the context of the "for" statement the difference does not
matter, since the value of expression-3 is not used.

Try changing test4 so that instead of n++ it has n = n + 1 for
expression-3. Compile and run the program to make sure it still
produces the same result.

Then change it so that n is incremented by some other number, instead
of 1.


-- Exercise define addup(start,incr,fin) ------------------------------

In a file called add.c define a function called addup, which takes three
arguments, start, incr, fin all of type "long" (i.e. long integer) and
produce a result of type long, as follows.

The result is the sum of all numbers less than or equal to fin, got
by repeatedly adding incr to start. Include start in the total. So
    addup(1,1,3) = 1 + 2 + 3 = 6
    addup(2,3,10)= 2 + 5 + 8 = 15
    etc.

The function heading would have to start thus (with an appropriate
comment between /* ... */):

/*
   Function -addup- returns result of type long, and takes arguments
   of type long
*/
long addup(start,incr,fin)
    long start, incr, fin;

(You could use "int" instead of "long", and then you would be restricted
to smaller numbers.)

The function body is enclosed in braces { ... }

You could declare a local variable of type total, initialised to 0.
    long total=0;

Then in a loop, repeatedly add the value of start to total, after adding
incr to start.

Use a for loop, with the syntax

    for(<initialise>; <continuation test>; <step>)<loop body>;

Note that any of <initialise>, <continuation test> and <step> can be
empty. In this case, start, incr, fin and total all have values
initially so no initialisation is needed. The continuation test is
    start <= fin

The step is
    start = start + incr

The loop body can be simple or complex. E.g. a simple version might
be:
    total=start+total;

A more complex version would print out the current values of start and
total, in which case braces would have to be used, something like:

    {
        total=start+total;
        printf("start = %d, total=%d\n",start,total);
    }

The definition would have to end with a command saying what result is to
be returned, e.g.
    return(total);

Put all that together in a definition of -addup- then in the same
file put a definition of -main- that runs and prints out the result
of a series of tests, e.g. something like:

    main()
    {
        printf("Total for 1,1,3 is %d\n",addup(1,1,3));
        printf("Total for 2,2,8 is %d\n",addup(2,2,8));
        printf("Total for 0,5,100 is %d\n",addup(0,5,100));
    }

-- Assignment: define test6(start,incr,fin) -----------------------------

You could do this one in a file called av.c as it is concerned with
averages.

Define a procedure called "test6" that returns a float as result and
takes as arguments three floats (decimal numbers), declared as

    float start,incr,fin;

It should cycle through all the numbers from start to fin got by
repeatedly adding incr to start, and find their average. E.g.

    test6(3.0,1.0,5.0) should be

    3.0 + 4.0 + 5.0
    ---------------         i.e. 4.0
          3

It will have to add up the total set of numbers, e.g. using a variable
of type float called "total", and it will have to divide the total by
the number of values used. One way to do this is to use a counter of
type int, which is incremented by 1 each time round the loop, then used
to divide the total.

To see how to define and initialise a local variable which is not one of
the formal parameters of a function look at function -slope- in
examples.c It has two local variables of type int, namely dx and dy.
In your definition of -test6- use one of type int, and one of type
float, in two separate local declarations. Use a "for" loop analogous to
that in test4, but with a different increment, and instead of printing
out intermediate values add up the total and increment the counter.

The final statement in the function body could be
    return(result/count);

Define the function and put it into a file called av.c, and in the
-main- function put in several tests, including the following:

    printf("\nThe value of test6(3.0,1.0,10.0) is %f",test6(3.0,1.0,10.0));
    printf("\nThe value of test6(0.0,2.5,10.0) is %f",test6(0.0,2.5,10.0));
    printf("\nThe value of test6(0.0,3.5,10.0) is %f",test6(0.0,3.5,10.0));

Alternatively, use a function print_test6(start,incr,fin) which does the
equivalent of one of the above printf statements, analogous to the
function -printslope- in examples.c, then use it in -main- to test your
definition of -test6-.

Check that the results printed out are correct.

ASSIGNMENT: hand in a report describing the contents of your av.c,
showing a test run, and discussing any limitations you can think of.
(See TEACH * REPORTS, ignoring the bits that are specific to POP-11.)

-- Assignment: fix printslope and slope ---------------------------------

Look at the definitions of -slope-, -printslope- and -test3- and check
that the output produced is correct. Copy them into a file called
slope.c.

Now try changing some of the numbers in test3 so that so that the slopes
are not whole numbers. e.g.

    printslope(0,0,2,3) should print a slope of 1.5

The problem is in -slope-. The value is calculated by dividing two
integers, dy and dx. C is defined so that if you divide an integer
by an integer the result is an integer, truncated downwards.  E.g.
3/2 becomes 1 instead of 1.5. So you need to change slope so that it
takes floats, and change the declarations of dx and dy so that they
are floats. Change printslope so that it takes floats. Then inside
the function main, test it with arguments like these:

    printslope(0.0, 0.0, 2.0, 3.0);
    printslope(-2.0, 0.0, 2.0, 3.0);

and others. Make sure that it produces the right results.

WARNING: in the course of your tests if you have made a mistake you may
find that C gives you the unfriendly message
    "floating point exception core dumped"

in which case it will have created creted a file called "core" in your
directory. This mechanism is designed so that when programs are very
complex and fail inexplicably you can use a debugging tool to analyse
the state of the core dump. (See "man adb"). However, we shall not get
into that in this course.  So just remove the file called "core" and try
again. If you get totally stuck ask for help.

ASSIGNMENT: write a report on your modified versions of slope and
printslope, explaining what you had to change to make it produce the
right results, and why.


-- Using a cast to transform a type -----------------------------------

It is possible to keep -printslope- and -slope- as functions that take
ints, keep dx and dy as int variables, but inside the function -slope-
convert dy to a float before doing the division, using

    (float) dy/dx

Here "(float)" is called a "cast". A cast is used to transform an
expression of one type (here int) to an expression of another type
(here float). Casts are often used in C, but have to be used with
great care.

It is not necessary to cast both dx and dy as floats since the divide
operator "/" will do that if given one float and one integer. So you
could also try

    dy/(float)dx

OPTIONAL assignment. Include versions of slope and printslope that
take integers and produce floats as necessary. Test them on integer
co-ordinates that correspond to slopes that are not whole numbers, and
write a report on what you have done and how it works, including
printout demonstrating the tests.

-- Using macros -------------------------------------------------------

Notice the difference between the  function "max" defined in  examples.c
and the "macro" MAX. The difference is that calls to the former  produce
a subroutine call, whereas the latter produces "in-line" code. I.e.  the
text is substituted at compile time. So the compiled code is bigger  but
should run faster. (Though too-much inline code can make the program  so
big that paging slows it down!)

Try putting a new macro definition at the top of the file.

    #define PP(a,b) printf("\nThe bigger of a and b is: %d",max(a,b))

Then instead of typing the following in test1, you could type

    printf("\nThe bigger of 3 and 4 is:  %d",max(3,4));

    PP(3,4);

Try defining a new function test1a, which is like test1, but uses PP.
Insert an appropriate call of test1a into main, then recompile and run
the program, checking that the use of PP produces the right output.

Play with other changes, till you feel you understand everything in
examples.c, before moving on.



-- SEE ALSO -----------------------------------------------------------
TEACH * C.refs
HELP * C_SYNTAX
VED  ~steveea/examples.c


--- $poplocal/local/teach/c.ex1
--- Copyright University of Sussex 1992. All rights reserved. ----------