Miscellaneous Topics [Part 2]


Pointer to functions

How to declare pointers to functions

Example : The following prints the sine table or the cosine table,

       #include <stdio.h>
       #include <math.h>

       int main()
       {   double (*fa)();
           double x, dumx;
           char buffer[1000];
           int iwhich;
           char *ptr[2]={"sin", "cos"};
   
        L20:     
           printf("Do you want to print the sine table (1) or cosine table (2) ? ");
           gets(buffer);
           sscanf(buffer,"%d",&iwhich);
           if (iwhich != 1 && iwhich != 2)
               {printf("\n    *** Enter 1 or 2 please ***\n\n");
                goto L20;
               }
           if (iwhich == 1)
               {fa = sin;
               }
           else
               {fa = cos;
               }

           printf("\n     Degree      %s\n\n", ptr[iwhich-1] );

           for (x=0.; x<= 90.; x+=1.)
               {dumx = x/180.*PI;
                printf("    %3.0f       %10.4f\n", x, (*fa)(dumx));
               }

           return;
         }

It can be seen that to declare a pointer for function, we use, e.g.

double (*fa)();

Exercise : How to declare "fb" to be pointer for a function returning an integer ?

Ans :

int (*fb)();

Exercise : Are the following declarations the same

   (a)     double (*fa)();
           double x, b[100], *ptr;

   (b)     double (*fa)(), x, b[100], *ptr;

Ans : Yes. We may put them all on one line. "fa" is a pointer for a function returning a double. "x" is a double. "b[100]" an array of doubles. "ptr", a pointer to double.

Exercise : What is the difference between the following :

  1. double (*fa)();
  2. double *fa();

Ans : The first is to declare "fa" pointer to a function returning a double. The second is to declare a function prototype : "fa" is a function returning a pointer (for double).

Exercise : Write a program that does Simpson's Integration.

Ans :

       #include <stdio.h>
       #include <math.h>


       /*  This is the function to integrate g(x) = 6x3 + 3x -2 */

       double g(double x)
       {   return  6*x*x*x + 3*x - 2;
       }


       /*  This subroutine does Simpson's Integration.  We have to pass to it
           the address of the function to be integrated, and lower and upper
           limits of integration, and the number of divisions 
       */

       double simpson( double (*f)(double), double xlow, double xhigh, int ndiv)
       {    int mdiv, i;
            double h, h2, sum1, sum4, sum2, x;

            if (ndiv < 2)
                {ndiv = 2;
                }

            mdiv = 2 * ndiv;

            h = (xhigh - xlow)/(double) mdiv;
            h2 = h + h;

            sum1 = f(xhigh) + f(xlow);

            for (sum4=0., x=xlow+h, i=0; i<ndiv; i++, x += h2)
                {sum4 += f(x);
                }

            for (sum2=0., x=xlow+h2, i=0; i<ndiv-1; i++, x+= h2)
                {sum2 += f(x);
                }

            return  (h/3*(sum1 + 4.*sum4 + 2.*sum2));
        }

        int main()
        {      printf("Area is %12.4f\n", simpson(g, 0, 4, 10));
               return 0;
        }


Cast
(with introduction to sort subroutine and binary search subroutine)

Examples of cast operator to convert one data type to another :

       double x;
       x = (double) 2;                      /* This will convert an integer into double. */
       h = (xhigh - xlow)/(double) mdiv ;   /* This is from the above program.
                                               "mdiv" is converted into double first.
                                               Usually compiler will does the
                                               conversion for us, without our
                                               using a cast */

The syntax is
(data type) expression

Mostly, cast is used to convert pointers. e.g.

        struct 
            { char name[40];
              int age;
              double wages;
            }  rec;

        char *ptr;
        ptr = (char *) &rec;    /* Since ptr stores only the address of character,
                                   but &rec is address of a structure. Hence, if
                                   we want to assign it to ptr, we have to change
                                   its data type first using a cast. */

Exercise : The following are 2 subroutines in <stdlib.h>. The first one does binary search of a sorted list, the second does sorting.


void *bsearch(void *key, void *base, size_t n, size_t size, 
                                   int (*cmp)(void *keyval, void *datum))

void qsort(void *base, size_t n, size_t size, int (*cmp)(void *, void *))
(Notice that "size_t" is usually defined to be "unsigned int" in library header files.)
  1. How many arguments bsearch has, and what data types are they? What does bsearch return ?
  2. How many arguments qsort has, and what data types are they? What does qsort return ?

Ans :

  1. bsearch has 5 arguments :
        void *key         is the address where "key" is stored.
        void *base        is the address where the sorted list is.  The list must be
                            sorted first (in ascending order) before binary search can
                            be carried out.
        size_t n          Number of objects in the sorted list.
                          ("Object" may be common data types, or structures, or pointers, 
                             .... etc.)
        size_t size       Size in byte of one object.
        int (*cmp)(void *keyval, void *datum)
                          Address of a function.  That function has 2 arguments,
                          both of type "pointer".  That function returns an integer.
                          It returns    0   if    keyval = datum
                                       <0   if    keyval < datum
                                       >0   if    keyval > datum
            

    bsearch will return NULL if key is not found, or, if key is found, the address.

  2. qsort has 4 arguments :
        void *base        address where the array to be sorted is.
        size_t n          Number of objects in the array.
        size_t size       Size in byte of one object.
        int (*cmp)(void *, void *)
                          Address of a function, which returns an integer based
                          on whether the two are equal, less than, or greater than.
                          Same as that in bsearch above.
            

    qsort does not return any value.

It may be noticed that "void pointer" (void *) are used in the above two. In fact, "void pointer" can take the place of any pointer type, and is called a "generic pointer". Usually "void pointer" is also a character pointer.

Below is an example of how to use the above two subroutines.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct
            { char  name[40];
              int   age;
              double wages;
            }  Mrec;

int namesort(Mrec *pa, Mrec *pb)
{   int idummy;
    idummy = strcmp(pa->name, pb->name);      /* Use "string compare" function in 
                                                 <string.h>. */
    return idummy;
}

int main()
{   Mrec a[4], *ptr;
    char buffer[1000];
    int i;

    /*  We artifically put data into the 4 records */

    strcpy(a[0].name, "Wu Siu Yan");      /* strcpy(..) = string copy, subroutine in
                                             <string.h>. */
    strcpy(a[1].name, "Tom");
    strcpy(a[2].name, "Mary");
    strcpy(a[3].name, "Jack");

    a[0].age = 51;
    a[1].age = 25;
    a[2].age = 30;
    a[3].age = 47;

    a[0].wages = 1000;
    a[1].wages = 1500;
    a[2].wages = 2200;
    a[3].wages = 1600;
    
    qsort(a, 4, sizeof(Mrec), namesort);  /* Whan array is passed as argument,
                                             the compiler will pass the address
                                             of its first element */

    /*  Print the sorted result */
    for (i=0; i<4; i++)
        {printf("%-40s %3d %5.0f\n",a[i].name, a[i].age, a[i].wages);
        }


    /*  Accepts a key, and see if it is in the list, using binary search */

 L20:
    printf("Enter name -> ");
    gets(buffer);
    if (strcmp(buffer, "****") == 0)
        {return 0;
        }

    ptr = (Mrec *) bsearch(buffer, a, 4, sizeof(Mrec), namesort);
    /*  Since bsearch returns a void pointer, we must cast the
        pointer to that of (Mrec *), because "ptr" is a Mrec pointer. 
        All subroutines that returns a void pointer must be
        cast into a pointer of proper type before use. */
    
    if (ptr == NULL)
         {printf("    *** No record with name %s found ***\n", buffer);
         }
    else
         {printf("   *** Found ***\n");
          printf("%-40s %3d %5.0f\n",ptr->name, ptr->age, ptr->wages);
         }

    goto L20;
}

Exercise : If we wish to sort the records according to age, (youngest first) what should be changed ?

Ans : The comparison subroutine as well as the call statement has to be changed,

int agesort(Mrec *pa, Mrec *pb)
{   return (pa->age - pb->age);   
    /* If oldest first, then "return (pb->age - pa->age); */
}

/*   In the main program, the call statement is now  */

qsort(a, 4, sizeof(Mrec), agesort);  

Exercise : If we wish to sort the records according to wages (highest wages first), what should be changed ?

Ans : The comparison subroutine as well as the call statement has to be changed,

int wagesort(Mrec *pa, Mrec *pb)
{   return (pb->wages - pa->wages);
}

/*   In the main program, the call statement is now  */

qsort(a, 4, sizeof(Mrec), wagesort);  

Exercise : If we wish to sort according to name first, but if the names are the same, then sort it according to age (eldest first). What should be the sort subroutine and the call statement ?

Ans :

int nameagesort(Mrec *pa, Mrec *pb)
{   int iret;
    iret = strcmp(pa->name, pb->name);
    if (iret == 0)
         {return (pb->age - pa->age);
         }
    else
         {return iret;
         }
}

/*   In the main program, the call statement is now  */

qsort(a, 4, sizeof(Mrec), nameagesort);  

Hence it may be seen that "qsort(..)" and "bsearch(..)" are very versatile, and we may sort in any way we like.
Also, the C library from Free Software Foundation contains lots and lots of subroutines not in the ANSI C specification. It is advisable that you inspect them. You may use the "info" (information) command, and then choose "Libc" (library C subroutines).

(Note : I use Debian LINUX, the item is "Libc". The name may be "glibc" ... in LINUX from other distributors. Also, some distributors may even omit "Libc" and include "c++" library instead.)


Global variable, local variable

Dynamic memory allocation, Static memory allocation

"extern", "static" declarations

Scope

Exercise : What are global variables and local variables. Can all subroutines access global variables ? Can an outside subroutine access a local variable ? (If you are unfamiliar with these concepts, it is advisable that you read Qbasic in this website first.) Point out which are global variables and which are local variables in the following program :

#include <c.h>  
/*You may use  <stdio.h>,  <stdlib.h>,  <string.h> instead */

double A[100];
int Iflag, Ncount;

int compare(double *a, double *b)
{     return (*a - *b);
}

int main()
{     char buffer[1000];
      int i;

      Ncount = 0;
 L20:
      printf("Enter a number (**** = end) -> ");
      gets(buffer);
      if (strcmp(buffer, "****") == 0) 
           {goto L100;
           }
      sscanf(buffer, "%lf", &(A[Ncount]));
      Ncount++;
      goto L20;

      /* This sort the numbers in ascending order */
 L200:
      qsort(A, Ncount, sizeof(double), compare);

      /*  This prints the sorted list */
      for (i=0; i<Ncount; i++)
           {printf("%10.3f",A[i]);
           }
      printf("\n");

      return 0;
}

Ans :

Any variable declared outside main() or other subroutines are global variables. Those declared within main() or subroutines are local variables.

Hence "double A[100]; int iflag, Ncount;" are global variables.

Whereas "char buffer[1000]; int i;" are local variables.

Global variables may be accessed by all subroutines, but not so with local variables (see "scoping" below).

Global variables are assigned static memory, whereas local variables are assigned dynamic memory (unless declared with "static"), and they are taken from the "run time stack".

Global variables are initialized to 0 by the compiler, local variables are not. Hence we must assign values to local variables before using them.

Exercise : In a large program with many subroutines, very often, the subroutines are put in different files. e.g.

FILE "/home/tom/t63a.c" contains

     double A[100];
     int Iflag, Ncount;

     int ascendcmp(double *a, double *b)
     {     return (*a - *b);
     }

     int descendcmp(double *a, double *b)
     {     return (*b - *a);
     }

FILE "/home/tom/t63main.c" contains

#include <c.h>  

int main()
{     char buffer[1000];
      int (*pf)();
      int i, ichoice;

      Ncount = 0;
 L20:
      printf("Enter a number (**** = end) -> ");
      gets(buffer);
      if (strcmp(buffer, "****") == 0) 
           {goto L100;
           }
      sscanf(buffer, "%lf", &(A[Ncount]));
      Ncount++;
      goto L20;

      /* This sort the numbers in ascending order */
 L100:
      printf("Do you want to sort it in ascending order (0) or descending order (1) ? \n");
      gets(buffer);
      sscanf(buffer,"%d",&ichoice);

      if (ichoice != 0  && ichoice != 1)
            {printf("   *** 0 or 1 please ***\n");
             goto L100;
            }

      if (ichoice == 0)
           {pf = ascendcmp;
           }
      else
           {pf = descendcmp;
           }

      qsort(A, Ncount, sizeof(double), pf);

      /*  This prints the sorted list */
      for (i=0; i<Ncount; i++)
           {printf("%10.3f",A[i]);
           }
      printf("\n");

      return 0;
}

Then what modifications are needed in order than main(..) may use global variables "A[100], Ncount" as well as subroutines "ascendcmp(..), descendcmp(..)" ? And what should the compile command be ?

Ans : We should modify "/home/tom/t63main.c" to be

#include <c.h>  

extern double A[100];
extern int Iflag, Ncount;
extern int ascendcmp(double *, double *);
extern int descendcmp(double *, double *);

int main()
{     char buffer[1000];
      int (*pf)();
      int i, ichoice;

        .....
        .....
        .....

}

And the compile command should be

gcc -Wall t63main.c t63a.c -I/home/tom/include/

"extern" is a C keyword. It informs the compiler that those variables as well as subroutines are defined outside this file.

Exercise : Suppose we modify file "/tom/t63a.c" to be

     static double A[100];
     static int Iflag, Ncount;

     static int ascendcmp(double *a, double *b)
     {     return (*a - *b);
     }

     static int descendcmp(double *a, double *b)
     {     return (*b - *a);
     }
i.e. with "static" added, and then compile with
gcc -Wall t63main.c t63a.c -I/home/tom/include/
Do you think it will work this time?

Ans : No. "static" has another meaning here (ordinarily, it would mean that the variable should be given static memory and not dynamic memory.) It informs the compiler that those global variables or subroutines are NOT to be accessed by files outside. They are considered to be "private". Therefore the compilation will fail.


Beware of initialization problem associated with "static" and "dynamic" variables

Exercise : Study the following program

#include <stdio.h>

void suba(void)
{   static int k=100;
    printf("%4d\n", k);
    k++;
}

int main()
{   int i;
    for (i=0; i<5; i++)
        {suba();
        }
    return 0;
}

What will it produce ? Suppose we remove "static",

#include <stdio.h>

void suba(void)
{   int k=100;        /*  "static" removed  */
    printf("%4d\n", k);
    k++;
}

int main()
{   int i;
    for (i=0; i<5; i++)
        {suba();
        }
    return 0;
}

What will the program produce ?

Ans : With "static", it will produce "100, 101, 102, 103, 104".

Without "static", it will produce "100, 100, 100, 100, 100".

It is because, "static" informs the compiler to allocate static memory to "k", and also give it initial value "100". Hence on every call, "k" will NOT be given initial value, and "k++" will increment "k".

But without "static", the compiler will assign "k" memory in the "runtime stack". Also the compiler will compile the initialization "int k=100" into

     int k;
     k = 100;
Hence everytime the subroutine is called, "k" has value "100".


Scoping

Exercise : Study the following program

#include <stdio.h>

int K=100;
void suba(void) 
{    printf("suba - %d\n", K);
     printf("suba - %d\n", L);
     printf("suba - %d\n", M);
     return;
}

int L=200;
void subb(void) 
{    printf("subb - %d\n", K);
     printf("subb - %d\n", L);
     printf("suba - %d\n", M);
     return;
}

int M=300;
void subc(void) 
{    printf("subc - %d\n", K);
     printf("subc - %d\n", L);
     printf("subc - %d\n", M);
     return;
}

int main()
{    suba();
     subb();
     subc();
     return 0;
}
What are the global variables ? What will be the outcome of the program ?

Ans : When you compile it, there will be many compilation errors. In short, a global variable is "visible" only to the subroutines below it, and not above it. Had we put

#include <stdio.h>

int K=100;
int L=200;
int M=300;

void suba(void) 
{    ....
}

void subb(void) 
{    ....
}

void subc(void) 
{    ....
}

int main()
{    ....
}
There will not be any problem.


Pre-processor directives

   #include <filename>     /* File in system directory, "/usr/include/" ... */
   #include "filename"     /* File in current directory */
                       
   (Notice that "#include" tells the pre-processor to include content from the
    files.  There is no restriction on the content of those files !  Although
    mostly they are header files and contain function prototypes or constant
    definitions.   But we may put "global variables, or Master Record definitions etc."
    in them.  Also those files may contain "#include .... " too !)

   #define token1  token2   /* Pre-processor will replace very
                               occurrences of token1 by token2. 
                               You may regard it as "function" definition
                               or "macro" expansion. */

   Exercise :   Will the following work as expected ?

            #define  prod(x,y)  x*y
                 ....
                 ....
            (in the program)

                y = prod(a+b,c);

   Ans :  No.  Pre-processor will convert it to " a+b*c ".
            Correct one should be   

            #define prod(x,y)   (x)*(y)              

   #define token  numeric-value

   #if (token)
         ....           /* If token is true (i.e. non-zero), this part
         ....              will be compiled. */
   #else
         ....           /* This part will be compiled if token is false. */
         ....
   #endif



   (Alternate form)

   #define token    /* define */
   #undef token     /* undefine - cancel the previous "define" */

   #ifdef   token       /* if defined token */
   #ifndef  token       /* if not defined token */
         ....
         ....
   #else
         ....
         ....
   #endif

There are documentation about "pre-processor" in LINUX, and you may read them for more.


A C program as a system command

In the example programs, we used

int main()
But the full form is (both may be used)
int main(int nargv, char **argv, char **env)
int main(int nargv, char **argv)
You may view it as
int main(int nargv, char *argv[], char *env[])
int main(int nargv, char *argv[])
Suppose the program is in the file "/home/tom/t20.c" and you compile it with
gcc -Wall -o t20 t20.c -I/home/tom/include/ -lm
i.e. You require the output file be named "t20" and not the usual "a.out". Then when you run the program with
t20 tom mary jack
the operating system will put 4 in "int nargv" (number of arguments), and put "t20", "tom", "mary", "jack" in certain places and passes their addresses to "char *argv[]", i.e.
    argv[0]   will point to "t20"
    argv[1]   will point to "tom"
    argv[2]   will point to "mary"
    argv[3]   will point to "jack"
Also the operating system will put the addresses of "environment variables" in the character pointer array "char *env[]", the last being indicated by a NULL value.

The following program will simply print out those arguments and environment variables :

#include <stdio.h>

int main(int nargv, char *argv[], char *env[])
{    int i;
     char *ptr;

     printf("Number of arguments is %d\n"
            "And they are : -\n\n" , nargv);

     for (i=0; i<nargv; i++)
          {printf("%s\n", argv[i]);
          }

     printf("Environment variables are :-\n\n");

     for (i=0; ; i++)
          { ptr = env[i];
            if (ptr == NULL)
                 {break;
                 }
            printf("%s\n", ptr);
           }

     return 0;
}

Hence we may write C programs and use them as commands.

The following is a program that will convert a MSDOS file (where a line ends with '\r' '\n' ) into a LINUX file (where a line ends with only '\n'), or vice versa.

gcc -Wall -o convert t70.c
convert infilename outfilename
#include <stdio.h>

int main(int nargv, char *argv[])
{     int ierror, ichoice, c;
      FILE *fin, *fout;

      if (nargv < 3)
          {printf("input file as well as output file needed\n");
           return 1;
          }

      ierror = 0;
      if ( (fin=fopen(argv[1],"r")) == NULL )
          {ierror = 1;
          }
      if ( (fout=fopen(argv[2],"w")) == NULL )
          {ierror = 1;
          }

      if (ierror == 1)
          {printf("Error in openning files. Aborted.\n\n");
          }


 L20:
      printf(" 1  -  MSDOS to LINUX\n"
             " 2  -  LINUX to MSDOS\n"
             " - > ");

      scanf("%d", &ichoice);
      if (ichoice != 1 && ichoice != 2)
           {printf("   *** 1 or 2, please ***\n\n");
            goto L20;
           }

      if (ichoice != 1)  {goto L100;}

 L40:
      c = fgetc(fin);
      if (c == EOF)
          {goto L200;
          }
      if (c != '\r')
          {fputc(c, fout);
          }
      goto L40;


 L100:
      c = fgetc(fin);
      if (c == EOF)
         {goto L200;
         }
      if (c == '\n')
         {fputc('\r', fout);
          fputc('\n', fout);
         }
      else
         {fputc(c, fout);
         }
      goto L100;

 L200:
      fclose(fin);
      fclose(fout);
      return 0;
}

Thus we can write lots and lots of commands! And this design philosophy makes C and UNIX powerful.


Another use of OR, ||

Normally, we would use || as a logical connectives. But you may find other uses of ||, e.g.

#include <c.h>

int die(char *s)
{   printf("%sn\n",s);
    exit(1);
    return 1;
}

int main(int narg, char *argv[])
{   FILE *fin;

    (fin=fopen(argv[1],"r")) ||  die("*** Unable to open file");

       .....
       .....

    fclose(fin);
    return 0;
}

When the computer executes (A || B), it will first check if "A" is true. If "A" is true, then it will not check "B". Only when "A" is false, it will check "B". e.g. the following code is valid

if (ptr==NULL || *ptr='a') {.....}
"A" fails means "ptr" is not NULL, hence we may store value at that address.

(fin=fopen(argv[1],"r")) || die("*** Unable to open file");
works the same way. When "fopen(..)" fails, it will return NULL, i.e. 0 (or "false"), and "die(..)" will be executed.

I dislike such kind of codes. I prefer a more direct one,

if ( (fin=fopen(argv[1],"r")) == NULL)
     {die("*** Unable to open file");
     }
because it is much easier to understand. But many people program that way, (especially in PERL), and if you are to read programs by others, you should know this strange usage of OR (||).


Recursion

In C, a data structure may contain other data structures, and they, in turn, contain other data structures. Recursion is common in data types.

Recursion may be used in program codes too. For a subroutine in C, it may call itself. The most common example is factorial function

#include <stdio.h>

int factorial(int n)
{  if (n <= 1)
       {return 1;
       }
   else
       {return (n*factorial(n-1));
       }
}

int main()
{   int n;

 L20:
    puts("Enter number ");
    scanf("%d",&n);
    if (n == -1)
        {return 1;
        }
    printf("Factorial of %d is %d\n", n, factorial(n));
    goto L20;
}

Notice that it is not always expedient to use recursion, e.g. the above factorial subroutine may be written using a simple loop. Moreover, recursion is slower and uses more memory.

But there are situations where we must use recursion. Writing compiler whose grammar is specified by recursive descend is one, below is another where we must use recursion.


Binary Tree

Suppose we have a sequence of numbers {7, 96, 31, 14, 18, 95, 63}, we may put it in a binary tree as follows.

Exercise : Draw a binary tree for the sequence {64, 50, 30, 96, 25, 27, 51}.

Ans :

Important points to remember for a binary tree : -

  1. It always starts at the topmost node.
  2. At any node, the set of smaller numbers is on the left, and the set of larger numbers on the right.
  3. To traverse a binary tree, and to access all nodes, we must use recursion.

Below is a subroutine that sorts the numbers we key in. Because "binary tree" grows, we need additional memory for each additional node. Either we reserve a large array for this purpose, or we request memory from the operating system. "malloc(..)" is a library function that requests memory from the operating system. "malloc(..)" is a standard subroutine in <stdlib.h>.

void *malloc(size_t nsize)

Note : size_t is defined in header files as unsigned int (integer).  We request
       memory "nsize" bytes, and malloc(..) returns the address where
       memory is allocated.  It returns NULL is no memory is available.

       e.g.   double *ptr;
              ptr = (double *) malloc(sizeof(double));
void free(void *ptr)

Note : When we no longer need the memory, we have to return it back.
       When the program terminates, all memory will be released automatically.

Below is a program that sorts a array of numbers using a "binary tree".

#include <c.h>
#define DEBUG 0

typedef struct DD
          {double data;
           struct DD *pright;    /* These address registers store the address */
           struct DD *pleft;     /* of the right and left portions            */
          }  Mrec;

static Mrec *P0;                /* P0 stores the top node address */
static int Msize, Ifirst=1;     /* Msize = sizeof(Mrec). 
                                   Ifirst is a flag, first time
                                   or not. */

/*  This subroutine compares 2 records.
    It returns   -1   if the first is "less than" the second.
                  0   if they are the same.
                  1   if the first is "greatter than" the second.
    Exercise : If you want to sort in descending order, what should
                  be the compare(..) subroutine.
*/
int compare(Mrec *pa, Mrec *pb)
{    return (pa->data - pb->data);
}

void store(double a)
{    Mrec *ptr, *ptr2, *ptr3;
     int iret;

     /* Request memory using library subroutine "void *malloc(size_t n)" 
        Since "malloc" returns a pointer to void, it must be casted before use. 
     */
     ptr = (Mrec *) malloc(Msize);  
     assert(ptr != NULL);

#if (DEBUG)
     printf("%p ",ptr);
#endif
   
     ptr->data = a;        /* Store the data */
     if (Ifirst == 1)
         {P0 = ptr;        /* Store  address of top-most node */
          Ifirst = 0;
          goto L100;
         }

     ptr2 = P0;
 L20:
     iret = compare(ptr, ptr2);
     if (iret <= 0)       /* less than or equal */
         {ptr3 = ptr2->pleft;
          if (ptr3 != NULL)         /* NULL means there is nothing on that portion */
              {ptr2 = ptr3;
               goto L20;
              }
          else
              {ptr2->pleft = ptr;
               goto L100;
              }
         }
     else
         {ptr3 = ptr2->pright;
          if (ptr3 != NULL)
              {ptr2 = ptr3;
               goto L20;
              }
          else
              {ptr2->pright = ptr;
               goto L100;
              }
         }

 L100:
     ptr->pleft = NULL;
     ptr->pright = NULL;
     return;
}

void mysort(Mrec *p)
{   Mrec *ptr;

    /*  Print the left portion first */
    ptr = p->pleft;
    if (ptr != NULL)
        {mysort(ptr);          /* Notice that recursion is used here */
        }                      /* for the left portion               */

    /*  Print itself */
    printf("%10.2f", p->data);

    /*  Print the right portion next */
    ptr = p->pright;
    if (ptr != NULL)
        {mysort(ptr);          /* Notice that recursion is used here */
        }                      /* for the right portion.             */

    return;
}

void freememory(Mrec *p)
{   Mrec *ptr;

    /*  Free the left portion first */
    ptr = p->pleft;
    if (ptr != NULL)
        {freememory(ptr);         /* Notice the use of recursion */
        }

    /*  Free itself */
    free(p);
#if (DEBUG)
    printf("%p ", p);
#endif

    /*  Free the right portion next */
    ptr = p->pright;
    if (ptr != NULL)
        {freememory(ptr);         /* Notice the use of recursion */
        }

    return;
}


int main()
{   char buffer[1000];
    double dummy;

    Msize = sizeof(Mrec);

 L20:
    puts("Enter number (**** = end) ");
    gets(buffer);
    if (strcmp(buffer,"****") == 0)
         {goto L100;
         }
    sscanf(buffer,"%lf",&dummy);
    store(dummy);
    goto L20;

 L100:
    mysort(P0);       /*  Prints the sorted list */
    printf("\n\n");

    freememory(P0);   /*  This frees all memory gotten from malloc */

    return 0;
}

It is important to release memory no longer needed. "Memory leaks" may cause a computer system to fail. I heard from TV that a program in a Police station worked fine day and night for 2 weeks, then suddenly it failed. Later, it was discovered that "memory leaks" is the cause.


Enumeration

Suppose a variable assumes only a finite number of states, e.g. The variable district may be "Hong Kong", "Kowloon", "New Territories". Either we may use coding : 0 for "Hong Kong", 1 for "Kowloon", 2 for "New Territories", or we may use enumeration.

Syntax (simplified) : "enum" is a C keyword. The syntax for "enum" is very similar to that of "struct".

enum [tag] { identifer1, identifier2, ... } variable1, variable2, ... ;

Note : an "enum" variable is in fact an integer. 
       {identifier1, identifier2, ... }  are the symbolic names for {0, 1, 2, ... }. 

e.g.
       enum {banana, orange, apple, pear } fruit;

The variable "fruit" is an integer. "banana, orange, apple, pear"
are symbolic names for {0, 1, 2, 3}.  Notice that it may start from any number,
e.g.
       enum {banana = 5, orange, apple, pear}  fruit;

Then it starts from 5.  {5, 6, 7, 8}

Example :

#include <c.h>

typedef enum { HongKong, Kowloon, NT } District;

int main()
{  District area;

 L20:
   puts("Enter area code");
   scanf("%d",&area);    /*  We key in 0, 1, 2, ... */
   if (area == -1) 
       {return 0;
       }
   if (area == HongKong)    /* Same as  if (area == 0) ... */ 
        {printf("Area is Hong Kong\n");
        }
   else if (area == Kowloon)
        {printf("Area is Kowloon\n");
        }
   else if (area == NT)
        {printf("Area is New Territories\n");
        }
   else
        {printf("  *** ERROR , area code not recognized ***\n");
        }

   goto L20;
}

[Previous] [Home] [Next]