Table of Contents
Table of Contents
C was created in the 1970's at Bell Labs. The idea at that time was to have a computer language which would be powerful and simple enough to implement UNIX.
C is a compiled language (we compile the program before we run it). As a consequence it runs faster than an interpreted program, e.g, S-Plus
Program code tends to be reasonably concise.
Much code has already been written for use in C (the UNIX system libraries for example)
Be aware, there are two "dialects" of C out there: the ANSI (American National Standards Institute) version that is almost exclusively used today, and the "K and R" (Kernighan and Ritchie) version which was popular in the early days of C but has long since fallen out of favor. Within these dialects there are subdialects (for example, ANSI 1989) corresponding to updated standards, etc.
In what follows we will stick to ANSI C. For the most part we will use the 1989 version and will avoid features that were introduced later (for example, in the 1999 ANSI C Standard).
We will assume you are using gcc as your C compiler.
Table of Contents
In a text editor (such as emacs) type in the following program and save it as "nothing.c"
#include <stdio.h>
int main ()
{
return 0;
}
We now compile the program. From the command line type the following:
gcc -o nothing nothing.c
(gcc stands for GNU C compiler.)
This command says to compile the source code nothing.c and call the resulting program nothing.
gcc can take many options, many of which are not useful to us at this time. However if you have a program which takes a long time to run, try using
gcc -O2 file.cas this will cause gcc to spend more time optimising your code for speed.
To run the program, at the command prompt type:
nothingWhat happens? As long as you made no typing errors, absolutely nothing!
Let's add a line which says prints some text. Alter the program as follows to obtain the classic "Hello World!" program.
#include <stdio.h>
int main ()
{
printf("Hello World!\n");
return 0;
}
Compile the program as above. When we run the program by typing
nothing at the command prompt we obtain:
Hello World!Not much, but it's a start!
In C, comments start with the symbols /* and end with the symbols */. All text inside a comment is ignored by the compiler. We can place a comment anywhere in a program as long as it does not separate commands. We can also spread comments over different lines.
Here is the above program with some comments.
/* nothing.c - By Peter Craigmile - Dec 1999
* This program displays the words "Hello World!"
* followed by a newline
*/
#include <stdio.h> /* let's include a header file */
/* if you want a program that can run you need a */
/* main function */
int main ()
{
printf("Hello World!\n");
return 0; /* a zero means the program ran OK! */
}
Table of Contents
All runnable programs must have a main function. Typically this function looks like this:
int main(void) {
<stuff>
return <something>;
}
The int before main tells us the function returns an integer value. The ( ) after main tells us that the function has no arguments.
#include <stdio.h>tells the compiler to include the header file called stdio.h. This file contains definitions that we will need such as the printf function.
Variable names must start with an alphabetic character, but subsequent characters can be alphabetic, digits or an underscore _. Variable names are case sensitive e.g. product, Product and prODUct are all different variable names.
In C, all variables must be declared at the beginning of a block (i.e., after the opening brace of the function, and before any statements). A declaration statement specifies the variable's type and name. Variables of the same type may be declared on the same line; simply place a comma between names.
int product; int x,y;
In C we use the = symbol for assignment. Thus,
x=2;means x is assigned the integer value of 2.
We can also initialise variables in the declaration statement: e.g.
int product, x=2, y=4;The initialization values used here must be constants (not other variables). Hence, it is correct to say
int product=2, x=2*4+3;but it is incorrect to say
int product x=2, y=x;
The basic types in C are int, long, float, double, char, and void. We will discuss each of these types below.
Single characters in C have type char. To define character constants in C we surround the characters by single quotes ', e.g.,
char yes='y'; char newline='\n';Note that \n is considered a single character.
Integers are defined with the int keyword.
...
{
int x;
int y;
int product;
...
Integers are typically limited in size; the exact size depends on the machine. On 32-bit machines, an int variable is (usually) four bytes long. Thus the allowable values of an int variable range from -(2^31 - 1) = 16535 to 2^31 - 1 = 16535.
For larger integers, use the long modifier:
long int x = 0L;(The "L" after the 0 is used to denote a long integer constant. It is normally safe to omit the "L", as the compiler will do the conversion for you.)
On 32-bit machines this will (usually) instruct the compiler to reserve eight bytes for x. Hence the effective range of a long int is -(2^63 - 1) to 2^63 - 1.
For real numbers we have two types—float (floating point numbers) and double (double precision floating point numbers). The difference between the two is the amount of space allocated for each: on a 32-bit machine a float is typically four bytes wide, and a double is eight bytes wide. The range of the real number types is different from that of the integer types, even though they might use the same amount of space: real numbers are stored in an exponential format, so they can store smaller/larger numbers. The trade-off, of course, is precision: real numbers are not exact, and computations with them are subject to rounding errors.
floats and doubles are declared just like their integer counterparts.
float x = 3.1, y = 0.126; double a, b = -1.0e4;declares x and y as floats and a and b as doubles. The -1.0e4 notation is exponential notation; it expands to -1 x 10^4.
Real arithmetic is approximate—can get rounding and truncation errors floating points and integers are two distinct types, e.g., 3 denotes the integer value 3; 3.0 denotes the floating point value 3.0;
To convert legally between types put the type you wish to convert to in brackets before the variable: e.g.
#include <stdio.h>
int main ()
{
float x=2.3,y;
int n=2;
y = x/(float)n;
printf("The float %f divided by the integer %d
equals the float %f",x,n,y);
return 0;
}
All your favorite arithmetic operators are available in C. The following table presents the basic arithmetic operators.
| - | Subtraction (also unary minus) |
| + | Addition |
| * | Multiplication |
| / | Division |
These operators can be applied to any built-in data type. (Though there are some special considerations for integers and character data; see below.) In addition to these operators, parenthesis ( ) are used for grouping operations.
In addition, there are some special operators that represent common programming idioms. In the following table, i and j are integers.
| ++ | Increment | i++ and ++i are equivalent to i = i + 1. |
| -- | Decrement | i-- and --i are equivalent to i = i - 1. |
| += | Addition with assignment | i+=j is equivalent to i = i + j |
| -= | Subtraction with assignment | i-=j is equivalent to i = i - j |
| *= | Multiplication with assignment | i*=j is equivalent to i = i * j |
| /= | Division with assignment | i/=j is equivalent to i = i / j |
| % | Modulus | i%j gives the reminader when i is divided by j. |
Notice the increment and decrement operators have two forms: a postfix form, i++, and a prefix form, ++i. The two forms accomplish the same task, but they differ in when they accomplish it.
Suppose i and j are integers. Compare the following code fragments. (They show the use of increment operator, but the behavior is the same for the decrement operator.)
i = 1; j = ++i;
i = 1; j = i++;In the first fragment, i is incremented to 2, then that value is assigned to j. Thus the result is i = 2, j = 2. In the second fragment, the value of i is assigned to j, then i is incremented to 2. Thus the result is i = 2, j = 1.
It is important to be mindful of this difference when using the increment and decrement operators in programming.
Since the result of arithmetic operations with integers in C must be an integer, integer arithmetic in C has a few "quirks". The result of integer division must be an integer, so any fractional part is discarded. Thus
int i = 5/2;sets i to 2.
The sizeof function takes a type name as an argument and returns how much memory storage is required by that type. For example,
int x; x = sizeof(int)sets x equal to the number of bytes required to store an int on the computer.
The value returned by sizeof technically has type size_t. For all practical purposes, however, you can treat this value as an unsigned int.
sizeof is useful for making code portable to other platforms.
Table of Contents
What do we do when we wish to repeat statments over and over again? The C language offers three primary constructs for repeating procedures: the while construct, the do...while statement, and the for loop.
syntax:
while (<expression>)
{
<statements>
}
(*)
Semantics:
evaluate <expression>
if "true" (non-zero), execute <statements> and goto (1)
Otherwise goto (*)
Example—print all the odd numbers from 1 to 50:
#include <stdio.h>
int main ()
{
int n=1;
while (n<=50)
{
if ((n2)==1)
printf("%d ",n);
n = n + 1;
}
printf("\n");
return 0;
}
syntax:
do
{
<statements>
}
while ( <expression> )
(*)
Semantics:
execute <statements>
evaluate the <expression>
if ``true'' (non-zero), go to (1) otherwise continue on to (*)
Exercise—rewrite the example for while as do-while.
Syntax:
for ( <expression1> ; <expression2> ; <expression3> )
{
<statements>
}
(*)
Semantics:
evaluate <expression1>
evaluate <expression2>. If result is ``true''
(non-zero), go to (*)
execute <statements>
evaluate <expression3>
go to (2)
Example—do the same as the while example:
#include <stdio.h>
int main ()
{
int n;
for (n=1; n<=50; n=n+1)
{
if ((n2)==1)
printf("%d ",n);
}
printf("\n");
return 0;
}
Exercise: Can you think of a more compact way of writing this program?
Table of Contents
Pointers are normally used when we wish to:
Refer to objects bigger than the basic types within functions;
use dynamic variables—variables which can be created on the fly, of any given size.
The essential idea with pointers is that every variable defined in C has an address—a location in memory where that variable is stored.
To obtain the address of any variable we append the & operator before the variable name, e.g.,
float x; <some type>addr_x; addr_x = &x;But what is the type of addr_x? This is where we need a pointer!
The type of addr_x is float *:
float x; float *addr_x; addr_x = &x;
The asterisk here denotes a pointer. We read the above definition as "define addr_x as a pointer to a float". In general:
<type name> *<variable name>;defines a pointer to a variable of type <type name>
Why is this useful? Is allows us to change variables from afar by only knowing the address. In particular, we can now alter more than one value at a time in a function.
To access to the actual variable pointed to by a pointer we use the * operator. This is called dereferencing the pointer.
float x=2, z; float *addr_x; addr_x = &x; z = *addr_x;z now has the same value as x.
Look at a this example function:
void swap_floats (float x, float y)
{
float temp;
temp = x;
x = y;
y = temp;
}
Why does this function not work?
Parameters in functions are constants; you cannot alter their value. The way to get round this is to pass the address of the variable you wish to change.
To make this function work we pass two pointers to floats
void swap_floats (float *x, float *y)
{
float temp;
temp = *x;
*x = *y;
*y = temp;
}
Note that the values of x and y never change—only the
variables pointed to by these variables
The void * type means a pointer to anything. It is typically used for dynamic memory allocation. We cannot use variable of this type by default; it must be cast to a usuable type.
We can use the operators +, -, ++, --, += and -= with pointers. A pointer is NOT an integer—it is an address in memory. Thus, pointer arithmetic differs from integer arithmetic. Some key points:
For every one unit we add or stubract, the compiler adds sizeof(<type pointed to>) units to the address of the pointer.
Table of Contents
Arrays are a way of implementing fixed length vectors of a given type. They are the "natural" representations of vectors, matrices, etc. in C.
Syntax for definition:
<type name> <variable name> [<length>]Here <length> must be a constant (at compile time) integer value—one cannot use another variable to specify the length. This is due to how this type of array is set up in memory: the compiler will allocate a contiguous block of memory for the variable, so it has to know the size of the variable at compile time.
Examples:
int nums[5]; float x[128];defines an array of 5 integers called nums and an array of 128 floats called x.
As you may have guessed, we use square brackets [ and ] to specify indices into arrays. By C convention, array elements are indexed starting at 0. Hence, the first element of nums is nums[0], and the last element of nums is nums[4]. Be very mindful of this when programming: attempting to access nums[5] will have unpredictable results, as most compilers will happily add the size of an int to the address of the last element of nums. When the code actually runs you may get complete garbage (whatever random data is in that memory location) or you may get an access violation (on Windows)/segmentation fault (on Unix). The latter event occurs when your code does not have permission to read from the memory location.
Example—initialise a vector of floats, x, to ( 0,2,1,4.3,5.5 ) and then let sum = sum_{k=0}^4 x_k$:
float x[5],sum; x[0] = 0.0; x[1] = 2.0; x[2] = 1.0; x[3] = 4.3; x[4] = 5.6; sum = x[0]+x[1]+x[2]+x[3]+x[4]+x[5];The index (the number in square brackets) can be any integer expression. For example, here is another way to do the above.
float x[5],sum; int i; x[0] = 0.0; x[1] = 2.0; x[2] = 1.0; x[3] = 4.3; x[4] = 5.6; sum = 0; for (i=0; i<5; i++) sum += x[i];
You can also initialize arrays in their declaration.
<type name> <variable name> [<length>]
= { <1st elem> , <2nd elem> , ... }
For example,
float x[5] = {0.0,2.0,1.0,4.3,5.6};
A handy shortcut: we do not need to give a length if we are
setting the elements.
float x[] = {0.0,2.0,1.0,4.3,5.6};
Make sure you know the length of the vector
yourself however—there is no determine this later on, i.e.,
there is no length command.
As we touched on in the section, major errors can occur in C when we do not keep track of the element numbers. C programs can crash or do weird things if you write to elements outside the range of the variable.
Exercise—read an array of 10 floats and return the mean of the elements of the array.
To define multi-dimensional arrays just add extra brackets.
float x[5][6];defines a 5 x 6 array of floats called x.
Using multi-dimensional arrays is just as easy as using one-dimensional arrays.
float x[5][5];
int i, j;
for ( i = 0 ; i < 5 ; i++ ) {
for ( j = 0 ; j < 5 ; j++ ) {
if ( i==j )
x[i][j] = 1;
else
x[i][j] = 0;
}
}
Table of Contents
We know how to declare an array of a fixed size.
double x[20];What do we do if we the size of the array cannot be fixed ahead of time?
Fortunately, C provides a mechanism for requesting an amount of space from the operating system that does not require you to know the size ahead of time. The main routine for this is malloc (memory allocation); it is part of the Standard C Library (so you need to include the header file "stdlib.h" in your code to use it).
malloc is meant to be as general as possible—imagine how difficult it would be to have a separate allocation for function for each type! As such, malloc takes an unsigned integer (giving the number of bytes to allocate) as an argument, and has return type void *.
<pointer variable> = (<type name> *) malloc (<memory size>)<pointer variable> denotes the name of the variable which will hold the address of the start of the newly allocated memory block. It must be of type "pointer to <type name>".
Note also that we cast the result of malloc to the desired type. This is not strictly necessary, but it is very good programming practice. (In addition, some compilers might warn you about a type mismatch if you don't cast.) (The C FAQ gives an argument in support of not casting, but their argument mostly applies to much older compilers.)
The <memory size> is the number of bytes to allocate. Typically (for an array, say) this is the number of entries times the size of the type. The sizeof is very useful here for ensuring portability across platforms.
In the example below, we allocate an array of 10 doubles.
double *x; x = (double *) malloc(sizeof(double)*10);
Occasionally, the operating system will not have enough free memory to fulfill your request. In such cases, malloc will return the special value NULL. It is a very good idea to always check the return value of malloc before using it. Attempting to dereference the NULL value results in a segmentation fault or access violation.
When you finish with the storage you requested with malloc, you need to tell the operating system that you don't need that memory any more. This allows the operating system to reallocate that memory to other programs (possibly your own). If you don't release memory in this fashion (i.e., you create a memory leak, the operating system can run out of memory, leading to failed malloc requests, slow system performance, and crashes.
To free up a block of memory allocated with malloc use the free function.
free(<pointer variable>);The free function takes a pointer to the beginning of the space. For example,
double *x;
x = (double *) malloc(sizeof(double)*10);
if ( x == NULL ) {
printf("Allocation error.\n");
exit(EXIT_FAILURE);
}
/* do something with x .... */
free(x);
As stated above, you should always free up memory when you are done with it. Do not assume the operating system will do this for you. It is a good idea to add a free statement at the same time as the malloc statement: that way you will be less likely to create leaks.
An additional caveat: it is a serious error to call free on a pointer that was not allocated with malloc (or one of its cousins) or a pointer that has already bene free'd. Furthermore, once a variable has been free'd, it is generally not a good idea to try to use it again. (See this entry in the C FAQ.) It is safe, however, to call free on a NULL pointer.
Dynamically allocating space for a multidimensional array can be tricky. There are basically two ways to do it.
In this option, we treat the multidimensional array as a really long 1-D array. We then use fancy indexing to access the elements of the array in a multidimensional fashion. A macro is very helpful for this method.
Example. Allocate space for a 2-D array as a 1-D array and then use arithmetic on the indexes to move about the space.
/* put this at the top of the code */
#define NROWS 10
#define NCOLS 10
#define MAT_ELEM(row,col) (((row)*(NCOLS))+(col))
double *x;
/* allocate a 10x10 array */
x = (double *) malloc(sizeof(double)*NCOLS*NROWS);
if ( x == NULL ) {
printf("Allocation error.\n");
exit(EXIT_FAILURE);
}
/* set x[2][2] = 0.0 */
x[MAT_ELEM(2,2)] = 0.0;
/* set x[0][2] = 0.0 */
x[MAT_ELEM(0,2)] = 0.0;
/*
Be a good geek!
Don't create a memory leak!
*/
free(x);
Note for the MAT_ELEM define we need to supply how many
columns there are for the arithmetic to work.
In this option we allocate an array of pointers to pointers to pointers etc., then allocate each lower dimensional array. This method is more complicated to set up than Method 1, but is often easier to work with in your code, as you can use repeated subscripts [ ][ ][ ]...[ ] for indexing.
Let's redo the above example using this method.
/* put this at the top of the code */
#define NROWS 10
#define NCOLS 10
double **x;
int i, j;
int memerr = 0; /* sentinel for memory errors */
x = (double **) malloc(sizeof(double *)*NCOLS);
if ( x == NULL ) {
printf("Allocation error.\n");
exit(EXIT_FAILURE);
}
for ( i = 0; i < NCOLS; i++ ) {
x[i] = (double *) malloc(sizeof(double)*NROWS);
if ( x[i] == NULL ) {
printf("Allocation error.\n")
memerr = 1;
break;
}
}
if ( memerr == 1 ) {
/*
free subarrays starting from where the allocation
error occurred.
*/
for ( j = i ; j >= 0 ; j-- ) {
free(x[i]);
}
free(x);
exit(EXIT_FAILURE);
}
/* set x[2][2] = 0.0 */
x[2][2] = 0.0;
/* set x[0][2] = 0.0 */
x[0][2] = 0.0;
/*
Be a good geek!
Don't create a memory leak!
*/
for ( i = 0 ; i < NCOLS; i++ ) {
free(x[i]);
}
free(x);
Notice the methodology used: we declared a pointer to a pointer, then allocated
an array of pointers. After checking that the first allocation was successful,
we allocate each column of the array. (We chose any array of columns, but an array
of rows would also work.) If allocation fails, we set a flag variable
memerr, then clean up before exiting. If all goes well, we can
access the elements of x using double brackets. This works because
x[i][j] is equivalent to (x[i])[j]. Finally,
we clean up as usual, making sure to clean up each subelement before we clean up the
main element.
Table of Contents
As we discussed in a previous chapter, C provides the char data type for representing single characters. Most communication, however, is done using words and phrases, so we'll usually want to deal with strings.
Strings in C are just arrays of characters—almost. A valid string is null-terminated, i.e., its last element is the special character '\0'. This character acts as a marker indicating the end of the string.
String constants in C are enclosed in double quotes, e.g., "Hello World\n". In memory this constant looks like this:
| H | e | l | l | o | (space) | W | o | r | l | d | \n | \0 |
char hello[13] = "Hello World\n";Alternatively, you could let C do the counting for you.
char hello[] = "Hello World\n";
Here are some useful string functions
#include <string.h> size_t strlen ( const char *s ) char *strcat( char *s1, const char *s2); int strcmp( const char *s1, const char *s2); char *strcpy( char *s1, const char *s2); char *strdup( const char *s1);To use these functions we include the file <string.h>.
strlen returns the length of the string pointed to by s, excluding the null terminating character. (Just consider the size_t returned value as an unsigned integer.)
strcat appends a copy of the string pointed to by s2 (including the terminating null \0 character) to the end of the string pointed to by s1. The ending null character of s1 is overwritten. It returns the value of s1 if successful or NULL otherwise.
strcmp compares the string pointed to by s1 to the string pointed to by s2. It returns a value of 0 if the strings are equal, non-zero otherwise. (In fact, the sign of the return value can be used to order the two strings.)
strcpy function copies the string pointed to by s2 (including the terminating null character) to the location pointed to by s1. It returns the value of s1 if successful or NULL otherwise
strdup returns a pointer to a new string that is an exact duplicate of the string pointed to by s1. The malloc function is used to allocate space for the new string.
For strcat and strcpy there must be enough memory to store the new string in s1.
Table of Contents
An essential idea in programming is that of modularity and code reuse. One way to encapsulate these ideas is to use functions.
The syntax for defining a function in C is as follows.
<return type> <function name> ( <parameter list> )
{
<variable declarations>
<statements>
<return statement>
}
For example, the following function which calculates the
maximum value of two floating point arguments x and y.
float max_float (float x, float y)
{
if (x>y)
return x;
else
return y;
}
int main ()
{
float x=2.3,y=4,max;
max = max_float(x,y);
printf("The maximum of %f and %f is %f.\n",x,y,max);
return 0;
}
Functions must be declared before any other statements that use them. This allows the compiler to check that functions are being used correctly, i.e., correct arguments, correct return value, etc.
You can get around this problem by using function prototypes. A function prototype is not a full function definition, but it tells the compiler enough information to do its job properly.
Here is the above example again, this time with a prototype.
#include <stdio.h>
float max_float (float x, float y);
int main ()
{
float x=2.3,y=4,max;
max = max_float(x,y);
printf("The maximum of %f and %f is %f.\n",x,y,max);
return 0;
}
float max_float (float x, float y)
{
if (x>y)
return x;
else
return y;
}
Note the semi-colon after the prototype. The full ANSI C
syntax for prototypes is
<return type> <function name> ( <parameter list> );The function must also be fully defined elsewhere...so don't forget about that part!
The prototype allowed us to put the actual definition of the max_float later in the file. We could also put the definition in another file. This is one of the best uses of prototypes: splitting up large programs into lots of smaller, easier-to-manage files.
The last statement of a function returns a value (possibly empty) specifying the result of the function. The keyword return is used both to indicate this value and signal the end of the subroutine.
return <expression>;
The return value of a function can be of type void, of one of the basic types, or of a pointer type. A void type means that the function does not return a value. The appropriate return statement is empty:
return;It is legal, in this case, to omit the return keyword, but we do not recommend this.
The type of <expression> must match the <return type> declared in the function's prototype.
There are two ways to pass arguments to functions: by value and by reference. In the pass by value method, the function gets a copy of the inputs; any changes made by the function do not affect the original values. In the above max_float function both x and y are passed by value.
In general, C uses the pass by value method for function arguments. This is sometimes inconvenient, particularly when you want to work with a large structure or array. C provides the call by reference method for these situations.
To use call by reference, all you have to do is use pointers to variables instead of the variables themselves. Here is a variant of the max_float program that uses call by reference.
void max_float_cr (float *x, float *y, float *max)
{
if (*x > *y)
max = x;
else
max = y;
return;
}
To call this function you would pass in the addresses of
the variables instead of the variables themselves.
float x = 5.4; float y = 3.6; float max; max_float_cr(&x, &y, &max);
Variables defined in the body of a function are local to that function, i.e., they cannot be accessed from other functions. Moreover, such variables are erased when the function exits, so their values are not preserved between calls to the function.
This behavior can of course by modified; see the keyword static.
Table of Contents
Structuresallow for data encapsulation; they allow us to define new types which are an collection of types we already have. These new types can provide a more "natural" representation for complex data.
The best way of defining structures is to use the typedefand structkeywords.
typedef struct
{
<variable declarations go here>
}
<struct name>;
<struct name>cannot match any built-in type
name.
The variables defined in the structure are called its members. We use the . and -> operators (depending on the context) to refer structures members.
For example here is a type to handle complex numbers.
typedef struct
{
double real,imag;
}
complex_double;
This defines a new data type called complex_double that contains two double
precision members, real and imag.
After we define the structure we can use it like another type.
void main ()
{
complex_double x;
x.real = 0.0; /* set the real element to 0.0 */
x.imag = 0.0; /* set the imag element to 0.0 */
}
This example illustrates the use of the . operator. When we have a structure
variable, this operator allows us to access the members of the structure.
On the other hand, suppose we instead had a pointer to a structure variable. In the example above such a pointer can be obtained simply by using the & operator on x. For clarity's sake, let's define a separate variable y that points to x.
complex_double* y = &x;Now to access the members of x using y, we could dereference y, then use the . operator, e.g., (*y).real. (Note the parentheses: . has higher precedence than *, so without them the program would try to access the nonexistent member of y, then dereference it. The results of such an operation are unpredictable! A decent compiler will warn you of the mistake, thankfully.) This situation occurs frequently, enough, however that C has an idiom for it: the -> operator. Thus, y->real is functionally equivalent to (*y).real.
When you want to pass and/or return a structure to and/or from a functions it's better to use pointers. (Otherwise, large structures would need to be passed and/or returned by value, using up lots of memory in the process.) Here is a small program that creates a complex_double variable representing zero.
#include <stdlib.h>
void complex_zero (complex_double *x)
{
x->real = 0.0;
x->imag = 0.0;
return;
}
void main ()
{
complex_double *x;
if ( x = malloc(sizeof(complex_double)) == NULL ) {
printf("Allocation error.\n")
exit(EXIT_FAILURE);
}
complex_zero(&x);
free(x);
}
Table of Contents
There are two types of output in C: console output (i.e., output that shows up on your screen) and file output. (Technically, the console is a special type of file. It's special enough, however, to treat it separately from the general case.)
Syntax:
#include <stdio.h> printf( <format string>, <arguments> )<format string> is a string which may contain placeholders for any variables we want to display. These variable names follow in the argument list, separated by commas.
Let us begin with the integer placeholder holder %d. Example:
#include <stdio.h>
int main ()
{
printf("%d times %d = %d\n",2,4,2*4);
return 0;
}
The output for this program is:
2 times 4 = 8
Other common placeholders:
| %d | signed decimal integers |
| %u | unsigned decimal integers |
| %e | real numbers in scientific notation |
| %f | real numbers in floating point form |
| %g | %e or %f, whichever is shorter |
| %o | unsigned octal |
| %x | unsigned hexadecimal |
| %c | character |
| %s | string |
| %p | pointer |
| %% | percent sign |
Field width and precision specifiers control the number of characters printed. A field width specifier should be an integer placed between the percent sign and the placeholder. This integer gives the minimum width of the output value; values shorter than this will be padded (by default with spaces; use a zero to pad with zeros). For example, %05d specifies that the integer value should be five characters wide, and integers with fewer than five digits should be printed with enough zeros added to the front to fill them out to five characters. Thus 1 would print as 00001 and 54321 prints as 54321. Values with more than five digits are left untouched by this specification.
A precision specification controls how many digits after the decimal point are printed. This specifier should be an integer, and should occur after a decimal point and before the placeholder, e.g., %.3f will print a float with three digits after the decimal point. When exponential notation is used the precision specifier controls the number of significant digits.
Precison and field width specifiers may be combined: %10.4f will print a float value in a field at least ten characters wide with four decimal places.
Precision and field width specifiers can be applied to strings; in this case the precision specifier gives the maximum width of the field. A string longer than this maximum will be truncated.
Finally, the minus sign - can be used to specify left justification instead of the default right justification. Example:
#include <stdio.h>
void main(void) {
printf("Right justified: %8d\n", 500);
printf("Left justified: %-8d\n", 500);
return;
}
Syntax:
scanf( <format string>, &<variable(s)> )<format string> is a string specifying how the input should be parsed. It should contain at least one format specifier. (The format specifiers for scanf are the same as the ones for printf.)
scanf will read a string from the console, pluck out the relevant values according to the format string, and place these values into the variables specified. It is important to remember that scanf requires the addresses of the target variables, not the variables themselves.
Here is example of the use of scanf.
#include <stdio.h>
int main ()
{
int x,y,product;
printf("Enter the value of x:");
scanf("%d",&x);
printf("\nEnter the value of y:");
scanf("%d",&y);
product = x*y;
printf("\n%d times %d = %d\n",x,y,product);
return 0;
}
In this section we'll cover how to deal with formatted output involving files. We have actually already seen two forms of this: printf, which writes to the special file stdin, and scanf, which reads from the special file stdout. As we will see, reading and writing to regular files is not much harder.
In ANSI C file access is accomplished via file pointers. The file pointer contains information about the file, such as its name and the current position in the file. The C data type FILE * is used for file pointers; it is declared in the header file <stdio.h>.
The first step to accessing a file is to declare a variable of type FILE *.
FILE *<file variable>(Traditionally the file variable is called something like "fp", for "file pointer".)
Next, we open the file for access using the fopen function. This function has the prototype
FILE *fopen(char *filename, char *mode);Here filename is the name of the file we wish to open (as a string), and mode tells the operating system what type of file it is (text vs. binary) and how we want to use it (read/write/append). The following table details the most common modes.
| r | open a text file for reading |
| w | create a text file for writing |
| a | append to a text file |
| rb | open a binary file for reading |
| wb | create a binary file for writing |
| ab | append to a binary file |
| r+ | open a text file for reading/writing |
| w+ | create a text file for reading/writing |
| a+ | append to or create a text file for reading/writing |
| r+b | open a binary file for reading/writing |
| w+b | create a binary file for reading/writing |
| a+b | append to or create a binary file for reading/writing |
Notes:
Text files are human readable. Binary files are not.)
For text files, line terminators (CR/LF on Windows, CR on UNIX, LF on Macintosh) are converted to newlines on input. On output the reverse happens.
The fopen function returns a pointer to a file if the command was successful or the value NULL if something went wrong. You should always check to see if the file opened correctly! Trying to use a NULL file pointer will usually result in a crash.
To close a file use the fclose function.
int fclose (FILE *file_var);Always close a file when you are finished with it!
Once the file has been opened, formatted I/O to a file is simple: instead of using printf and scanf, we use fprintf and fscanf.
int fprintf( FILE *file_var, char *format, ...); int fscanf( FILE *file_var, char *format, ...);The differ from their console counterparts only in the first argument, which must be a pointer to the target file.
Here is a program to read a vector of ten integers from the file "input_vector".
#include <stdio.h>
#include <stdlib.h>
#define N 10
#define INPUT_FILE "input_vector"
int main(void)
{
FILE *in;
int i;
double sum=0.0;
double x[N];
in = fopen(INPUT_FILE,"r");
if (in == NULL)
{
printf("open failed\n");
exit(EXIT_FAILURE);
}
for (i = 0 ; i < N ; i++) {
fscanf(in, "%lf", x+i);
sum += x[i];
}
fclose(in);
printf("mean of vector = %f", sum/(double)N);
return 0;
}
Given an arbitrary file, how do we know where we are in the file? The FILE * data structure contains this information, but not in a way we can easily understand. Fortunately, C provides several functions to facilitate working with this information.
For text files, the only function we really need to worry about is feof function. It tells us whether we are at the end of a file.
int feof(FILE *fp);feof returns a non-zero value if the end of file has been reached, otherwise it returns 0.
Note: a end-of-file will not be detected until after the first use of fscanf.
Example. Read doubles from the file "infile" until MAX_DOUBLES have been read or the end of file is reached. Print out all the values that are read.
#include <stdio.h>
#define MAX_DOUBLES 256
int main (void)
{
FILE *in;
double x[MAX_DOUBLES];
int count=0;
in = fopen("infile","r");
if (in != NULL)
{
fscanf(in,"%f",x+count);
while ((!feof(in)) && (count < MAX_DOUBLES))
{
printf("%f\n",x[count]);
count++;
fscanf(in,"%f",x+count);
}
fclose(in);
}
else
{
printf("Could not open file!\n");
}
return 0;
}
Formatted output using fprintf is actually buffered: data are not written to the file immediately. Rather, the operating system stores the data, and writes out blocks of data at once. (This is more efficient than writing one character at a time.)
Usually this buffering goes on behind the scenes, and should not concern you. Occasionally, though, you'll want to be certain the write has happened. (For instance, if you want to read data back from the file.) For such cases C provides the fflush command
int fflush(FILE *fp)The return value of fflush is zero, if the operation was successful; otherwise it returns EOF.
Table of Contents
Preprocessor directives are instructions to the compiler. They are not part of the C language (and can be somewhat compiler-specific) but they simplify many aspects of C programming.
Some general facts about preprocessor directives:
All preprocessor directives begin with the pound sign (#, a.k.a, the "sharp" a.k.a. the octothorpe).
They are compiled before any other C keywords.
They must appear at the start of a line.
They can only contain one command per line.
The #define preprocessor directive is used to define compiler constants or macros. These constants can be values or functions.
An example: the constant M_PI in math.h is defined as
#define M_PI 3.1415926535897932385E0The compiler will replace all occurrences of the string M_PI in your code with '3.1415926535897932385E0'. (It won't check that this value makes sense, though, so be careful!) For example, suppose we had the following code.
double x,y; y = sin(2.0 * M_PI);In the resulting compiled code this is translated to the following.
double x,y; y = sin(2.0 * 3.1415926535897932385E0);This type of behavior would be useful if we later decided we wanted to use a less accurate approximation to pi, for instance: we'd only need to make the change in one place, rather than everywhere in the code.
#define can also be used to define things that are "function-like".
#define <function name> <parameter list> <replacement><parameter list> is either empty or takes the form
( <variable name 1>, <variable name 2>,... )For example, we can define a function that adds two numbers as follows.
#define ADD(x,y) x+yWhen invoked in a program the inputs x and y will be substituted by the compiler into the expression x+y. The resulting expression will then be computed at run-time.
One caveat about macros: it's best to parenthesize arguments when you are unsure of what might be substituted in their place. For instance, suppose we had a macro for squaring a number.
#define SQR(x) x*xThis will work fine for SQR(4), but SQR(4+2) expands to 4+2*4+2, which will evaluate to 4+8+2=14, not 36. It would be safer to write the macro as
#define SQR(x) ((x)*(x))The parentheses help ensure proper evaluation of the expression.
This construct is convenient, but you should not think of it as a formal function, as it does no type checking: it will happily try to add a character and a float, for instance. Nevertheless, macros such as the one above are often useful, particular for very simple functions.
Table of Contents
There are many useful functions in the C Standard Library. Include the header file
#include <stdlib.h>to use these functions.
In a previous example there was a case in which we could not allocate memory—in such cases it is often best to write an error message, and exit the program. The exit() function will stop the program and will close any files that were open at the time. (It won't, however, free up any memory that you allocated during the program!)
#include <stdlib.h> void exit(int status);
Table of Contents
The standard C implementation provides a library of useful mathematical functions. To use the library we first need to include the requisite header file, math.h. Simply add the line
#include <math.h>to the start of our program.
Next, after writing and compiling our program, we have to link in the math library (which contains the actual code for the functions described in the math.h header file). To do this with gcc, use the "-lm" switch:
gcc -o my_math_program my_math_program.c -lm
Example 1: write a program which takes two rectangular coordinates x and y and converts them to polar notation.
#include <stdio.h>
#include <math.h>
int main ()
{
double x,y,r,theta;
printf("Enter the value of x:");
scanf("%f",&x);
printf("\nEnter the value of y:");
scanf("%f",&y);
r = (x*x)+(y*y);
theta = tan(y/x);
printf("\nx=%f and y=%f has polar representation r=%f and theta=%f.\n",x,y,r,theta);
return 0;
}
tan is a function which has the prototype:
double tan (double x);Note how it expects a double precision argument. The function which works with a float argument is called tanf. It's generally better do use double precision for mathematical calculations, so you will rarely have an occasion to use tanf. (In fact, most compilers will set up tanf to do all the calculations in double precision, then convert the result to single precision.) Also, note that when interfacing with external programs (such as S-Plus) you may have to use a specific precision for compatibility (e.g., double precision for S-Plus).
A useful subset:
double acos(double x)—Compute arc cosine of x double asin(double x)—Compute arc sine of x
double atan(double x)—Compute arc tangent of x
double atan2(double y, double x)—Compute arc tangent of y/x
double ceil(double x)—Get smallest integral value that
exceeds x
double cos(double x)—Compute cosine of angle in radians
double cosh(double x)—Compute the hyperbolic cosine of x
double exp(double x—Compute exponential of x
double fabs (double x )—Compute absolute value of x
double floor(double x)—Get largest integral value less than x
double fmod(double x, double y)—Divide x by y with integral quotient and return remainder
double frexp(double x, int *expptr)—Breaks down x into mantissa and exponent of number
double ldexp(double x, int exp)—Reconstructs x out of mantissa and exponent of two
double lgamma (double x)—Computes the logarithm of the gamma function
double log(double x)—Compute log(x)
double log10 (double x )—Compute log to the base 10 of x
double modf(double x, double *intptr)—Breaks x into fractional and integer parts
double pow (double x, double y)—Compute x raised to the power y
double sin(double x)—Compute sine of angle in radians
double sinh(double x)—Compute the hyperbolic sine of x
double sqrt(double x)—Compute the square root of x
double tan(double x)—Compute tangent of angle in radians
double tanh(double x)—Compute the hyperbolic tangent of x
Note how we could have used the function atan2 in our example!
To find other functions try the apropos shell command. To get information on Bessel functions:
madrid1_2% apropos bessel j0 (3) - Bessel functions j0f [j0] (3) - Bessel functions j0l [j0] (3) - Bessel functions j1 [j0] (3) - Bessel functions j1f [j0] (3) - Bessel functions j1l [j0] (3) - Bessel functions jn [j0] (3) - Bessel functions jnf [j0] (3) - Bessel functions jnl [j0] (3) - Bessel functions y0 [j0] (3) - Bessel functions y0f [j0] (3) - Bessel functions y0l [j0] (3) - Bessel functions y1 [j0] (3) - Bessel functions y1f [j0] (3) - Bessel functions y1l [j0] (3) - Bessel functions yn [j0] (3) - Bessel functions ynf [j0] (3) - Bessel functions ynl [j0] (3) - Bessel functions
The math.h file also defines some useful mathematical constants:
HUGE—The maximum value of a single-precision floating-point number
M_E—The base of natural logarithms (e)
M_LOG2E—The base-2 logarithm of e
M_LOG10E—The base-10 logarithm of e
M_LN2—The natural logarithm of 2
M_LN10—The natural logarithm of 10
M_PI—pi
M_PI_2—pi / 2
M_PI_4—pi / 4
M_1_PI—1 / pi
M_2_PI—2 / pi
M_2_SQRTPI—2 / sqrt(pi)
M_SQRT2—The positive square root of 2
M_SQRT1_2—The positive square root of 1/2
MAXFLOAT—The maximum value of a non-infinite single- precision floating point number
HUGE_VAL—positive infinity
Table of Contents
More preprocessor commands (#ifdef, #ifndef, #endif)
Playing about with loops: break and continue
Making lots of choices using only one variable (switch and case)
More with types—auto, static, register, unsigned, signed, union, enum, extern
Shift and logical operators, the if-then ? : operator
Jumping with goto. (My advice: never learn this!!)
Table of Contents
Using functions recursively
Abstract data types - the list, table, vector
and tree
Program design - bottom up, bottom down, hybrid methods.
Waterfall programming model.
Using Sentinels - what conditions do we need to end a loop?
Pre and Post conditions - using mathematics to rigorise our code
Algorithmic complexity - how can we efficiently write algorithms?
Program Testing - black and white box testing procedures
Lots more here ...
Table of Contents
Write a program which takes in one integer, n say, and displays all the prime numbers up to and including n.
Write a program to display your name and address formatted into separate lines.
Write a program which reads first an integer n, and then n floating point numbers. Calculate the mean and variance of these numbers.
Write a program which reads in doubles and writes the square of the number in each case. Set up the program so that it terminates when one enters a value less than -999.
Write a program which reads the coefficients (as double precision numbers) of the quadratic ax^2+bx+c, followed by a list of x's at which the quadratic is to be evaluated. Terminate the program when two successive values of x are the same.
This exercise revolves around writing functions for arrays of doubles. Write the following functions:
double sum (double x, int n)— calculates the sum of the array x, of length n.
double mean (double x, int n)— calculates the mean of the array x, of length n.
double var (double x, int n)— calculates the variance of the array x, of length n
double min (double x, int n)— calculates the minimum of the array x, of length n
double max (double x, int n)— calculates the maximum of the array x, of length n
int find (double f, double x, int n) —returns the index of the array x (of length n) which contains the value f (within an epsilon of 1E-8). If no value is found return -1
Think about how you would write a function which sorts an array of doubles of length n—can you use this to write a function which calculates the pth quantile?
Write functions to add, subtract, multiply and divide complex numbers. Also write a function to calculate the modulus of a complex number.