C
C
Considering that there are many feet of books on C at the UCSD bookstore,
it is unrealistic to attempt a comprehensive overview of the language in
part of a one-quarter course. Rather I hope to provide enough to get
you started writing programs and familiar enough with the general idea
that you will be able to pick up more skills as your programming experience
grows.
#include <stdio.h>
By itself, C is a quite low-level language that cannot do many of the
things that one expects a language to be able to do. To expand the
capabilities of the language many standard libraries are available
in C installations but they must first be loaded. This is done at
the beginning of the program. The <stdio.h> library facilitates input
and output and is the most commonly used library. As we will see
later, the <math.h> library is necessary for doing all but the most
elementary mathematics.
int main()
Every program in C is actually a series of functions. Every program must
have one function called "main" that tells the compiler where to start
executing. The "int" defines main as an integer function that will return
an integer value. The "()" provides space for the function arguments;
in this case there are none. Finally, the contents of the function are
included between the curly brackets, { and }. For clarity, it is conventional
to indent the contents of the function.
printf("hello, world\n");
This statement sends "hello, world\n" to the screen. The quotes serve to
define a character string. \n is C notation for a newline character so
that the cursor advances to the next line. If you leave out \n then
the cursor will remain at the end of the line. Thus, we could have written
printf("hello, ");
printf("world\n");
and we would get the same result. Forgetting the "\n" is a common mistake
for beginning programmers (at least it was for me).
Finally, you MUST end the statement with a semi-colon. This may seem
unnatural if you are used to FORTRAN. A new line within a C function
does not mean anything. For example, the program would run just fine as:
#include <stdio.h>
int main()
{
printf
("hello, world\n");
return 0;
}
Similarly we can combine two statements on one line if they are separated
by a semi-colon. The following will also work:
#include <stdio.h>
int main(){printf("hello, world\n"); return 0;}
There are differences in programming style. Some people seem to think that
it is more efficient to put multiple statements on the same line. Perhaps
they want to be able to brag that their program "only took five lines of code."
This makes no sense to me. Their programs don't run any faster; they just
are harder to read. Thus, for your class assignments, DO NOT PUT MORE THAN
ONE STATEMENT ON EACH LINE.
return 0;
Functions normally return a value to the main program. In this case. main
is an integer function so we return an integer value. We do this simply for
the sake of completeness here because this value is never used.
This example shows one way to write this program. KR uses a slightly different
style:
#include <stdio.h>
main()
{
printf("hello, world\n");
}
This will run just fine. It is not required that the main function be defined
as an integer or that a value be returned. Another convention is used by
Numerical Recipies. i.e.,
#include <stdio.h>
int main(void)
{
printf("hello, world\n");
return 0;
}
The C source code is just an ascii file (use your favorite text editor);
the file name should end in .c, thus hello.c is the name of the program
discussed above. To run the program, you first must create an executable
version of the code by running the C compiler. On a UNIX system, this
is done as follows:
cc hello.c -o hello
or
make hello
This should create the executable file "hello". However, if the compiler
finds syntax errors in your code, you will get a list of error messages
instead. To run the program, just enter the program name:
rock% hello
hello, world
rock%
If for some reason, you type "hello" and get a message something like
"hello: Command not found" then you probably don't have "set path" set
up correctly in your .cshrc file (see UNIX notes).
ASSIGNMENT C1
Write a C program to print your favorite pithy phrase.
------------------------ HOW TO MULTIPLY TWO INTEGERS ------------------
rock% multint
Product = 6
rock%
float a, b, c;
in which case they would be floating point (real) variables. For the
Sun compiler, floats range from 1.175494e-38 to 3.402823e+38)
The values of a and b are set on separate lines. Sometimes you will see
this done in the variable definition, e.g.,
This is permitted but, as a FORTRAN programmer, I'm not used to this and
usually prefer to assign the variables on separate lines.
c = a * b;
This sets c to the product of a and b. As in FORTRAN, * indicates
multiplication. Other arithmetic operations are as you might expect
(+ for add, - for subtract, / for divide, parentheses can be used to
define the order of operation).
printf("Product = %d \n",c);
This prints the string "Product = " followed by the value of c and a
newline character. The "%d" is not printed; instead it defines the
format in which c is printed. Some examples of different formats are:
%d print as integer
%8d print as integer at least 8 characters wide (including blanks)
(useful to make output line up nicely in columns)
ASSIGNMENT C2
Write a program called multfloat.c that multiplies 2.88 and 3.14 and
prints the output with two digits to the right of the decimal place.
Any reasonable programming language must provide a way to loop over a series
of values for a variable. In FORTRAN this is done with the "do" statement.
In C, this is most naturally implemented with the "for" statement.
(trigtable.c)
#include <stdio.h>
#include <math.h>
int main()
{
float theta, stheta, ctheta, ttheta, degrad;
degrad = 180./3.1415927;
for (theta=0.0; theta<89.5; theta=theta+1.0){
ctheta = cos(theta/degrad);
stheta = sin(theta/degrad);
ttheta = tan(theta/degrad);
printf("%5.1f %6.4f %6.4f %7.4f \n",theta,ctheta,stheta,ttheta);
}
return 0;
}
First, notice that we have included the math library so that we can access
the trig functions. IN ORDER TO ACCESS THE MATH LIBRARY USING THE SUN
COMPILER YOU MUST ALSO USE THE -lm COMPILER OPTION, e.g.,
The C math library uses radians (not degrees) as the arguments of the
trig functions. Thus, following the definitions of the variables as
floats, we assign degrad to 180/pi so that we can easily make this
conversion.
Inside the for loop we compute the cosine, sine and tangent of theta,
after converting from degrees to radians by dividing by degrad. We output
the results to the screen using a fixed floating point format so that the
numbers line up nicely in columns.
theta = 0.0;
while (theta<89.5){
ctheta = cos(theta/degrad);
stheta = sin(theta/degrad);
ttheta = tan(theta/degrad);
printf("%5.1f %6.4f %6.4f %7.4f \n",theta,ctheta,stheta,ttheta);
theta=theta+1.0;
}
The conversion variable, degrad, has a single fixed value in this program.
It also is likely to be useful in other C functions that we may want to
add to the program. Thus it is good programming practice to define
degrad as a "symbolic constant" that can be used thoughout the program.
In our case, the trig table program would look like this:
#include <stdio.h>
#include <math.h>
#define DEGRAD 57.29577951
int main()
{
float theta, stheta, ctheta, ttheta;
for (theta=0.0; theta<89.5; theta=theta+1.0){
ctheta = cos(theta/DEGRAD);
stheta = sin(theta/DEGRAD);
ttheta = tan(theta/DEGRAD);
printf("%5.1f %6.4f %6.4f %7.4f \n",theta,ctheta,stheta,ttheta);
}
return 0;
}
Student question:
Why will the following statement not work in the program?
#define DEGRAD 180./3.1415927
Why will this alternative work?
#define DEGRAD (180./3.1415927)
Why is this not as efficient as DEGRAD 57.29577951 ?
ASSIGNMENT C3
Write a C program to print a table of x, sinh(x), and cosh(x)
for values of x ranging from 0.0 to 6.0 at increments of 0.5.
Use a suitable format to make nicely aligned columns of numbers.
Note: You can use the cosh and sinh functions in the math library.
x is NOT in degrees. Do not apply the DEGRAD conversion factor.
z = x;
if (y > x) z = y;
This is not very aesthetic but it does the job. The statement
inside the parenthesis is tested; if true, the following statement
(or a series of statements enclosed in braces) is executed.
Here is a summary of the common operators used inside the "if" test
and their FORTRAN and MATLAB equivalents
== .eq. == equals
!= .ne. ~= does not equal
< .lt. < less than
> .gt. > greater than
<= .le. <= less than or equal to
>= .ge. >= greater than or equal to
&& .and. & and
|| .or. | or
These operators are also used in the FOR and WHILE loops that
we discussed earlier. For example, we could write:
In this case we loop from i=0 to i=10, but exit the loop
immediately if error = -1 (the error variable could be
assigned to this in the code inside the loop).
if (y < x)
z = x;
else
z = y;
if (condition1)
{statements 1}
else if (condition2)
{statements 2}
...
...
else
{statements n}
z = (x > y) ? x : y; /* z = max(x, y) /*
z = MAX(x+y, x-y);
rock% usermult
Enter two integers
3 5
Product = 15
rock% usermult
Enter two integers
3.1 15
Product = -12790968
Clearly our program is not very robust w.r.t. this kind of error.
Unfortunately, there is no C equivalent to the FORTRAN free-format
read statement (i.e., read *, a, b).
Notice that the scanf statement now appears within the if statement.
This will look strange to FORTRAN programmers, but this type of
construction is extremely common in C.
rock% usermult2
Enter two integers
3.1 5
Error in input
rock%
Finally, let's change the code so that it will allow the user
to continue entering numbers until he/she wants to stop:
(usermult3.c)
#include <stdio.h>
int main(){
int a, b, c;
while (1) {
printf("Enter two integers (zeros to stop)\n");
if ( scanf("%d %d", &a, &b) != 2){
printf("***Error in input \n");
fflush(stdin);
} else if (a == 0 && b == 0)
break;
else {
c = a * b;
printf("Product = %d \n",c);
}
}
return 0;
}
(1) while(1) is used for a while loop that will continue forever.
Previously we tested a condition to see if it continued to be
true. In C, true conditionals are assigned the value 1; thus 1
is always true and the while loop will continue forever.
(2) We use the "break" statement to manually exit the while loop
in the case where both a and b are zero. "break" is an extremely
useful command and can also be used to exit "for" loops.
(3) We use "fflush(stdin)" to clear the keyboard buffer following the
input error so that we can safely try inputting again. It is
often a good idea to precede any scanf statement following the
first with fflush(stdin).
ASSIGNMENT C4
Write a program to repeatedly prompt the user for a positive
real number. If it is negative, ask the user to try again. If
it is positive, compute and display the square root using the
sqrt() function in <math.h> (remember to use the -lm option
with the Sun compiler. Stop the program if the user enters
a zero. Make the program robust with respect to input errors.
i = i + 1;
i++;
which has exactly the same meaning. In addition, the two FORTRAN
statements:
j = i
i = i + 1
j = i++;
j = ++i;
i = i + 1
j = i
i += 2;
In this case += is called an assignment operator. There are related
expressions using -, * and /, i.e.,
n -= 10; means n = n - 10
x *= 2.0; means x = 2.0 * x
zeta /= 8.3; means zeta = zeta/8.3
Here is an example program that uses some of the concepts that we have
just learned:
(gcf.c)
int main()
{
int a, b, i, imax;
while (1) {
printf("Enter two integers (zeros to stop)\n");
if ( scanf("%d %d", &a, &b) != 2){
printf("***Error in input \n");
fflush(stdin);
} else if (a == 0 && b == 0)
break;
else {
for (i=1; i<=MIN(a,b); i++){
if (a % i == 0 && b % i == 0) imax=i;
}
printf("Greatest common factor = %d \n",imax);
}
}
return 0;
}
(1) You can test these functions individually to see if they work before
trying to get the complete program to work.
To illustrate how to define your own function, here again is the greatest
common factor program:
(gcf2.c)
#include <stdio.h>
#define MIN(A,B) ( (A) < (B) ? (A) : (B) )
int main()
{
int a, b;
while (1) {
printf("Enter two integers (zeros to stop)\n");
if ( scanf("%d %d", &a, &b) != 2){
printf("***Error in input \n");
fflush(stdin);
} else if (a == 0 && b == 0)
break;
else {
printf("Greatest common factor = %d \n",getgcf(a,b));
}
}
return 0;
}
The "int" indicates that the function will return an integer value. The
argument list follows inside the parenthesis and indicates what type of
numbers are to be passed to the function. The actual variable names
in the function prototype (a and b in this example) are optional and are
not actually used by the program. They are chosen at the convenience of
the programmer and often simply match the variable names in the function
or in the calling program.
The actual function is normally included after the main program, although
the code would still work if the main program came last. In the argument
list, a and b are the names of the variables that are used in the function;
they must agree in number and type (int, float, etc.) with the variables
in the calling program, but they can have different names.
The values of a and b in this example are passed to the function, but
they are not passed back to the main program. Only the returned value
of the function (imax) in this case goes back to the calling program.
This is a big difference between C and FORTRAN. In a FORTRAN subroutine,
the variables in the argument list are passed back to the main program.
In a C function, the variable in the function can be changed without
these changes affecting the values of the variables in the calling program.
We will have more to say about this difference when we talk about pointers.
ASSIGNMENT C6
Modify your program from C5 to compute the least-common multiple as a
user-defined function.
ASSIGNMENT C7
Write a function that computes the volume of a sphere, given its radius,
together with a main program that inputs different values and outputs the
result.
Arrays are indicated by brackets following the variable name and are
defined, for example, as follows:
float x[100];
x can now assume 100 different values, from x[0] to x[99]. Yes, you
read correctly. C assumes that the array indices start with zero!
This is a BIG difference from FORTRAN and can be very confusing.
In this example, x[100] is out of bounds! Thus, if you want to be
able to refer to x[1] through x[100], you should define x as:
float x[101];
real x(-100:100),y(0:5)
that explicitly allows you to choose the index range of each variable.
It is possible to write C functions, however, that provide this
kind of control on array indices, and Numerical Recipes includes these
in its nrutil.c package.
Another big difference from FORTRAN is that brackets are used for
array indices, not parenthesis. You will get an error if you write
x(15) when you meant x[15].
x[2][3]
(prime.c)
#include <stdio.h>
#define MAXNUM 1000
int main()
{
int i, j, prod[MAXNUM+1], nprime=0;
for (i=1; i <= MAXNUM; i++) prod[i] = 0; /* set prod array to zero */
When we are finished, we simply print out all numbers that were not
eliminated. In our case, this is all i such that prod[i] = 0. We
count the number of primes using the counter variable nprime. Note
that we output a linefeed after every ten primes so that the output
lines do not get too long.
ASSIGNMENT C8
(listrand.c)
#include <stdio.h>
#include <stdlib.h> /* contains srand and rand functions */
int main()
{
int i;
float xran;
srand(5); /* initialize generator */
printf("RAND_MAX = %d \n",RAND_MAX);
for (i=1; i<=20; i++) {
xran = (float)rand()/(float)(RAND_MAX+1);
printf("%f \n",xran);
}
return 0;
}
0 1009
1 1048
2 1001
3 1038
4 1008
5 959
6 993
7 925
8 1017
9 1002
int x[100];
y = sumtot(x);
Notice that the single quotes (apostrophes) are used to write 'A'
because double quotes (") are used to write strings, not a single
character.
(president.c)
#include <stdio.h>
#include <string.h>
int main()
{
char name[81];
strcpy(name,"Bill Clinton");
printf("Our last president was %s \n",name);
strcpy(name,"George Bush");
printf("Our current president is %s \n",name);
return 0;
}
Here is an example program that uses the strstr function and also
demonstrates how to read strings from the keyboard:
(vote.c)
#include <stdio.h>
#include <string.h>
int main()
{
char name[81];
printf("Who did you vote for? \n");
fflush(stdin);
fgets(name,81,stdin);
printf("Then you are likely a");
if (strstr(name,"ill") != NULL || strstr(name,"lin") != NULL)
printf(" Democrat \n");
else if (strstr(name,"eor") != NULL || strstr(name,"ush") != NULL)
printf(" Republican \n");
else
printf("n independent voter \n");
return 0;
}
We have chosen to use the library function "fgets" to read from the
keyboard (stdin in this case). 81 gives the maximum number of
characters that can be input. Sometimes C programs use the
"gets" function to read strings from the keyboard but this is
not recommended because "gets" does not set a maximum length and
thus there is a risk of entering a string that is too long which
will overwrite memory off the end of the allocated space for the
string.
fgets does have the awkward feature that it will include the
linefeed character '\n' as the 2nd to last character in the string
(before '\0' at the end).
The function "scanf" can also be used to read a string from the
keyboard as in:
scanf("%s",name);
The problem with this in our example is that any blank character
will be interpreted as the end of the input. Thus, for example,
if the user tried to input
George Bush
using scanf, the program would assign "George" to name and leave
"Bush" in the input buffer. This could screw up the next input
unless "fflush(stdin)" is called to clear the input buffer.
ASSIGNMENT C9
Then use the fgets function to input a line from the user/patient.
Search this line for various key words that you can then use to
guide the computer's response. Design the program so that it
will continue this "conversation" between doctor and patient.
Some suggestions:
So far, all off our examples have involved input from the keyboard
and output to the screen. Now let us see how to open files, read
from them, and write data to them. Here is a program that reads
pairs of numbers from an input file, computes their product, and
outputs the original numbers and their product to an output file.
(fileinout.c)
#include <stdio.h>
main()
{
FILE *fp1, *fp2;
char infile[100], outfile[100];
float x, y, z;
printf("Enter input file name \n");
scanf("%s",infile);
fp1 = fopen(infile,"r");
if (fp1 == NULL) {
printf("***Can't open file: %s\n", infile);
return 1;
}
printf("Enter output file name \n");
fflush(stdin);
scanf("%s",outfile);
fp2 = fopen(outfile,"w");
if (fp2 == NULL) {
printf("***Can't open file: %s\n", outfile);
return 1;
}
while (1){
if (fscanf(fp1, "%f %f", &x, &y) == EOF) break;
z = x * y;
fprintf(fp2,"%8.3f %8.3f %8.3f \n", x, y, z);
}
fclose(fp1);
fclose(fp2);
return 0;
}
fp1 = fopen(infile,"r");
The 2nd argument is called the file mode and can assume the
following:
These all assume that the files are in ascii (text). For
binary files, use "rb", "rw" and "ra".
If the program is not able to open the file it will set the
file pointer to NULL. The program tests for this and prints
out an appropriate message.
Finally, the program closes the two open files using the
fclose statement. These closing statements could be
deleted in the case of this program because open files are
closed automatically upon normal termination of a program.
However, it is good practice to close files whenever their
I/O is finished.
ASSIGNMENT C10
10 20 30 40 50
1 2 3 4 5
2 4 6 8 10
5 5 5 5 5
Note that the elements are stored such that one sweeps over
the second index first. This is easy to remember is one
thinks of a[i][j] as really representing (a[i])[j]. In fact,
the internal brackets are not needed and the initialization
could have been written as:
(matmult.c)
#include <stdio.h>
#define DIM 3
int main()
{
int i, j, k;
float a[DIM][DIM] = { {-5.1, 3.8, 4.2},
{ 9.7, 1.3, -1.3},
{-8.0, -7.3, 2.2} };
float b[DIM][DIM] = { { 9.4, -6.2, 0.5},
{-5.1, 3.3, -2.2},
{-1.1, -1.8, 3.0} };
float c[DIM][DIM];
for (i=0; i<DIM; i++){
for (j=0; j<DIM; j++){
c[i][j] = 0.0;
for (k=0; k<DIM; k++) c[i][j] += a[i][k] * b[k][j];
}
}
return 0;
}
ASSIGNMENT C11
(stringarray.c)
#include <stdio.h>
#include <string.h>
int main()
{
char name[10][81];
strcpy(name[0],"Bill Clinton");
printf("Our last president was %s \n",name[0]);
strcpy(name[1],"George Bush");
printf("Our current president is %s \n",name[1]);
return 0;
}
char name[10][81];
(procfile.c)
#include <stdio.h>
#include <string.h>
main()
{
FILE *fp1, *fp2;
char infile[100], outfile[100];
char linebuf[20], event[20];
nevent = 0;
ndata = 0;
sum = 0.0;
while (1){
if (fgets(linebuf,20,fp1) == NULL) break;
if (strncmp(linebuf," ",1) != 0){ /* no init blank, must be event */
nevent++;
if (nevent !=1) { /* need to output previous event info */
if (ndata != 0) xavg = sum / (float)ndata;
else xavg = 0.0;
fprintf(fp2,"%s %7.2f %5d\n", event, xavg, ndata);
ndata = 0;
sum = 0.0;
}
strncpy(event,linebuf,18);
} else { /* has starting blank, must be measurement */
sscanf(linebuf,"%f",&x);
sum += x;
ndata++;
}
}
if (ndata != 0) xavg = sum / (float)ndata;
else xavg = 0.0;
fprintf(fp2,"%s %7.2f %5d \n", event, xavg, ndata);
fclose(fp1);
fclose(fp2);
return 0;
}
The beginning of the program where the files are opened is the same
as in fileinout.c (our earlier example of file I/O).
We use the "fgets" function to read one line at a time into the string
variable linebuf. Next, we use the strncmp function to check if the
first character in linebuf is blank (" "). If it is not blank, then
we know we have a data/time line. In our program, we term this an "event"
and increment an event counter at this point. If we are not on the
first event (normally the first line of the input file), then we must
at this point compute the mean of the previous set of measurements
and output the previous data/time line followed by this mean value.
Continuing, we set the event string variable to linebuf so that we
can keep this information while we overwrite linebuf later in the
program. Note that we only copy 18 characters of linebuf to event.
This is because fgets has the unfortunate property of including the
linefeed character in the string that it inputs. We can exclude
the linefeed from event by only copying the first 18 characters.
If, on the other hand, the line read into linebuf does contain a
leading blank, then we know we have a measurement line. In this
case we need to read a number from the line. This is done using
an "internal read" and the sscanf function:
sscanf(linebuf,"%f",&x);
ASSIGNMENT C12
There is a line for each earthquake with the date and time (UT not
local time). For the first line in the above example, the
additional bits include:
L = local event
1.1 = magnitude
c = how magnitude was computed (coda in this case)
35.962 = lat
-117.693 = lon
5.2 = depth (km)
A = SCSN assigned quality (A=best)
9159024 = cusp id number
8 = number of lines of phase arrival time info
E-mail me a copy of the C program source code and the UNIX script.
The good news about this data format is that it does include a number
in the event line that lists the number of phase lines that will
follow. The bad news is that sometimes the last 1 or 2 phase lines
are "garbage" in that they have numbers instead of station names
and are some kind of calibration info. You will have to figure
out how to recognize and discard these lines in your program. Also
the phase lines begin with a tab character rather than a long
series of blanks; this may complicate things somewhat.
Hint: Don't try to write your entire C program all at once. First,
get a version working that simply reads the input file and prints
out the parts of each line that you will need (i.e., the depth
and number of phase lines for the event line, the phase name and
the X and P values for the phase lines). Once you have this
working correctly, then go on to add the part to output the
(X,T) points.
OK, we can't put it off any longer. Let's talk about pointers.
Any variable defined in a program is stored in a particular
place in the computer memory. Its "pointer" is the address
of that place in memory. Its value is the number that is stored
in that location. Thus, all computer programs must use pointers,
but some, like FORTRAN, take care of pointers internally so that
the programmer does not generally need to be concerned with them.
However, C is much more pointer oriented and knowledge of how
pointers work is necessary for many C programs.
x = value of x
&x = pointer (memory address) for x
You can also declare pointers and give them regular names
(i.e., without the &). Pointers must refer to a specific
type of variable (i.e., int, float, char, etc.) and this
must be defined when the pointer is declared. Let us illustrate
this be defining a pointer to an integer; we will call the
pointer xpt
int *xpt
So now let's illustrate how all this could work for a simple
program:
(testpoint.c)
#include <stdio.h>
int main()
{
int x, y;
int *pt;
x = 1;
pt = &x; /* pt1 now points to x */
y = *pt; /* y is now set to 1 */
*pt = 2; /* x is now set to 2 */
printf("%d %d %d \n",x,y,*pt);
return 0;
}
2 1 2
int main()
{
float r, area, circum;
r = 1.33;
areacircum(r, &area, &circum);
printf("r, area, circum = %f %f %f \n", r, area, circum);
return 0;
}
This is very useful because often you will want to write functions
that return a number of different values, not just a single one.
int x[5];
(testarraypt.c)
#include <stdio.h>
int main()
{
int z[5] = {11, 12, 13, 14, 15};
int *pt;
pt = z;
printf("%d %d %d \n", *pt, *(pt+1), *(pt+2) );
printf("%d %d %d \n", *z, *(z+1), *(z+2) );
return 0;
}
The result of running this program will be:
rock% testarraypt
11 12 13
11 12 13
rock%
One might ask the question: How does the program know what
the next memory address is? The answer will depend upon
the type of variable that the pointer is addressing. If
it is a (long) integer array, then it should move 4 btyes.
However, if it is a character array, then it should move
only 1 btye. Fortunately, the C compiler keeps track of
the type of variable and shifts the appropriate amount.
However, this makes it clear why pointers must be declared
as referring to a specific kind of variable.
pt++;
x[10]
as
*(x+10)
int x[10];
that x[10] exists! In fact, since x starts with x[0], the last
array element is x[9].
float a[5][9];
In FORTRAN, one has complete control over the index limits used
in matrices and arrays. For example, the statement
(definearray.c)
#include <stdio.h>
#include "nrutil.c"
int main()
{
float *a, **bb;
a = vector(1,7);
a[6] = 6.4;
bb = matrix(1,13,1,9);
bb[5][5] = 3.3;
free_vector(a,1,7);
free_matrix(bb,1,13,1,9);
return 0;
}
This assumes that the nrutil.c file is within the same directory
as the program. You can grab this file from the class web site
or via anonymous ftp to mahi.ucsd.edu/pub/239.
To use the vector functions, you must define your vector variable
as a pointer (i.e., float *a). The nrutil vector functions
include:
To use the matrix functions, you must first define your matrix
variable as a pointer to a pointer (i.e., float **b). The nrutil
functions include:
trigtable : trigtable.c
cc -lm trigtable.c -o trigtable
Note that the first line just identifies the name of the program
that we are "making" -- the executable file trigtable. This is
considered the target of the Makefile. We follow this with a colon
and then a list of everything upon which the target depends, in
this case just the source code trigtable.c.
The next line MUST start with a tab. If you use spaces, it will
not work! The "cc -lm trigtable.c -o trigtable" is simply what
will happen when you enter "make trigtable" at the UNIX command
level. The advantage of using a Makefile in this example is that it
allows us to avoid having to type in "cc -lm trigtable.c -o trigtable"
everytime we want to recompile the program.
Now let's see how we can link to the nrutil code. First, we
change our definearray.c program to start with:
(definearray2.c)
#include <stdio.h>
#include "nrutil.h"
Notice that we include the header file "nrutil.h" this time rather
than the nrutil.c source code. If we try to compile the program
without a Makefile we will get error messages because the computer
will not be able to find the vector and matrix functions. So we
must create an object code for nrutil by entering at the UNIX
command level:
cc -c nrutil.c
This will create the file nrutil.o which is what we will want to
include with our program if we are to access the functions. In
the make file, we write
(sortrand.c)
#include <stdio.h>
#include <stdlib.h> /* contains srand and rand functions */
#include "nrutil.c"
int main()
{
float *x;
int i, j;
x = vector(1,NPTS);
piksrt(NPTS,x);
for (i=0; i<NPTS/10; i++) {
for (j=1; j<=10; j++) printf("%7.4f ",x[10*i+j]);
printf("\n");
}
free_vector(x,1,NPTS);
return 0;
}
(piksrt.c)
void piksrt(int n, float arr[])
/* sorts an array arr[1,n] into ascending order by straight insertion.
n is input; arr is replaced on output by its sorted rearrangement
WARNING: Very slow for large n, use only for n < 50 or so */
{
int i, j;
float a;
for (j=2; j<=n; j++) {
a = arr[j];
i = j - 1;
while (i > 0 && arr[i] > a) {
arr[i+1] = arr[i];
i--;
}
arr[i+1] = a;
}
}
(sortrand2.c)
#include <stdio.h>
#include <stdlib.h> /* contains srand and rand functions */
#include "nrutil.c"
#include "piksrt.c"
int main()
{
float *x;
int i, j;
x = vector(1,NPTS);
piksrt(NPTS,x);
free_vector(x,1,NPTS);
return 0;
}
ASSIGNMENT C13
Often data products are labeled by the station name, without including
information about the station, such as its location. Data processing
programs will often require this information. Thus, there is frequently
a need for a computer routine that will retrieve information about
a station, given the station name. One could hardwire this information
into the program with a series of if statements, but this would become
very cumbersome for large numbers of stations and would limit the
flexibility and portability of the code. A better approach is to
save the station information in a file and have the computer read from
the file to access the information when necessary. However, file
I/O is relatively slow so we won't want to open, read, and close the
file every time we need to determine a station location. It would
be much faster to read the station information file once and then
save the information during the time that the program is running.
For portability, ideally all of this overhead would be performed
in a function that we could use in many programs without having
to worry about the format of the station file or the size of the
array that are required to store it.
(testgetstat.c)
#include <stdio.h>
#include <string.h>
int main()
{
char stname[5];
float slat, slon, selev;
while (1) {
printf("Enter station name \n");
scanf("%s",stname);
if (firstcall){
firstcall = 0;
printf("Reading station file: %s \n",STFILE);
fp1 = fopen(STFILE,"r");
if (fp1 == NULL) {
printf("***Can't open file: STFILE \n");
return 1;
}
i1 = 0;
i2 = nsta;
for (iter=0; iter<15; iter++){
i = (i1+i2)/2;
itest = strcmp(stname, &stlist[i][0]);
if (itest == 0) {
*slat = stlat[i];
*slon = stlon[i];
*sdep = stdep[i];
return 0;
}
else if (i1 == i2) break;
else if (itest < 0)
i2 = i - 1;
else
i1 = i + 1;
}
printf("***Station not found: %s \n",stname);
*slat = -999.0;
*slon = -999.0;
*sdep = -999.0;
return 1;
}
Note that we pass slat, slon and sdep as pointers to the function
because we want to pass these values back to the main program.
Inside the function, we first define
These are variables that are used only within the function. The
"static" prefix means that the values of these variables will be
preserved for subsequent calls to the function. This is necessary
in our case because we want to read the table of station locations
once (during the first call to the function) and then save the
table values for all additional calls to the function. If we left
out the static, then the values would not be preserved and the
function would not work properly. Note that the static prefix
also causes the initialization of firstcall and nsta to apply only
to the first time that the function is called.
if (firstcall){
The tricky part of this is reading the station names, which are contained
in the array stlist[5000][5]. The first index in this two-dimensional
array is i; the second is for the characters within the string containing
the station names. Recall that when we use fscanf (or scanf) that the
input variables are referenced by pointer; this is why we put & in front
of their names. Arrays are referenced by a pointer to their first element.
&stlist[i][0] is the pointer to the first character in the station name
for the i-th line of the file (all of the arrays in this program start
with 0, not 1).
Note that [] has precedence over & so that this expression means
&(stlist[i][0]) and not (&stlist[i])[0].
Question for students: Why is nsta+1 output for the number of stations,
rather than simply nsta?
The next part of the function searches for the target station name
from the list of stations. For maximum efficiency this search takes
advantage of the fact that the stations in the station file are
arranged in alphabetical order. i1 and i2 are indices that are
designed to bracket the target station in the list. Initially, they
are set to 0 and nsta, the first and last station indices. Next,
i is set to a point halfway between i1 and i2. The station in the
station list at i is compared to the target station. If they are
the same, then we can set the return variables. If the stlist
station is greater than the target station (later in the alphabet),
then i2 is set to i-1. If the stlist station is less than the
target station, then i1 is set to i+1. We iterate using this procedure
15 times to narrow in on the station name. (2^15 must be greater
than NMAX to make sure we do enough iterations)
ASSIGNMENT C14
/*
sph_dist computes angular separation of two points on sphere
Inputs: flat1 = latitude of first point (degrees)
flon1 = longitude of first point (degrees)
flat2 = latitude of second point (degrees)
flon2 = longitude of second point (degrees)
Returns: del = angular separation between points (degrees)
Note: This routine is inaccurate for del less than about 0.5 degrees.
For greater accuracy, perform a separate calculation for close
ranges using Cartesian geometry.
Requires: <math.h>
*/
where plat and plon are the coordinates going into the function and
stname, slat, slon and sdep are passed back from the function to
the main program. HINT: Remember to use the string copy function,
strcpy, to set stname once you have found the index for the closest
station.
int main()
{
typedef struct { char name[NAMELENGTH];
int age;
float height, weight;
} person_type;
person_type students[NMAX];
int i,ii;
float weightmin;
ii=0;
weightmin=999.0;
for (i=0; i<NMAX; i++){
if (students[i].weight < weightmin) {
weightmin = students[i].weight;
ii = i;
}
}
return 0;
The statement:
person_type students[NMAX];
In this line, person_type acts just like the int or float
statements that we are used to. It can be used to define more
than one variable name and these variables do not need to be
arrays. Here is another example of this:
students[3] = students[2];
students[3].name = students[2].name;
students[3].age = students[2].age;
etc.
(namebase2.c)
#include <stdio.h>
#define NAMELENGTH 12
#define NMAX 5
int main()
{
person_type students[NMAX], lightstudent;
getpeople(students);
lightstudent = findlightest(students);
printf("%s is the lightest student \n", lightstudent.name );
return 0;
}
(complexmult.c)
#include <stdio.h>
#include <math.h>
#include "complex.c"
int main()
{
fcomplex a, b, c;
float x, y, cabs;
printf("Enter 1st complex number (real + imag) \n");
scanf("%f %f", &x, &y);
a = Complex(x, y);
printf("Enter 2nd complex number (real + imag) \n");
scanf("%f %f",&b.r, &b.i);
c = Cmul(a, b);
cabs = Cabs(c);
printf("product = (%f, %f), cabs = %f \n", c.r, c.i, cabs);
return 0;
}
fcomplex a, b, c;
ASSIGNMENT C15