We will now concentrate exclusively on the content of programs.
We return to the elementary program used in the previous chapter, and discuss its features.
/* Program written by EF October 1991 */ main() { printf( "Hi there!\n" ); /* The "\n" represents a newline character */ } /* end of the main program */
Notes:
The earlier example program could be written
main(){printf("Hi there!\n");}
DO NOT WRITE LIKE THIS!
The identifier printf is used in combination with the parameters following it in printing instructions as follows.
/* Program written by EF */ /* October 1991 */ main() { /* to print several lines of text */ printf( "My room is\n1102 Tower\nUninott\n" ); /* and to combine values and text */ printf( "One seventh is %f\n", 1.0 / 7 ); } /* end main */
Text between double quotes is printed exactly as given, arithmetic values are evaluated and printed as numbers where a "%" appears in the format string , see details below.
We will wish to have identifiers to represent variables which will be used for storing intermediate results during the running of the program.
For each identifier that we use, we must tell the computer the type of object which we wish to use it for.
/* Program written by EF */ /* October 1991 */ #define pi 3.14159 #define seven 7 main() { /* A integer variable, initialised */ /* Its value may be reassigned */ int number = seven; /* A float variable, initialised */ /* It can contain non-integral values */ float reciprocal = 1.0 / number; /* An integer variable, not initialised */ /* to any particular value */ int square;
/* Now to print some results */ printf( "The reciprocal of %d is %f\n", number, reciprocal ); printf( "The area of a unit circle is %f\n", pi ); square = number * number; printf( "Square is %d\n", square ); } /* end main */
The first "printf" instruction will print the text
The reciprocal of 7 is 0.142857
The second prints
The area of ... is 3.14159
The third will print
Square is 49.
Each print instruction in the above examples ends with a newline character. You will generally want to end with a newline, unless you are printing a prompt requesting the user to enter a reply.
The identifiers you choose to represent variables or #define items must satisfy various rules and recommendations.
Examples of identifiers are:
It is common practice to use the underscore symbol to separate the component parts of an identifier.
Declarations are program statements which tell the compiler which identifiers we intend to use. If an identifier occurs which we have not declared (either we forgot to declare it, or, more likely, we mistyped an identifier) the compiler will print an error message. Declarations such as those in the above program do two things:
At a later stage, we may need to distinguish between these two effects by separating a declaration into two parts.
You may combine a declaration with the assignment of an initial value. If you do not initialise a variable, you cannot rely on it containing any particular value before you use it.
At this stage, we will insist that you put #define lines before the main() line, and variable declarations after it, before any executable program code.
The basic variable types in C are given below, together with their sizes in bytes on some implementations.
type | PDP | 68000 | VAXVMS |
char | 1 | 1 | 1 |
int | 2 | 4 | 4 |
long int | 4 | 8 | 4 |
short int | 2 | 2 | 2 |
float | 4 | 4 | 4 |
double | 8 | 8 | 8 |
A denotation is a representation of a particular value. Typical denotations for constants of various types are as follows.
25 -15 +99 0177 /*leading zero denotes octal */ 0XFF /*leading zero and X denotes hexadecimal */
1999L 0L
'a' 'Z' '+' '.' ' ' '\t' /* tab */ '\n' /* newline */ '\a' /* alert = bleep */ '\177' /* ASCII octal code */ '\0' '\\' /* backslash */ '\'' /* prime */
111.222 1.2345E6 -1E6 -1E-6
The latter denotations represents the values -1234500, 1000000 and -0.000001 respectively.
Comments are from "/*" to the next "*/".
/* ... comment ... */ /* This is a long comment */ float velocity; /* velocity in kph */
For long comments, some people use
/* * This is * a long * comment */
to look pretty.
Lay out your program carefully, with plenty of white space, indented as appropriate. I don't mind which convention you use, but be consistent! As programs become larger, program layout becomes more and more important.
We use printf for program output, and a similar function scanf for input to the program.
/* Program written by EF */ /* October 1991 */ main() { int number; float reciprocal; printf( "Type a number: " ); /* "scanf( "%d", ...)" expects a value valid */ /* for the given type, %d for integer, %f for floating. */ scanf( "%d", &number ); /* If you use "1" instead of "1.0" */ /* it would be an integer result. */ reciprocal = 1.0 / number; printf( "Reciprocal of %d is %f\n", number, reciprocal ); } /* end main */
Note that the prompt does not have a newline character, and has a space before the final closing quote.
The scanf has a format string as its first parameter, and a variable name preceded by an ampersand (&) sign as its second argument. The ampersand sign is essential, its omission may cause strange failures with messages such as
Bus error - core dumped
which leaves a (possibly huge) file called core in your directory. You should remove this with the UNIX command
rm core
This second program reads two values.
/* Program written by EF */ /* Calculate the area of a rectangle */ main() { /* Declare three float variables */ float length, breadth, area; printf( "Type length and breadth: " ); /* Read two values */ scanf( "%f", &length ); scanf( "%f", &breadth ); /* Compute area */ area = length * breadth; /* Print results */ printf( "Length %f, breadth %f, \ area is %f\n", length, breadth, area ); } /* end main */
The escaped end-of-line causes the end-of-line to be ignored.
You could avoid the variable "area" all together, and print the results with
printf( "Area is %f\n", length * breadth );
but this is not good practice.
It is good practice at this stage of your course to print out all of the values you have read in, so that the output gives a complete picture of what has been calculated.
You may loose a few marks on the dynamic testing if you do not do this. You will certainly loose marks if you do not put explanatory text into your output.
You can now write simple programs to read in some values, evaluate a formula, and print the result.
You can use formatted output to print output more nicely. Above we used "%d" for decimal printing, and "%f" for floating. You can also use:
%o /* for octal */ %x /* for hexadecimal */ %c /* for a single character */ %3d /* decimal, allow for 3 digits */ %03d /* ditto, but zero fill on left */ %-5d /* allow 5 but left justify */ %10.3f /* float, width 10, 3 DPs */ %s /* string, see later */
printf( "hours %d\nmins %d\nsecs %d\n", h, m, s ); printf( "time %2d:%02d:%02d", h, m, s );
The last statement would print in the format
time 15:02:05 time 5:05:59
We will now have a thorough tour of all of the available operators and their significance.
The simple arithmetic operators you would expect to see are
+ - * /
representing addition, subtraction, multiplication and division, respectively. As everywhere in C, types are important here. Between two "int"s the result is an "int", otherwise (between two "float"s, or between an "int" and a "float") the result is a "float". This is fairly obvious for addition, subtraction and multiplication.
Beware of "/" between integers; the result is an "int", which may not be what you expected. It gives the quotient as an integer rounded down towards zero if the result is positive. If you type
float x = 1 / 7; float y = 6 / 7;
you may not get the expected result; both x and y will be set to zero! If the result would be negative, the language does not define exactly what will happen, for example whether "10 / -7" is -1 (which you would get if you rounded towards zero) or -2 (if you rounded down).
The operator "%" between integers gives the remainder when the first is divided by the second. Thus "72 % 10" evaluates to 2, and "30 % 13" evaluates to 4. If either of the operands are negative there are ambiguities similar to those in division. The value of "10 % -7" might be -3 or +4. The official definition says that the value of the expression
( a / b ) * b + a % b
must always equal the value of "a."
There is no operator in C for exponentiation (for raising any number to a given power).
In the example programs given from now on, we may give only the text within the "{" and "}" of the main program. To run the program, you would have to add the lines up to main(){ and the final "}". Further, we may not include the niceties of prompts and input/output if we are really demonstrating other types of statement.
float fahrenheit, celsius; printf( "Type Fahrenheit temperature: " ); scanf( "%f", &fahrenheit ); celsius = (fahrenheit - 32) * 5 / 9; printf( "Celsius is %f\n", celsius );
#define spokes_per_wheel 44 int spokes, wheels, left_over; printf( "How many spokes? " ); scanf( "%d", &spokes ); wheels = spokes / spokes_per_wheel; left_over = spokes % spokes_per_wheel;
integer won, drawn, lost, points; scanf( "%d", &won ); scanf( "%d", &drawn ); scanf( "%d", &lost ); points = won * 3 + drawn;
There are many other operators besides those used for arithmetic evaluations. We will need these in the next chapter for use in "if ... then" constructs.
a > b /* greater than */ a < b /* less than */ a >= b /* g.t. or equal to */ a <= b /* l.t. or equal to */ a == b /* equals */ a != b /* not equals */ /* watch for the double "=" sign */
These can be between (almost) any types of object. The result delivered is zero for FALSE, one for TRUE. In appropriate places in C generally, zero is always interpreted as meaning FALSE, while non-zero is interpreted as TRUE.
Note that testing for equality "==" between floats or doubles is not sensible, because of the possibility of rounding errors. The compiler will permit you to do it, but it is considered bad practice. You should instead look for a small absolute difference between the two values.
fahrenheit > 32
spokes >= 2 * spokes_per_wheel
For combining the results of comparisons, we need general logical operators. In fact, we are not limited to combining the results of comparisons; we can combine any values, and any zero value will be interpreted as FALSE, and non-zero value as TRUE. We use "&&" for the logical "and" and "||" for "or".
int number; /* set "number" to some value ... */ number >= 0 && number < 10 /* "&&" is "and", */ /* so true if the number is from 0 to 9 inclusive */ /* false otherwise */ number < 0 || number >= 10 /* "||" is "or" (inclusive or) */ /* one or the other or both */ /* so true if the number is outside the range 0 to 9 */ ! ( number >= 0 && number < 10) /* "!" is logical "negation" */ /* this expression has the same value as the previous one */ /* a monadic operator */
5 < celsius && celsius < 15
spokes >= 2 * spokes_per_wheel && spokes % spokes_per_wheel < 10
These are unique to C and C++. Their real significance and use will become apparent later.
++i /* increment, deliver new value */ i++ /* increment, deliver old value */ --i /* decrement, deliver new value */ i-- /* decrement, deliver old value */
The word "increment" means "increment by a suitable value". Compare
i = 0; printf( "%d\n", i++ );
which prints the value 0, with
i = 0; printf( "%d\n", ++i );
which prints the value 1. In both cases, "i" takes the value 1 after the instruction.
Note that
j = p + i++;
is equivalent to
j = p + i; i = i + 1;
where
j = p + ++i;
or
j = p + (++i);
is equivalent to
i = i + 1; j = p + i;
You will often see the free-standing increment
i++; /* increment i, could be ++i */
to add 1 to i, used instead of writing
i = i + 1;
You will see examples of these operators later.
The values being assigned will be cast or coerced (their types will be changed and their values converted between types) as required. Examples of assignments include the following.
int i, j; char c, lc; i = ( j + 2 ) / 3; /* integer divide */ c = 'X'; j = c - 'A'; /* j is ordinal of char c */ lc = c - 'A' + 'a'; /* lc is lower case char */ /* for upper case char c */ i += 3; /* "plus-and-becomes" */ i -= j; /* "minus-and-becomes */ i *= 10; /* "times-and-becomes" */
Assignment is an operator, and delivers as its result the value just assigned. Thus
i = ( c = getchar() ) - 'A';
is equivalent to
c = getchar(); i = c - 'A';
To assign the same value to several variables use
i = j = k = 0;
Note that in initialising declarations, you MUST still write in full
int i = 0, j = 0, k = 0;
These operations could all be performed as two separate instructions; express them as two statements if you feel happier that way.
spokes_used = ( wheels = spokes / spokes_per_wheel ) * spokes_per_wheel;
Beware of using "=" instead of "==", such as writing accidentally
if ( i = j ) ....
with a single equals sign. This copies the value in "j" into "i", and delivers this value, which will then be interpreted as TRUE if is is non-zero.
The real significance and use of this operator will appear later. An expression consisting of statements separated by commas, as in
statement1, statement2, statement3
causes each statement to be executed in turn; the result finally delivered is that delivered by the last statement. Thus you can write
i = ( c = getchar(), j = i - 'A' ) + k;
or
if ( c = getchar(), i = c - '0', c != '\n' ) { ...
The effect of this last statement is exactly the same as if you had typed
c = getchar(); i = c - '0'; if ( c != '\n' ) { ...
It is necessary to define carefully the meaning of such expressions as
a + b * c
to define the effect as either
(a + b ) * c
or
a + ( b * c )
All operators have a priority, and high priority operators are evaluated before lower priority ones. Operators of the same priority are evaluated from left to right, so that
a - b - c
is evaluated as
( a - b ) - c
as you would expect.
Exact details are in any book on C. There are many operators here that we have not yet met, but they are all entered here for completeness.
If you are ever in doubt, use extra parentheses to ensure the correct order of evaluation, and (equally important) to ensure the easy readability of the program.
From high priority to low priority the order is
( ) [ ] -> . ! ~ - * & sizeof cast ++ -- (these are right->left) * / % + - < <= >= > == != & ^ | && || ?: (right->left) = += -= (right->left) , (comma)
Thus
a < 10 && 2 * b < c
is interpreted as
( a < 10 ) && ( ( 2 * b ) < c )
and
a = b = c = spokes / spokes_per_wheel + spares;
as
a = ( b = ( c = ( spokes / spokes_per_wheel ) + spares ) );
The exercises in this unit are marked partly on dynamic correctness and partly on Typographic layout. For dynamic correctness we run the program against samples of test data, and look in the output for the correct results; we also check for words in your output, so that you should print for example
123 degrees Fahrenheit corresponds to 65.34 degrees Celsuis
rather than merely print a few numbers. In general you should also at this stage print out the values you have just read in, so that the output is complete in itself.
Your program itself should be easily "readable" to others. The "typographic" marker checks that your program is laid out as suggested above and in lectures; that it is indented correctly, that it contains a reasonable amount of "white space" (blank lines and spaces within lines), that you have a reasonable number of comments in the program, that the identifiers you have chosen are meaningful, and that you have done all the other things mentioned above to make a program readable to others.
The exact typographic marking metrics vary from exercise to exercise, but as a rough guide the typographic mark is formed as follows. The marking looks for correct indentation, approximately 20 to 30 characters per line, 30 to 60% comment in the program with comments after every "}", 20 to 50% blank lines, 20 to 30% white space in a line, and an average identifier length between 5 and 10.
Copyright Eric Foxley 1996
Notes converted from troff to HTML by an Eric Foxley shell script, email errors to me!