An instantaneous introduction to C

This document introduces the basic programming structures provided in C. It covers the basic statements and basic data structures available. It is not an introduction to "programming" in C as much as it is an introduction to the language itself.

Table of contents

  1. A first C program
  2. Basic C variables and data types
  3. Reading data from the keyboard
  4. If statements and relational operators
  5. Using logical operators
  6. List of C operators
  7. Arrays
  8. Structures
  9. Repetition and looping constructs
  10. Function calls
  11. Variable scope
  12. Calling by reference
  13. Pointers
  14. Using the ANSI C standard library
  15. Some final thoughts

A first C program

To begin with, you can build a very simple program that does something obvious to the user by using the C printf statement:

#include <stdio.h>

main()
{
   printf("Hello world!");
}

The first line tells the compiler which header file to include in the program. Header files contain information that the computer uses when your program is made into an executable program. The second line contains the mainfunction header. A main function header is needed in every C program, because the program starts execution at this header. The function header is followed by an open brace { to show where the function begins. This open brace is matched by the closed brace } on line 5. The closed brace signifies the end of the function.

The fourth line causes the string "Hello world!" to appear on the screen. This line is composed of a function call, printf, an argument surrounded in parentheses, ("Hello world!"), and is terminated by a semicolon, ;.

Use the pico editor to define a file named hello.c. Type pico followed by the file name at the UNIX command line prompt, which is probably a dollar sign, $.

   $ pico hello.c

After the program is entered and saved to the file hello.c, the program must be compiled. There are many ways to compile a C program, and we'll use the GNU C compiler, GCC. At the command prompt, type gcc, the output file switch, -o, the output file name hello, and the c source file, hello.c.

   $ gcc -o hello hello.c

Once the program is compiled, type ./hello at the command prompt, and it should show :

   $ ./hello
   Hello world!$

In this example, the next command line prompt is on the same line as the program's output. To fix this, change the fourth line in hello.c to read :

   printf("Hello world!\n");

Save and compile the modified program, and then rerun it. The output should read :

   $ gcc -o hello hello.c
   $ ./hello
   Hello world!
   $

This time the program prints a newline (represented by \n) after printing "Hello world!", so that the next command prompt appears on its own line. Each newline character forces the cursor down one line and moves the cursor to the beginning of the line.

Back to the Table of Contents

Basic C variables and data types

The "Hello world!" program could also be written using variables. Variables are used to store information for later use. C has quite a few different variable types. For example, "Hello world!" is a group of characters, thus a character variable type would be used. Here is the "Hello world!" program using a a character type variable :

#include <stdio.h>

main()
{
   char greeting[] = "Hello world!"; 

   printf("%s\n", greeting);
}

On the fourth line, the variable greeting[] is declared to be an array of characters. Arrays are ordered structures of variables of the same type. When greeting[] is defined, the double brackets [] at the end of the variable name show that it is an array.

Another way to write the line :

   char greeting[12] = "Hello world!";

Arrays always have a defined size. When the array greeting[] was defined to be "Hello world!", its size was set to be the length of "Hello world!", or twelve characters. The previous example line demonstrated how to manually define an array length.

The printf command in the previous program is also different. In this version, printf is called with two arguments. The first argument, "%s\n", tells the computer to print a string followed by a newline. The second argument, greeting, is a variable which contains the character array, Hello world!.

Try compiling a new version of hello.c which reads :

#include <stdio.h>

main()
{
   char name[] = "Dan";
   
   printf("Hello world, my name is %s!\n", name);
}

The output should read :

$ gcc -o hello hello.c
$ ./hello
Hello world, my name is Dan! 
$

Another C variable type is the integer, or int. Obviously, this type is used to hold integer values. Here's an example of using integer data in a program :

#include <stdio.h>

main()
{  
   int age     = 19;
   char name[] = "Dan";

   printf("My name is %s and I am %i years old.\n", name, age);
}

In this program, the format code %i is used, which means the inserted variable will be an integer. The output of this program would read :

$ gcc -o hello hello.c
$ ./hello.c
My name is Dan and I am 19 years old.
$

Back to the Table of Contents

Reading data from the keyboard

You can read data from the keyboard into a C program like this :

#include <stdio.h>

main()
{  
   char name[30];

   scanf("%s", &name);
   
   printf("Your name is %s.\n", name);
}

This program will read one line of input from the "standard input" and write that line to the name variable. Then a sentence is printed to the screen using name.

Notice that name is initialized to a length of 30. This is so that it can hold a really long name. If a name is over this length, it may be read into the variable, but it can cause a memory error. The rule of thumb is to use a length that is much longer than you'll need.

A new function, scanf(), was introduced in this program. It is to read data from the keyboard (the standard input). The first argument is a format string to read from the line. In this case, the format string is a %s. This will read the first string from the line.

The second argument is a predefined variable, name, preceded by an ampersand, &. The second argument receives the string that the format argument read. So, if you typed in "Bobby" at the prompt, "Bobby" would be stored in the variable name.

Here's an example of the program's output :

$ gcc -o name name.c
$ ./name
Bobby
Your name is Bobby.
$

You can prompt the user for input by using a print statement followed immediately by a read, as in:

/* Program that prompts a user for his name */
/* and then tells it to him again. */

#include <stdio.h>

main()
{  
   char name[30];

   printf("Enter your name : ");          /* prompt the user */

   scanf("%s", &name);                    /* read the name */
   
   printf("Your name is %s.\n", name);    /* print the name */
}

This program is the same as the last, except for the first printf() statement. This statement is used as a prompt for the user. Combining printf and scanf statements in the correct way can greatly help the user interface of any program you write.

Another new feature in this program is commenting. The program can run without commenting, but as your programs increase in complexity, commenting helps both you and others understand what your code does. Nobody wants to look at 2000 lines of source code with no comments! (Some may know this from experience.)

Back to the Table of Contents

If statements and relational operators

You can evaluate the data input from the user (data validation) by using an "if" statement and a comparison as in:

/* Program that prompts a user for his age */
/* and then . */

#include <stdio.h>

main()
{  
   int age = 0;   

   printf("Enter your age : ");           /* prompt the user */
   scanf("%i", &age);                 /* read the age */
   
   printf("You're %s years old.\n", age); /* print the age */

   if (age > 130)
   {
      printf("I don't believe you!\n");
   }
}

The if statement allows you to compare two numbers and respond accordingly. In this case the number stored in the variable age is compared with the number 130 to see whether age is greater than 130. If the user enters a number that is greater than 130, the message expressing skepticism will be printed.

The whole expression, age > 130 is a "conditional expression", and the general syntax (form) for an if statement may be represented as:

     if ( conditional_expression )
     {
         program statements to execute if the conditional_expression 
          evaluates "true".
     }

Note that these relational operators only work for numbers, not strings such as words or names.

Another way to handle this validation is to use an if...else... statement like:

/* Program that prompts a user for his age */
/* and then prints a message. */

#include <stdio.h>

main()
{  
   int age = 0;   

   printf("Enter your age : ");           /* prompt the user */
   scanf("%i", &age);                     /* read the age */
   
   if (age > 130)
   {
      printf("I don't believe you!\n");   /* print skepticism */
   }
   else 
   {
      printf("You're %s years old.\n", age);    /* print age */
   }
}

This time EITHER the message of skepticism OR the message of acceptance will be printed, but NOT both.

Back to the Table of Contents

Using logical operators

A user may also enter a negative number, which would be another invalid entry. You can test against both of these invalid entry possibilities by using a logical OR operator || as in:

/* Program that prompts a user for his age */
/* and then prints a message */

#include <stdio.h>;

main()
{  
   int age = 0;   

   printf("Enter your age : ");           /* prompt the user */
   scanf("%i", &age);                     /* read the age */
   
   if ( (age < 0) || (age > 130) )
   {
      printf("I don't believe you!\n");   /* print skepticism */
   }
   else 
   {
      printf("You're %s years old.\n", age);    /* print age */
   }
}

It follows that the logical AND operator is &&, and the logical NOT operator is !.

For example, to identify a believable response, you might ask:

     if ( (age >0) && (age < 130) )

Back to the Table of Contents

List of C operators

A partial list of C operators includes those in the table below. The list is presented in (approximate) descending precedence order. That is, if an expression contains two or more operators, C will apply the highest ones in the list first. (Some of the operators in this list share the same order of precedence, but have been shown on different lines for clarity.) Knowing the precedence order of an operator may help you decide where to use parentheses in your conditional expressions.

Unary operators 
!logical not
-signify negative numeric value
Arithmetic operators 
* / %arithmetic multiply, divide, modulus
+ -arithmetic add, subtract
Relational operators 
>true if the first is greater than the second
<true if the first is less than the second
<=true if the first is less than or equal to the second
>=true if the first is greater than or equal to the second
==true if the first is equal to the second
!=true if the first is not equal to the second
Logical operators 
&&logical and
||logical or
Assignment operators 
=equality assignment

Back to the Table of Contents

Arrays

Remember the beginning when arrays of characters were used to represent a string? Let's take a more in depth look at how arrays work.

Arrays are a data structure that C provides for more convienent access to a list of variables. An array, such as numbers[] could be defined using a C statement like :

int numbers[20];

This statement would declare an array of 20 integers. Each member of an array is called an "element". To access an element of the array, you must tell the computer which element you would like to access. The first element in an array is accessed with numbers[0], and the last element in the array is accessed at location numbers[19]. The last element in the array is 19 since C starts counting at element 0. So, the 19th location is actually the 20th element. If the location numbers[20] is accessed, you'll get junk information. If you try to write to this location, you will be corrupting data.

Here's a simple program which uses an array to store inputted numbers :

/* Program that prompts a user for three numbers */
/* and then prints the numbers to the screen. */

#include <stdio.h>

main()
{  
   int numbers[3];   

   printf("Enter a number: ");      /* prompt for first number */
   scanf("%i", &numbers[0]);        /* read the number */
   
   printf("Enter a number: ");      /* prompt for second number */
   scanf("%i", &numbers[1]);        /* read the number */

   printf("Enter a number: ");      /* prompt for third number */
   scanf("%i", &numbers[2]);        /* read the number */
  
   /* print the three numbers */
   
   printf("Your three numbers : %i %i %i\n", numbers[0],
      numbers[1], numbers[2]);        
}

Back to the Table of Contents

Structures

Like arrays, a structure (or "struct") is a data structure used for convienent information access. Unlike arrays, structs can contain multiple variable types. For example, a struct could be defined like this :

/* a personal information data structure */

struct __person {

   float height;
   float weight;
   
   int age;    
   
   char name[50];
};
typedef struct __person person_t;

This is the definition of a data type called person_t. The first line defines the name of the struct as __person. After this is the block of variables which make up the data structure. person_t has two floats, one int, and an array of characters. Yes, you can have arrays and even other structures within a struct definition. Note that once the block is finished, a semi-colon (;) follows the closing brace (}).

After the struct definition is the line :

typedef struct __person person_t;

The typedef statement defines the name for struct __person to be person_t. This line is not absolutely nessicary, but it makes creating a struct a more simple process. Below is an example of using a struct in a program.

/* Program to get a person's information and 
   print it out again. */

/* First, define the data structure */

struct __person {

   float height;
   float weight;
   
   int age;    
   
   char name[50];
};
typedef struct __person person_t;

main()
{
   /* Make a variable, your_info, of the type person_t */ 

   person_t your_info;

   printf("Enter your height : ");      
   scanf("%f", &your_info.height);      
   
   printf("Enter your weight: ");      
   scanf("%f", &your_info.weight);      
  
   printf("Enter your age: ");      
   scanf("%i", &your_info.age);      

   printf("Enter your first name: ");      
   scanf("%s", &your_info.name);      

   printf("Hello, %s.  You are %i years old.\n", 
      your_info.name, your_info.age);  
   printf("You are %f feet tall, and weigh %f thousand pounds.\n",
      your_info.height, your_info.weight);
}

Notice that your_info is defined the same way as any other variable -- type first, variable name second. This can happen because of the typedef statement.

To reference data within a struct, a dot delimiter (.) is used. If you want to reference the age variable inside of your_info, you must type the name of the data structure, the dot delimiter, and then the variable name within the data strucuture you want to reference. (your_info.age)

Repetition and looping constructs

It would be impractical if not impossible to print out the values of large arrays using single print statements. C provides special constructs for performing repetitive tasks such as this.

For example, to print the values of each list element within numbers[] you can write a for loop like this:

/* a program to print out the elements of an array */

#include <stdio.h>

main()
{
   /* initialize a loop counter */
   int counter    = 0;
   int numbers[3] = { 12, 34, 56 };

   /* print the three numbers using a for loop */

   for (counter = 0; counter < 3; counter++)
   {
      printf("%i\n", numbers[counter]);
   }
}

The output of this program would be :

$ gcc -o numbers numbers.c
$ ./numbers
12
34
56
$

The for loop has a construct that looks like this :

for (initialize_counter; loop_test; modify_counter)
{
   loop code
}

This approach implements the following process:

In the example, counter is incremented with the ++ operator. This operator adds one to the variable it is attached to. It is written with the following syntax :

variable++

You might also use a while loop to accomplish the same thing as in:

/* a program to print out the elements of an array */

#include <stdio.h>

main()
{
   /* initialize a loop counter */
   int counter    = 0;
   int numbers[3] = { 12, 34, 56 };

   /* print the three numbers using a for loop */
    
   while (counter < 3)
   {
      printf("%i\n", numbers[counter]);
      counter++;
   }
}

Though the while loop does the same thing as the previous for loop, the two loops have different uses. While loops are useful when a program cannot know at the beginning how many repetitions it must make. This would be the case when reading information from a file, or when looking for specific user input.

Back to the Table of Contents

Function calls

The next program does the same as the previous program, but in a more structured way. This program has three "functions", which are reusable blocks of code.

/* Program using function calls that prompts a user */
/* for his age and then prints a message */

#include <stdio.h>
 
void print_disbelief()
{
   printf("I don't believe you!\n");   /* print skepticism */
   return;
}

void print_age(int your_age)
{
   printf("You're %s years old.\n", your_age);    /* print age */
   return;
}

int get_age()
{
   int your_age = 0;
   
   printf("Enter your age : ");        /* prompt the user */
   scanf("%i", &your_age);             /* read the age */

   return your_age; 
}

main()
{  
   int age = 0;   

   age = get_age();

   if ( (age < 0) || (age > 130) )
   {
      print_disbelief();
   }
   else 
   {
      print_age(age);
   }
}

Functions must be defined before they are used in C programs. Notice that the function definitions are before the main() function. Even though the main() function is the last function defined in the program, execution still starts at the beginning of the main() function. A function definition has this construct :

return_type function_name(argument1, argument2, ...)
{
   statements executed in the function
   return statement
}   

We'll start with the simplest function in the program, print_disbelief. The function definition for print_disbelief is :

void print_disbelief()
{
   printf("I don't believe you!\n");   /* print skepticism */
   return;
}

The purpose of print_disbelief is rather obvious -- it prints a message to the screen. The function doesn't need any information from the program, so the argument section is empty. Also, the program doesn't need any information from it, so the return type is void. A void return means that no information is being returned from the function. The program will simply print "I don't believe you!" with a call to this function.

print_age is the next function defined in the program :

void print_age(int your_age)
{
   printf("You're %s years old.\n", your_age);    /* print age */
   return;
}

print_age's purpose is to print out an age. But, the function doesn't have the age value internally; it must be passed this value in its argument section. The argument section reads "int your_age". This means that the function wants an integer value. The function call in main satisfies this requirement :

print_age(age);

Earlier in the program, age is defined to be an integer, so this variable would be a valid pass.

Rather than using the value of the passed variable, age, the function creates it's own variable, your_age. Using a new variable within the function instead of the passed variable isolates the function from the rest of the program. All variables created within the function can only be accessed within the function, and other parts of the program cannot access these variables. In the function print_age, your_age is a new variable with a type int. It's value is set to the value of the variable passed to the function, age. If your_age's value is modified within the function, the value of age is not changed -- they are seprate variables.

The last function in the program is get_age :

int get_age()
{
   int your_age = 0;
   
   printf("Enter your age : ");        /* prompt the user */
   scanf("%i", &your_age);             /* read the age */

   return your_age; 
}

This function prompts the user for an age, and then returns the value. The return type is int. Since the return type is int, the variable returned, your_age, must be an int also. After the user response is entered into your_age with the scanf function, the value of your_age is returned with the command :

return your_age;
This command returns the value of your_age to the line in the main where the function get_age was called :

age = get_age();

In main, age is defined as an int, which is the same as the return type of the function get_age. So, when this line is executed, the function get_age is called. Within the function, the user is prompted for an integer number, and that number is returned. The variable age is set to the returned number.

Functions are an extremely useful programming tool for making programs that do anything more than say "Hello world!". Functionally designed programs are typically easier to understand, debug, and modify because their structure is clear and data isolation keeps functions from changing each other's data.

Note that the functions in the example above are used in contexts that do not require the function to take or return a value. You may have noticed that print_disbelief doesn't do anything that couldn't be done in one statement in the main function. This is all right, for the functional approach makes the code in the main function cleaner and easier to understand for someone reading the source code.

Back to the Table of Contents

Variable scope

Variable scoping is a very important concept in C. Scope refers to where a variable can be referenced. Variable scope can be used for data hiding and protection throughout a program. With a good understanding of scope, you can write safer and more solid code.

The two types of scope are global and local. A variable with global scope can be seen from anywhere in a program. Variables with set values or values that don't change often are are appropriate global variables. Programming with many global variables tends to create errors and data corruption, so they should be used carefully. The syntax for a global variable is below :

#include <stdio.h>

extern int VERSION = 1;

main() 
{
   printf("This is version %d.", VERSION);
}

Notice that VERSION is declared outside of a function. When a variable is declared outside a function, it is visible and modifiable to all following functions. Also, the variable name is capitalized. A common programming convention is to capitalize all constant variables. Since VERSION refers to the version number of the program and won't be changing during execution, it's name is capitalized.

The modifier extern is a more advanced feature used when multiple source files all need to have the variable in their scope. It's a good idea to put the extern modifier in front of all global variables even if you don't have multiple source files. Some compilers, such as gcc, will report warnings when a variable is declared to be of type extern.

The other type of variable is local. Local variables can only be seen from within the function they are defined within. If a variable is defined in main, it can only be seen in main.

#include <stdio.h>

void print_an_int()
{
   int number = 100;
   printf("%d\n", number);
}

main()
{
   int number = 50;
   
   printf("%d\n", number);
   print_an_int();
   printf("%d\n", number);
}

Both variables in this program are local. They also have the same name, number. When the program executes, the following line is encountered first.

   int number = 50;

This sets the local variable, number in the function main to 50. After this, the number is printed to the screen. Then print_an_int is called. When print_an_int is called, main's local variables become invisible, or out of scope to print_an_int. Since main's variables are not in scope, print_an_int can define any variable names it wants, including names used in main. A new variable is created named number, and its value is set to 100. This doesn't affect number in main, because that variable is local to main and is out of scope within the function print_an_int. After print_an_int prints, it returns to main. At this point, all local variables in print_an_int are destroyed, because that function is completed. Main now has control in the program, and all of main's variables regain scope -- number is visible again. The value of number is still 50, because print_an_int didn't (and couldn't!) modify it. The output for the program is below.

12
34
56
$
Local variables are most always preferable to global variables. Local variables make data hiding possible and help protect accidental variable corruption.

Back to the Table of Contents

Calling by reference

Another way to pass variables to a function is by reference. When a variable is passed by reference, the variable's value can be modified from within the receiving function. Previously, we've passed information to functions by value. This means that when a function, such as print_age is called,

      print_age(age);

the value of age is passed to print_age. Inside the function, the value of age is passed as the first argument. The value is received in the function, and the new variable, your_age is assigned to the value.

void print_age(int your_age)
{
   printf("You're %s years old.\n", your_age);    /* print age */
   return;
}

Passing by reference makes the variable in the function the same as the variable it is passed. The memory addresses of the two variables are the same, so if the passed variable is modified, the value is changed in the rest of the program. The syntax for reference passing is an & in front of the function prototype's argument.

#include <stdio.h>

void print_an_int(int &num)
{
   printf("print_an_int:%d\n", num);
   num = num + 50;
}

main()
{
   int number = 50;
   
   printf("main:%d\n", number);
   print_an_int(number);
   printf("main:%d\n", number);
}

Output:

$ gcc -o numbers numbers.cc
$ ./numbers
main:50
print_an_int:100
main:100
$

Passing by reference has many advantages over passing by value. Since just the address to the variable is passed and not the value, memory space is saved.

Back to the Table of Contents

Pointers

A pointer is a variable that contains a memory address, not a value. Pointers are said to be pointing to a memory address. When a pointer is declared, it's value is essentially a random memory location. In order to use a pointer, it must have a variable or structure to point at in memory.

Pointers are useful when passing data to and from functions. When a pointer is passed, it is the same thing as passing a reference to a variable. The data's memory location is passed, not the value in the location. This is especially valuable with large data structures or arrays, because the memory address of the first element is passed, and that is all. Imagine a 10 gigabyte array being passed by value -- what a waste of memory!

The assignment statement for a pointer has the following format :

data_type *pointer_name;

The data type field can be any of the generic data types, such as int, float, double or any user defined data types, such as a struct. After the data type field, the pointer name is given, preceeded by an asterisk (*). The asterisk is what defines the variable as a pointer.

Pointers have two ways to be assigned. The first, and easiest to understand, way is when a normal variable has already been created. With a statement such as :

int *pointer = variable;

The value of pointer is assigned to the memory address of variable.

If the pointer is not meant to be assigned to the memory address of a normal variable, the second, more complicated way of assigning a pointer is needed. When a pointer is created, it's value is set to point at a free memory location. Even though a pointer is defined to be of a certain type, the size in memory that the data the pointer points to is not defined. So, when you declare a pointer of a data structure, or even an integer, you must allocate space for it's data. The syntax is below.

int *pointer = malloc(sizeof(int *));

The function malloc() is used to allocate memory space for the pointer's data. malloc takes an argument of size in bytes. The function sizeof() is used to get the size of an integer pointer. With these two functions, you can allocate space for a pointer's data.

Here is a sample program to show what is possible with pointers.

/* Program that creates an info structure, loads it */
/* with information and prints it to the screen. */

#include <stdio.h>

/* define the data structure */

struct __person {

   float height;
   float weight;
   
   int age;    
   
   char *name;
};
typedef struct __person person_t;

person_t *new_person()
{
   /* Define the pointer and allocate memory for it */
   
   person_t *person;
   person = malloc(sizeof(person_t *));

   /* Load the data structure with some information */
   
   person->height = 5.6;
   person->weight = 180;
   person->age    = 25;

   person->name = malloc(strlen("Bob"));
   person->name = "Bob";                    

   /* Return a pointer to the pointer */
   
   return person;
}

main()
{
   /* Define a pointer to a person_t structure */

   person_t *info;

   /* Have info point to the structure the function creates */

   info = new_person();

   /* Print all the information */
   
   printf("Hello, %s.  You are %i years old.\n", 
      info->name, info->age);  
   printf("You are %f feet tall, and weigh %f thousand pounds.\n",
      info->height, info->weight);
}

Back to the Table of Contents

Using the ANSI C standard library

The ANSI C standard library is not a part of the C language, but it is usually included with a C compiler. The library is made up of pre-written functions that deal with file I/O, Inter Process Communication (IPC), and math, etc. We've already used a few standard library functions, such as printf and scanf.

Along with the standard library, manual pages (called "man" pages for short) are available. Try typing "man scanf". You should see output similar to this :

SCANF(3)            Linux Programmer's Manual            SCANF(3)

NAME
      scanf,  fscanf,  sscanf,  vscanf, vsscanf, vfscanf - input
      format conversion

SYNOPSIS
      #include 
      int scanf( const char *format, ...);
      int fscanf( FILE *stream, const char *format, ...);
      int sscanf( const char *str, const char *format, ...);
                                                       
      #include 
      int vscanf( const char *format, va_list ap);
      int vsscanf( const char *str, const char *format, va_list ap);
      int vfscanf( FILE *stream, const char *format, va_list ap);
                                                                            
DESCRIPTION
      The scanf family of functions scans input according  to  a
      format  as  described below.  This format may contain con-
      version specifiers; the results from such conversions,  if
      any,  are stored through the pointer arguments.  The scanf
      function reads input from the standard input stream stdin,

      ....

Information on all the standard library functions can be obtained in the same manner. The man pages show information on related functions, which #include statements to use, function call syntax, etc. The man page for the function scanf shows five other related functions and the syntax for each one. This can be an extremely valuable reference tool.

Back to the Table of Contents

Some final thoughts

C programs can be very complicated, but a consistent structure and a liberal commenting regime can make even the most complicated programs understandable.

This tutorial is but an overview of the C programming language. If you want to learn more about the details of the language, both bookstores and the WWW are full of tutorial and reference books. With these resources, you shouldn't have too many problems progressing in programming proficiency.

Back to the Table of Contents

Daniel Thomasset
Sept 9, 1999