Computational Physics
The standard library
Comments and questions to John Rowe.
As well as the actual language itself, C also provides a
standard library of functions. We won't try to give
an exhaustive list, that's what
K&R and
Google
are for, just a few examples.
The library functions are split into groups, each with its own include
file.
Standard input: <stdio.h>
You've met these before, there are a few main classes:
Formatted input and output
Most of these come in three versions, eg
#include<stdio.h>
int main(void) {
char string[256];
printf("Hello, world\n");
fprintf(stdout, "Hello, world\n");
sprintf(string, "Hello, world\n");
return 0;
}
The second call is identical to the first since
stdout is one
of three file descripters that is automatically opened when a program
is run (the others are
stdin and
stderr which is
used for error messages).
sprintf takes a character array (string) as its first argument.
<stdio.h> is also the place where NULL is defined, so
you will need to include it on the rare
occasions you want to use NULL without any IO.
Character input and output
These just read or write one or more characters. Mixing these with
the formatted input and output routines can be tricky.
Character tests: <ctype.h>
Wnat to know if a particular character is a letter(or a number, or a
space, or..)?
<ctype.h> is your friend.
In the example below we demonstrate the use of fgets to
read in a string with spaces and putchar to print a single character.
#include<stdio.h>
#include<ctype.h>
/*
* Print out letters from the input string and
* print out all spaces as tabs
* Note use of fgets to handle spaces
*/
int main(void) {
char string[256], *p;
printf("String?\n");
fgets(string, 256, stdin);
/* Get rid of the '\n' at the end */
p = string + strlen(string) - 1;
if (*p == '\n')
*p = '\0';
for(p = string; *p; ++p)
if (isalpha(*p))
putchar(*p);
else if(isspace(*p))
putchar('\t');
putchar('\n');
}
Result:
./ctype
String?
abc def
abc def
./ctype
String?
ab2cdef ;lo
abcdef lo
Ie, all the letters have been printed out, the spaces changed to tabs
and everything else ignored. Similarly, the
isprint(c) function
returns non-zero if
c is a printable character.
String functions: <string.h>
Again, you've all used them but you should normally try to use the
'
n' versions when possible:
#include <stdio.h>
#include <string.h>
#define BUFLEN 4
void killme(void) {
char buffer[BUFLEN+1] = "";
strcpy(buffer, "This would be very bad news indeed");
}
void dontkillme(void) {
char buffer[BUFLEN+1] = "";
strncpy(buffer, "This would be truncated", BUFLEN);
}
Strlen and strcmp are also often used.
A minor warning
You may have noticed that the buffer we passed to
strncpy had
a length one greater than it apparently needed to have and that we
carefully initialised it to all zeros. This is because
strncpy fails in a rather unhelpful way if the string to be
copied is longer than the length provided by the third argument : it
copies all
N bytes leaving a string without the zero at the
end. Thus, whilst it doesn't over-write the buffer it does leave an
invalid string. We've got round this by making
buffer one
byte larger than it needs to be and ensuring that
buffer[BUFLEN]
is zero. Another approach would be to write a wrapper function:
#include <string.h>
char *mystrncpy(char *to, const char *from, size_t n) {
strncpy(to, from, n);
to[n-1] - '\0';
return to;
}
The const qualifier
The
const char *from syntax above means that we are
promising that
*from is a pointer to a
constant string, ie one that
mystrncpy won't
try to change. Had we tried to write to
*foo that would have
been an error. Finally we note that the
const could have come
after the
char * meaning that the variable itself is a
constant, not what what it points to. Here, for example, we take
constant copies of the two arguments
to and
from.
The four commented-out statements are all errors: the first two try to
change a constant variable, the second try to change a constant string.
#include <string.h>
char *mystrncpy(char *to, const char *from, size_t n) {
char * const tocopy = to;
const char * const fromcopy = from;
strncpy(tocopy, from, n);
++to;
// ++tocopy;
// ++fromcopy;
tocopy[n-1] = '\0';
// fromcopy[n-1] = '\0';
// from[n-1] = '\0';
return tocopy;
}
Mathematical functions: <math.h>
You've all used them (linux users: you may need to specify
-lm when you link), here's an example that answers the
age-old question: "How do I find the angle whose cosine is
y/sqrt(x*x+y*y) and sine is
x/sqrt(x*x+y*y)?"
#include <stdio.h>
#include <math.h>
#define PI 3.14159265358979
int main(void) {
double angle, sinbit, cosbit;
/* NB, sinbit and cosbit don't need to be normalised */
printf("sin and cos?\n");
scanf("%lf %lf", &sinbit, &cosbit);
printf("angle is %4f pi\n", atan2(sinbit, cosbit)/PI);
}
Sorting and <stdlib.h>
As well as
malloc, etc.
<stdlib.h> includes a
function,
qsort, to sort array elements. These can be arrays
of any type (
float,
int, etc.) or arrays of
structures.
Since this can be a little confusing, we'll provide a
working example.
Sorting an array of structures poses two problems:
- Given two structures, how does qsort know which should come first?
Answer: we have to right a function to tell it.
- More subtly; if we pass qsort a pointer to the first element of
the array, how does it know where the second, third, etc elements are?
Answer: we tell it.
Sorting example
NB the boxes of code in this section are
a single C file that has been split into sections.
Let's imagine we have a list of foods and we want them sorted by calories,
with foods with identical calories listed alphabetically. Our
data structure looks like this
#include <stdio.h>
#include <stdlib.h>
#define NAMELEN 256
typedef struct food {
char name[NAMELEN];
float calories;
} Food;
Now we declare the function that
qsort will
call whenever it needs to know which order two items should be in.
The prototype is:
int calories_or_alphabetical(const void *a, const void *b);
It must return an
int which is negative or positive according
to whether the thing
a points to should be before or after in
the list than the thing
b points to. If it turns out they
should both occupy the same place ("second equal") it returns zero.
The arguments are both void pointers because qsort
neiether knows or cares what they point to and the word const
in front of them says that calories_or_alphabetical is
not allowed to modify the thing they point to.
OK, let;s read in the data values.
int main(void) {
Food *foods = NULL;
int howmany, todo;
do {
printf("How many foods (>= 1)?\n");
scanf("%d", &howmany);
} while (howmany < 1);
if ((foods = malloc(howmany * sizeof *foods)) == NULL) {
fprintf(stderr, "Sorry you're too hungry for this Mac!\n");
exit(-1);
}
for (todo = howmany -1; todo >= 0; --todo) {
int readin;
printf("Name and calories?\n");
readin = scanf("%s %f", foods[todo].name, &foods[todo].calories);
if (readin != 2) {
fprintf(stderr, "Sorry, I couldn't understand that\n"
"Please make sure the food name has no spaces.\n");
++todo;
}
}
We use a
do..while loop to check that
we read in a positive number of foods and we also test
to check we've read the (space-free) name and calories properly.
Now we sort the array and print out the foods in calorie/alphabetical order:
qsort(foods, howmany, sizeof *foods, calories_or_alphabetical);
for (todo = howmany -1; todo >= 0; --todo)
printf("%s %f\n", foods[todo].name, foods[todo].calories);
return 0;
}
The first two arguments to
qsort are quite simple: a pointer
to the start of the array (note it
must be an array
not a linked list) and the number of elements to be sorted. The third
argument is the answer to our second question above: it's the
'distance' (in bytes) between the
Nth and
(N+1)th elements of the array which tells
qsort
where the second, third, fourth, etc. array elements are. Finally,
calories_or_alphabetical is just the name of our function.
yes, you can pass the name of a function to another function.
The function itself looks like this:
/*
* Return positive or negative or zero according to which food
* should come first in the sorted list
*/
int calories_or_alphabetical(const void *a, const void *b) {
const Food *fooda = a, *foodb = b;
if ( fooda->calories != foodb->calories)
return foodb->calories - fooda->calories;
return strcmp(foodb->name, fooda->name);
}
Notice that the very first thing the function does is to turn the two
void * pointers into something it can use (which must be
the same type as the array
qsort was called with of
course). Then it checks to see if one food has more calories than the
other. If they have the same calories it checks for which comes first
in the alphabet. Note that our function
calories_or_alphabetical can return
any
positive or negative number it likes. All that counts is whether the
value is positive, negative or zero.
The results
INPUT:
How many foods (>= 1)?
5
Name and calories?
cream 300
Name and calories?
bigmac 300
Name and calories?
kentucky 500
Name and calories?
yoghurt 100
Name and calories?
apple 100
OUTPUT:
apple 100.000000
yoghurt 100.000000
bigmac 300.000000
cream 300.000000
kentucky 500.000000
Sorting and pointers
The
sort function always
introduces another layer of pointing. In the above example, we have
an array of structures but each argument to
calories_or_alphabetical
is a pointer to a structure (
Food *).
Had we had an array of pointers to structures
each argument would be a pointer to a pointer (
Food **)
and the start of
calories_or_alphabetical would have been:
int calories_or_alphabetical(const void *a, const void *b) {
const Food *fooda = * (Food **) a, *foodb = * (Food **) b;
Sorting is important in its own right, it has also introduced two new
concepts: passing a function name as an argument to another function
so that second function can call it and using a
void *
pointer to pass a pointer to a structure of our own devising through a
library function to another function of ours. We conclude with a small
example of this:
typedef struct food {
float val;
char name[32];
} Food;
int alphabetical(const void *a, const void *b) {
const Food *fooda = a, *foodb = b;
fprintf(stderr, "%s %s\n", fooda->name, foodb->name);
return strcmp(fooda->name, foodb->name);
}
void tryit(int fun(const void *, const void *), void *a, void *b) {
printf("%d", fun(a, b));
}
int main(void) {
Food a, b;
strcpy(a.name, "Potato");
strcpy(b.name, "Apple");
tryit(alphabetical, &a, &b);
return(0);
}
Notice my diagnostic inside
alphabetical. This to check I've
got my levels of pointers right. You would be
very wise
to do the same!
exit
Sometimes your program just needs to quit (for example if
malloc fails, meaning you've run out of memory). Calling
exit(integer_value) will do this for
you, the convention is that a value of zero means success, anything
alse means failure.
exit is also defined in
stdlib.h.