C PROGRAMMING


Ivor Page

Index

Click on any of the following to go to that section.
  1. A Simple Program
  2. Built In Types
  3. Declarations and Assignments
  4. Implicit type conversion
  5. Operators and Precedence
  6. Stream I/O
  7. Increment and Decrement Operators --, ++
  8. Boolean Expressions
  9. If Statements
  10. Compound Statements
  11. Arrays
  12. Strings
  13. Loops
  14. Break Statements
  15. Switches
  16. Functions
  17. Recursion
  18. Pointers
  19. Structures
  20. Typedef
  21. The Pre-Processor,

A Simple Program

      #include < iostream.h>        //  The C++ I/O library   

            /*  comments in C  look like this, 
                 and can go over many lines. */   
            //  or they are single line comments like this:        

            //  PROGRAM TO ADD TWO NUMBERS

      main()
      {					
            int  i,  j,  k;	
            cin >> i,j;          // read in two ints     
            k = i+j;
            cout << endl 
                   << "The sum of "  
                   << i 
                   << " and " 
                   << j 
                   << " is " 
                   << k; 
      }	

Built In Types

TYPEPC SUN COMMENT
long double8064A very long float
double6464A double precision float
float3232A single precision float
unsigned long 3232An int w/o sign
long3232A large int
unsigned int1632An unsigned int
int1632A signed int
short int1616a signed short int
char88one byte

Declarations and Assignments

  
      int i, j, k; 	   //  Variable names must begin with an alpha.
      char c1,c2;          //  Note the order in a declaration:
      float x,y,z;         //  The type specifier (int, float) is followed   
      double w;            //      by a list of variable names.

We can assign variables of the same type:

      x = y*z;   
      i = j+k;

Or we can use explicit conversion (Casts)

      x = (float)i + y;    // Changes i to a float before adding  

Implicit Type Conversion

We can also rely on implicit type conversion:

      int i, j, k;
      char c1,c2;
      float x,y,z;   
      double w;

      i = (x*j) + w;

The assignment does the multiply using floats, then the add using doubles, the result is then converted to int.

             
      z = (c1+j)*w;  

This assignment does the add using ints, the multiply using doubles, the result IS then converted to float.

Operators and Precedence

OperatorAssociativity Type name
(, )left to rightparenthesis
++, --, +, -, !right to leftunary
*, /, %left to rightmultiplicative
+, -left to rightadditive
<, <=, >, >=left to rightrelational
= =, !=left to rightequality
&&left to rightlogical AND
||left to rightlogical OR
=, +=, -=, *=, /=, %= right to leftassignment

Examples

Expression Meaning
x = a + b*c/d - e;(a + ((b*c)/d)) - e
y = a%b/c*e;((a%b)/c)*e
z = a = b*c;a = b*c then z = a
w = --a + a++;a = a-1 then w = a+a then a = a+1
if (x= = y && a != b|| c>d) if (((x= =y) && (a != b)) || (c > d))
x = a + b + c;(a + b) + c
x = f(a) + g(b); no rule for order in which functions are called
x = f(++a, h(a));no rule for order of evaluation of function arg's
x += y -= z *= w;x = x + (y = y - ( z = z*w))

Stream I/O

The input and output operators are << and >>:

       cin >> x >> y >> z;
       cout << endl << "The answer is " << w;   

Special characters

There are special characters that may appear in strings:

DenotationName>
\n newline
\0 end of string (zero value)
\t tab
\\ backslash character
\' single quote
\" double quote
To print the message:

        She said, "A \ quotes the char that follows it."  

on a newline, we would write:

       cout << "\nShe said, \"A \\ quotes the char that follows it.\"";  

OR

       cout << endl << "She said, \"A \\ quotes the char that follows it.\"";  

Notice that the field "endl" is an alternative to "\n".

Increment and Decrement Operators --, ++

      ++i;            // means i = i + 1;
      i++;            // also means   i = i + 1;   

The position of the operator determines which value of i is used in an expression:

      a = ++i  +  j++;   

Here i is incremented before it is used, and j is incremented after it is used.

If i=5 and j=8 before the assignment, we get i = i+1, then a = i + j, then j = j+1. So after the assignment, i=6, a=14, j=9

Avoid overly complex uses of these operators, such as:

      a = x[++i] - y[10 - i--];   

Boolean Expressions

Boolean variables are actually integers. A value of zero implies FALSE, while any other value implies TRUE.

      int a,b,c;
      if (b!=c) statement       // same as  if (b-c) statement   
      if (b!=0) statement       // same as  if (b) statement 
      if (b==c) statement       // same as  if (!(b-c)) statement   

If Statements

If statements have form:

      if (Boolean expression) statement   

Or

      if(Boolean expression) statement1   
            else  statement2

Examples:

      if (a < b) x = y;                // Note semicolon is part of statement1    
            else x = z;                //     so it appears even before else

      if (a == b) x = y;               // Note == for the relation equals 
            else  if (a != c) x = z;

      if ((a < b) && (b < c)) u = v;   // C uses short circuit evaluation 
            else if (a >= b) u = -v;   //    of Boolean expressions. Once the  
        else u = 0;                    //    value of the result is known, 
                                       //    computation of the Boolean stops 

Compound Statements

Everywhere a statement appears we can substitute a compound statement, enclosed in curly braces:

         {statement statement statement ... }   

      if (a > b) {
            e++;                         // Increment  e.   
            if (c == d) {
                  x = y;
                  cout << "\nb < a";  
            }
      }                    
      else {
             cout << "\nb >= a";         // Indentation adds readability.  
             x = -y;                
      }

Arrays

Arrays are one dimensional "rows" of equal sized objects:

      char  c1, c2, ck[20], cm, cn[10]; // means ck[0 ... 19],  cn[0 ... 9]   

You can have rows of rows :

      double xx[10][20];     // means xx[0 ... 9][0 ... 19], 200 elements   
    
      for (i = 0; i < 10; i++)                                
            for (j = 0; j < 20; j++) 
                  xx[i][j] = 0.0;

Strings

Arrays of chars hold strings. A char of value zero means "end of string." A string is enclosed in double quotes; a single char is in single quotes.

      char message[17] = "\nThe end is near"; // includes 1 char for end marker   
      message[1] = 't';                       // the array begins in message[0]

You can only initialize an array when it is declared, as in message above and:

      int table[] = {0,1,2,4,8,16,32,64};       // size is figured by compiler   
      int mat[2][3] = {{1,2,3},{4,5,6}};

Loops

There are several kinds of loops available in C:

      for ( i = 0; i < 100; i++) statement   // means: for i starting at 0,   
                                             //   while i  < 100, increment
                                             //    i by 1 on each iteration   

      for( ; ; ) statement                   // means: loop forever 

      for(i = 0;i < 20;i++) a[i] = 0;        // means: a[0,1, ..., 19] = 0 

      while(Boolean expression) statement    // execute statement zero or more   
                                             // times while expression is true

      do statement while(Boolean Expression) // statement is executed at least
                                             // once, and repeatedly while
                                             // expression is true

Example:

      i = 0;
      while(a[i]) b[i] = a[i++];        // copies a into b until 0 is found   

Assignment statements return values, so we can do the following:

      i = 0;
      while((b[i] = a[i++]));           // copies a into b until 0 is copied    

Note the null statement. All the work is done in the assignment.

The parens around the assignment are needed, otherwise it would be treated as a Boolean expression; "=" is not a Boolean operator

"Give me a break"

The break statement causes an exit from the compound statement in which it appears:

      i = 0;
      for( ; ; ){
            a[i] = b[i];
            if (!a[i++]) 
                  break;      // get out of for loop   
      }
      i = 0;
      while (b[i]!=27){
            if (b[i] == 0) 
                  break;      // get out of while loop        
            a[i] = b[i++];
      }

Switches

      char  ch;
      switch (ch) {
            case 'a': 
                  cout << ("\nIt was an a";
                      -
                  break;                // Goto end of switch statement   
            case 'b':
                  cout << "\nIt was a  b";
                      -
                  break;                // Don't leave out the break   

                         /* code for other cases goes here */

            default:                    // For cases not covered
                  cout << "\nIt wasn't alphabetic";                
                  break;                                        
      }                                 // End of switch 

Functions

Function declarations have the following form:

      return_type function_name(argument_list) statement    

Example:

      int sum_square(int i, int j)    
      {                          
            return i*i + j*j;
      }

All arguments are passed by value, so the caller's arguments cannot be changed. If the function returns something there must be a return statement. This function would be called as follows:

      sum_sq_ab = sum_square(a,b);   

Here, a and b can't be altered by the function.

Functions need not have arguments and need not return results:

      void print_results(void)             /* void means no arguments */   
      {
            cout << "\nThe earnings details are "  << salary << ": " << tax;
      }

Local variables can be declared in functions. Their scope is the body of the function:

      int magnitude_ave(int a, int b, int c)
      {
            int sum;              // Scope of variable name is limited to    
                                  //   block in which it is declared. 
            sum = a + b + c;                
            if (sum > 0) 
                  return  sum/3;  // Can have many return statements. 
                                  //    When one is executed the rest
            else                  //    of the fn's code is skipped.   
                  return  - sum/3;       
      }
 

The ANSI standard requires that a program file begins with prototypes for all functions declared in that file. The prototype for the above function would be:

      int magnitude_average(int, int);   

OR

      int magnitude_average(int x, int y);   

You can give your arguments names in the prototype. If you use prototypes, then your function bodies can appear in any order in the program source file.

Recursion

A function can call itself. This is quite common in many data structures. We'll look at some simple exercises first:

      long factorial(int num){
            if (num <= 1) return 1;              // terminal condition   
            else return num*factorial(num-1);    // recursive call 
      }

The call:

      factorial(3);   

returns 3*factorial(2) = 3*2*factorial(1) = 3*2*1 = 6

We must beware of situations where recursion seems the right technique, but may cause very long run time. The Fibonacci numbers are the infinite sequence: 1, 2, 3, 5, 8, 13, 21, 34, 55, where Fib(n) = Fib(n-1) + Fib(n-2). This suggests the following recursive function:

      long fib(int n) {
             if(n<=1) return 1;
             else return fib(n-1) + fib(n-2);    
      }

The run time of this function is exponential in num, because the procedure keeps repeating the same calculations:

                                          fib(6) 
                  = fib(5)                  +                   fib(4)
       = fib(4)       +        fib(3)       +        fib(3)       +        fib(2)
=  fib(3)  +  fib(2)  +  fib(2)  +  fib(1)  +  fib(2)  +  fib(1)  +  fib(1)  +  fib(0)
Notice that the width of this tree doubles on each level. Its height is n. That implies 2n calculations.

A much better way is to iterate from 1 upwards:

      long fib(int n) 
      {
            long last=1, prev=0, newfib,i;      // The run time of      
            for(i = 0;i < n;i++) {              //    this function is   
                  newfib = last+prev;           //    O(n), linear
                  prev = last;                  //    in n.
                  last = newfib;
            }
            return last;
      }

Pointers

Pointers are integers, but are treated as special types:

      int i, j, k, *pi, *pj;    // 3 ints and 2 pointers to ints  
      pi = &i;                  // pi  is assigned the address of i   
      pj = &j;                  // pj  is assigned the address of j   
      *pi = 3;                  // read as "what pi points to, becomes 3"   
      *pj = *pi;                // Same as j = i;  
 

An array name is really a pointer:

      int a[10];                // Reserve space for 10 ints and  
                                //   assign address of the first of them to a. 
      int *b;                   // Declare a pointer to an int.
      b = &a[0];                // a  and  b  are now identical in meaning,   
                                //    b[3] is the same as a[3].  
      *b = 23;                  // Same as a[0]=23 or *a=23  or  b[0]=23
      *(b+1) = 67;              // Same as a[1]=67 or *(a+1)=67 or b[1]=67   

Pointers are especially useful in dynamic (heap) storage:

      int *pi;
      pi = (int *)malloc(100*sizeof(int));   

This statement reserves space for 100 ints on heap, pi points to the first. When calling malloc():

We have to give the size in bytes of the space needed: 100*sizeof(int)

We also have to cast the result to the pointer type needed: (int *)

Now we can write pi[k] or *(pi+k) to access a value from the array. The array remains on the heap until it is freed by free(pi).

Since function arguments are passed by value, we have to use pointers as arguments if we want to modify values owned by the caller :

      void function swap(int *p1, int *p2)       // Pass in the 
      {                                          //    addresses of the 
            int temp;                            //    actual arguments.   
            temp = *p1;                          // To swap the ints 
            *p1 = *p2;                           //    u and v, the call      
            *p2 = temp;                          //    would be
      }                                          //    swap(&u,&v); 

When passing arrays into functions, remember, an array name is really a pointer to the first element of the array.

      void bubble_sort(int *pa, int size)
      {
            int i,j;
            for(i=size -1; i >= 0; i--)
                  for(j=0; j < i; j++)
                        if(pa[j] > pa[j+1]) swap(&pa[j],&pa[j+1]);   
      }

To sort the integer array table[20], the call would be:

      bubble_sort(table, 20);   

OR

      bubble_sort(&table[0], 20);      

Structures

Structures are like records in Pascal. We first have to define the fields:

      struct Date {
            int day,  month,  year;
            char month_name[4];
      };                                   // Note the semicolon   

Then we can declare structs as follows:

      struct Date day1;
      struct Date day2 = {4, 11, 1933, "apr"};   // Can initialize them 
                                                 //   just like arrays.   
      struct Date *pd;                           // Can have pointers to them  
      struct Date my_dates[20];                  // Can have arrays of them

We refer to elements of a struct by using the syntax struct_name.element_name:

      day1.day = 5;
      my_dates[2].year = 1968;
      struct Date *pd;                            // A pointer to a Date.
      pd = (Date *)malloc(sizeof(Date));          // Make a Date on the heap   
                                                  //   and make  pd point to it   
      *pd.month = 11;                             //   and set its month to 11

When addressing a field of a struct via a pointer, we can use the right arrow (made from a minus sign followed by a greater than sign):

      pd -> month = 10;     // same as *pd.month = 10

Structures as Function Arguments

Structure arguments are passed by value. The caller's value will not be changed by the function.

      void print_date(struct Date d)       // can't change any part of d    
      {                                    // since it is passed by value   
            cout << month_name << day << year; 
      }

To change the fields of the caller's struct, a pointer must be passed in:

      void add_one_to_day(struct Date *ptrd)   
      {         
            ptrd -> day++;  
      }

Then in main(),

       struct Date first = {1,1,1995,"Jan"};   
       add_one_to_day(&first);

Structures in Lists

We can also use them in linked lists:

      struct List_node {
            int data; 
            struct List_node *next;   
      };

Then we can declare a pointer to one of these:

      struct List_node *head;         /* points to the first node */   

Here is a function that searches a linked list for a data value:

      struct *List_node search(int key_value)         
      {                                              // Returns a pointer   
            struct List_node *ptr = head;            //  which is NULL if 
            while(ptr!=NULL)                         //  search fails, or 
                  if (ptr -> data == key_value)      //  points to the node   
                        return ptr;                  //  containing 
                  else ptr = ptr -> next;            //  the key_value
            return NULL;                             //  if found
      }

Typedef

Typedefs are a way to create synonyms for previously defined types:

      struct card {                       // definition   
            int value;   char suite[8];
      };

      typedef struct card Card;           // declare synonym   

We can combine the definition of the struct and the typedef:

      typedef struct { 
            int value;
            char suite[8];   
      } Card;
      typedef struct Card {    
            int value;
            char suite[8];
      };

Either way, we can declare Cards as follows:

      Card ace_spades = {1,"spades"},wild;   

The struct tag is not needed for typedefs in declarations or function argument lists.

Arrays and Typedef

To pass 2D or higher dimensional array into functions, use a typedef:

      typedef double Matrix[4][4];

      void mat_mult(Matrix m1,Matrix m2,Matrix m3)   
      {
      int i,j,k;
      for(i = 0;i < 4;i++)
            for(j = 0;j < 4;j++) {
                  m3[i][j] = 0.0;
                  for(k = 0;k < 4;k++)
                        m3[i][j] += m1[i][k]*m2[k][j];   
            }
      }

The Pre-Processor, #include

Lines in the source file beginning with "#" are handled by the preprocessor. The following lines include header files:

      #include < math.h>         //  sin(), cos(), log(), etc.
      #include < stdlib.h>       //  malloc(), free(), exit() and lots more   
      #include < string.h>       //  string copy and compare etc
      #include < time.h>         //  time structs and functions
      #include < assert.h>       //  support for assertions
      #include < ctype.h>        //  tests on chars and char conversions

Macros can be defined:

      #define   pi   3.14159    // replace all occurrences of pi by 3.14159   

We can define inline functions:

      #define CIRCLE_AREA(x) PI * (x) * (x)   

then in the program text:

      area = CIRCLE_AREA(4);           // area = pi * (4) * (4)   

Since only constants are involved, the expression is evaluated at compile time. The parens around the variables in the #define statement force correct evaluation when the macro is called with an expression:

      area = CIRCLE_AREA(c + 2);   // area = pi*(c+2)*(c+2)   

We also have #undef to undefine a macro name.

Conditional Compilation, #if

It is typical to have parts of the program conditionally compiled:

      #if !defined(NULL)                // evaluates to 1 if defined,   
            #define NULL 0              // and to zero if not 
      #endif 

This code defines NULL to be 0 if it isn't already defined. Every #if ends with a #endif.
#ifdef and #ifndef are short for #if defined and #if !defined.

We also have #elif and #else.

Say we have a header file called ray.h that is #included in file1.c and file2.c, and file2.c #includes file1.c. We don't want the compiler to read the header file twice, so we do the following:

      #ifndef _rayh   // On the first reading of this file, _rayh is not   
      #define _rayh   //   defined, so this line defines it and the rest   
                      //   of the header file is read

      /* body of header file goes here */ 

                      // If the file has already been read, _rayh is defined   
      #endif          //   and the rest of the header file is skipped