CS 2213 Advanced Programming
Structures


Read Chapter 6 of the text.
Previous Topic: Arrays and Pointers
Next Topic: Input and Output
Start of Class Thursday, March 22, 2001
Start of Class Tuesday, March 27, 2001
Start of Class Thursday, March 29, 2001
Start of Class Thursday, April 5, 2001
Start of Class Tuesday, April 10, 2001

Introduction

A structure is a collection of one or more variables, grouped together under a single name. It is like a class without any code.


6.1 Basics of Structures

One of the simplest classes in Java is the Point class which contains two integer fields x and y.

Here is how we would make a point structure in C:

   struct point {
      int x;
      int y;
   };
The identifier following the key word struct is called the structure tag and can be used to as a shorthand to represent the part of the structure definition which is in the following braces.

The variables named in the structure are called its members.
These names only have to be unique with the structure.
The same names can be used outside the structure to represent different variables.
The structure tag may also be the same as the name of a variable, but this should be avoided.

The struct definition defines a new type and variables can be declared with that type once the structure is declared.

struct point pt1, pt2, pt3;
defines three variables of type point.

You can initialize variables by listing the values of the members in braces:

struct point pt1 = {100,200};

You can reference the members of a structure with the same notations as you use for fields of a class:

struct point pt1, pt2, pt3;

pt1.x = 4;
pt1.y = 5;
printf("The point has x value %d and y value %d\n",pt1.x,pt1.y);
Structure definitions can be nested:
struct line {
   struct point pt1;
   struct point pt2;
};
struct line myline;
myline.pt1.x = 3;
myline.pt1.y = 4;
myline.pt2.x = 5;
myline.pt2.y = 7;
printf("My line starts at (%d,%d) and ends at (%d,%d)\n",
       myline.pt1.x,myline.pt1.y,myline.pt2.x,myline.pt2.y);


6.2 Structures and Functions

Consider the following program and its output:
#include <stdio.h>
 
struct point {
   int x;
   int y;
};

struct line {
   struct point pt1;
   struct point pt2;
};
 
void showpoint(char *msg, struct point apoint) {
 
   printf("%s is at (%d,%d)\n",msg,apoint.x,apoint.y);
 
}
 
void showline(char *msg, struct line aline) {
 
   printf("%s starts at (%d,%d) and ends at (%d,%d)\n\n",msg,
           aline.pt1.x, aline.pt1.y, aline.pt2.x, aline.pt2.y);
 
}
 
void changepoint(struct point apoint) {
 
   showpoint("In change point before change point is ",apoint);
   apoint.x++;
   showpoint("In change point after  change point is ",apoint);
 
}
 
struct point addpoints(struct point point1, struct point point2) {
 
   point1.x = point1.x + point2.x;
   point1.y = point1.y + point2.y;
 
   return point1;
 
}
 
int main() {
 
   struct line myline;
 
   struct point firstpoint = {4, 12};
   struct point secondpoint;
 
   secondpoint = firstpoint;
   secondpoint.x++;
   secondpoint.y--;
 
   myline.pt1 = firstpoint;
   myline.pt2 = secondpoint;
 
   showpoint("firstpoint",firstpoint);
   showpoint("secondpoint",secondpoint);
   showline("My line ",myline);
 
   firstpoint.x = 17;
   showpoint("firstpoint",firstpoint);
   showpoint("secondpoint",secondpoint);
   showline("My line ",myline);
 
   changepoint(firstpoint);
   showpoint("After changepoint returns,    point is ",firstpoint);
 
   printf("\n");
   showpoint("Before addpoints,  firstpoint",firstpoint);
   showpoint("Before addpoints, secondpoint",secondpoint);
   secondpoint = addpoints(firstpoint,secondpoint);
   showpoint("After  addpoints,  firstpoint",firstpoint);
   showpoint("After  addpoints, secondpoint",secondpoint);
 
   return 0;
 
}
Here is the output generated:
firstpoint is at (4,12)
secondpoint is at (5,11)
My line  starts at (4,12) and ends at (5,11)

firstpoint is at (17,12)
secondpoint is at (5,11)
My line  starts at (4,12) and ends at (5,11)

In change point before change point is  is at (17,12)
In change point after  change point is  is at (18,12)
After changepoint returns,    point is  is at (17,12)

Before addpoints,  firstpoint is at (17,12)
Before addpoints, secondpoint is at (5,11)
After  addpoints,  firstpoint is at (17,12)
After  addpoints, secondpoint is at (22,23)

Note that in C, function parameters are always passed by value.

Why does it work to pass an array to a function that changes the array, but it does not work to pass and int or struct point to a function to change its value?

Differences between Java and C

Java Code:                  C Code:

P1 = new Point(1,2);        struct point p1 = {1,2};
P2 = new Point(3,4);        struct point p2 = {3,4};
P2 = P1;                    p2 = p1;
P1.x = 17;                  p1.x = 17;
What are the points now? Why?

We can simulate the new Point of Java with the following:

struct point makepoint(int x, int y) {
   struct point temp;
   temp.x = x;
   temp.y = y;
   return temp;
}

In what ways is this the same as in Java and in what ways is it different?

How do you pass a structure to a function so that it can be modified?
You do this the same way as you would with an int.
You pass a pointer to it.

struct point *mypointp;
defines a pointer to struct point.
You can access the members as follows:
struct point *mypointp;
struct point p;
mypointp = &p;
(*mypointp).x = 17;
Note that the parens are needed because . has a higher precedence than *.

Because this combination occurs so often, there is an operator to handle this directly:
mypointp -> x is the same as (*mypointp).x, so we can write:

mypointp->x = 17;
Here is a new version of changepoint
#include <stdio.h>

struct point {
   int x;
   int y;
};

void showpoint(char *msg, struct point apoint) {

   printf("%s is at (%d,%d)\n",msg,apoint.x,apoint.y);

}

void changepointok(struct point *apointp) {

   showpoint("In change point before change point is ",*apointp);
   apointp -> x++;
   showpoint("In change point after  change point is ",*apointp);

}

int main() {

   struct point firstpoint = {4, 12};

   changepointok(&firstpoint);
   showpoint("After changepoint returns,    point is ",firstpoint);

   return 0;

}
Which produces output:
In change point before change point is  is at (4,12)
In change point after  change point is  is at (5,12)
After changepoint returns,    point is  is at (5,12)

What is wrong with the following?:

struct point *mypointp;
struct point p;
*mypointp = p;
(*mypointp).x = 17;
Note that the operators with highest precedence are ., ->, () for function calls, and [] for array subscripts.


Here is some information about Hashing.

Consider the following:

struct stringwithlength {
   int len;
   char *str;
} *p;
This defines a variable, p which is a pointer to this structure.
++p->len increments len because -> has a high precedence.
(p++)->len increments p after to gets the len member.

The sizeof operator can tell you the size of a variable or a type.
You can use either:
sizeof object
or
sizeof(typename)

For example, on our system
sizeof(int)
will return 4 and
sizeof(int *)
will also return 4.

What about sizeof(stringwithlength)?

This is an error, but
What about sizeof(struct stringwithlength)?

This gives 8.

Suppose we had

struct newstringwithlength { 
   int len; 
   char str[10]; 
}; 
What is sizeof(struct newstringwithlength)?

16    Why is that?

consider

   struct newstringwithlength x;
   printf("x.len has size %d\n",sizeof(x.len));
   printf("x.str has size %d\n",sizeof(x.str));
   printf("x has size %d\n",sizeof(x));
This gives
x.len has size 4
x.str has size 10
x has size 16
Conclusion: The whole is sometimes bigger than the sum of its parts.

Results may be different on different hardware, even when ints are 4 bytes.


6.7 Typedef

Note that we are covering this out of order.

The library routine strlen has the prototype:

      size_t strlen(const char *s);
This returns the length of the string.
What is size_t?
This is just a made up type representing an unsigned int and is defined in stdio.h as
typedef unsigned int    size_t;
typedef is used to give new names to existing types.
The format of a type definition is
typedef oldname newname

Typedefs are often used with structures. For example

typedef struct stringval {
   char *str;
   int val;
} strval;
Would allow us to make a declaration like:
   strval sval;
and sval would then be a struct stringval.

One usual way of doing this is to use the same name for the new type as the structure name:

typedef struct stringval { 
   char *str; 
   int val; 
} stringval; 
and then the following would be equivalent:
   struct stringval sval;
   stringval sval;
You can even leave out the structure name completely:
typedef struct { 
   char *str; 
   int val; 
} stringval; 
and we could still say:
   stringval sval;
but struct stringval sval would now be undefined.

What is the difference between the following:

typedef struct {              struct {
   char *str;                    char *str;
   int val;                      int val;
} stringval;                  } stringval;

The one on the left defines stringval as a new type.
The one on the right defines a variable, stringval, which can contain a structure.


6.3 Arrays of Structures

Consider the structure from out Programming Assignment 2 (now with a typedef):
typedef struct {
   char *str;
   int val;
} stringval;
Instead of using a hash table, assume that we have just an array of these:
#define NUMSTRINGS 1000
stringval mystrings[NUMSTRINGS];
mystrings is and array of structures.
Let us assume that we have initialized this array and sorted it.
We will now write the lookup part:
int binsearch(char *str, stringval *sval, int size);
Where str is the string to search for and size is the size of the array.
We will return the index if the string is found and -1 if not found.
int binsearch(char *str, stringval mystrings[], int size) {
   int cond;
   int low;
   int high;
   int mid;

   low = 0;
   high = size - 1;
   while (low <= high) {
      mid = (low + high)/2;
      cond = strcmp(str,mystrings[mid].str);
      if (cond < 0)
         high = mid - 1;
      else if (cond > 0)
         low = mid + 1;
      else
         return mid;
    }
    return -1;
}
The idea is that the string we are looking for, if it exists, is between mystrings[low] and mystrings[high], inclusive.


Here is some information about the test next week.


6.4 Pointers to Structures

We can look at the same binary search problem in terms of pointers, rather than arrays.
Instead of using integers low, mid, high we can use pointers instead.
Our new binsearch will look like this:
stringval *binsearch(char *str, stringval *mystrings, int size);
Now we return a pointer to the found entry, or NULL if it is not found.
Here is the obvious translation of the above function:
stringval *binsearch(char *str, stringval *mystrings, int size) {
   int cond;
   stringval *low;
   stringval *high;
   stringval *mid;

   low = mystrings;
   high = &mystrings[size-1];
   while (low <= high) {
      mid = low + (high-low)/2;
      cond = strcmp(str,mid->str);
      if (cond < 0)
         high = mid - 1;
      else if (cond > 0)
         low = mid + 1;
      else
         return mid;
   }
   return NULL;
}
What is wrong with this?

The problem is that we are not allowed to dereference pointers outside the array, and in fact, we should not even be allowed to computer a pointer to something outside of the array.

Suppose that the array has 2 elements starting at location 1000 and that sizeof(stringval) is 8.

Suppose also that the string we are looking for is not in the array and in fact is smaller than any of the stored strings.

We start with (addresses in bold):
low = 1000
high = 1008
mid = 1000 + (1008 - 1000)/2 = 1000 + 1/2 = 1000 + 0 = 1000;
cond < 0 so
high = 1000 - 1 = 992;
But we are not even allowed to make such a pointer this way.

We can solve the problem by reorganizing the code slightly.
The C standard allows us to compute (but not dereference) the pointer just past the end of an array.

In the following, the string we are looking for is between mystrings[low] and mystrings[high], including the left endpoint, but not including the right endpoint.

 
stringval *binsearch(char *str, stringval *mystrings, int size) {
   int cond; 
   stringval *low; 
   stringval *high; 
   stringval *mid; 
 
   low = mystrings; 
   high = &mystrings[size]; 
   while (low < high) { 
      mid = low + (high-low)/2; 
      cond = strcmp(str,mid->str); 
      if (cond < 0)
         high = mid;
      else if (cond > 0)
         low = mid + 1;
      else
         return mid;
   }
   return NULL;
}


6.5 Self-referential Structures

The example we will look at here involves linked lists.

We have already looked at an array implementation of a stack.

This has the limitation of a fixed maximum size.

We will consider a stack of stringval items.
Each stack element will contain a stringval and a pointer to the next element on the stack.

struct stackentry {
   stringval sval;
   struct stackentry *next;
};
A structure is not allowed to contain a copy of itself, but it can contain a pointer to itself.
It would be useful to make a typedef for this:
typedef struct stackentry {
   stringval sval;
   struct stackentry *next;
} stackentry;
so that we can refer to it by the name stackentry rather than struct stackentry.

Notice that the following is not allowed:

typedef struct stackentry {
   stringval sval;
   stackentry *next;
} stackentry;
because the name stackentry (without the struct) is not defined until the closing brace.

We will make a "stack object" which will use a linked list to implement the stack.
Here is the contents of the stack.h file:

typedef struct stringval {
   char *str;
   int val;
} stringval;

int push(stringval sval);
stringval *pop(void);
void printstack(void);

The push returns true on success and false on failure.
The pop removes the item from the top of the stack and returns a pointer to it.
It returns NULL if the stack was empty.

There are many ways to represent a stack using linked lists.
In our implementation, an entry on the stack will contain a stringval and a pointer to the next element on the stack.

The stack itself will just be a pointer to a stack entry.
The pointer will be NULL when the stack is empty.

Here is what the stack.c file might look like:

#include <stdio.h>
#include <stdlib.h>
#include "stack.h"

typedef struct stackentry {
   stringval sval;
   struct stackentry *next;
} stackentry;

typedef stackentry *stack;

static stack mystack = NULL;

static stackentry *make_entry(stringval sval) {
   ...
}

/* return true if success, false if error */
int push(stringval sval) {
   ...
}

/* remove and return pointer to top item if OK, NULL if error */
stringval *pop() {
   stringval *svalp;
   ...
   return svalp;
}

void printstack() {
   stackentry *this;
   int count = 0;
   this = mystack;
   printf("Stack follows:\n");
   while (this != NULL) {
      printf("%10d %s\n",this->sval.val,this->sval.str);
      this = this->next;
      count++;
   }
   printf("Number of entries found: %d\n",count);
}

Here is a main program that can be used to test the stack:

#include <stdio.h>
#include <stdlib.h>
#include "stack.h"

static void testpush(char *str, int val) {
   stringval sval;
   sval.val = val;
   sval.str = str;
   printf("Before push\n");
   printstack();
   if (push(sval) < 0)
      printf("push failed\n");
   printf("After push of %s\n",str);
   printstack();
   printf("\n");
}

static void testpop() {
   stringval *svalp;
   printf("Before pop\n");
   printstack();
   svalp = pop();
   if (svalp == NULL)
      printf("pop failed\n");
   else
     printf("poped: %10d %s\n",svalp->val,svalp->str);
   printf("After pop\n");
   printstack();
   printf("\n");
}

int main() {
   testpop();
   testpush("this is pushed first",10);
   testpush("this is pushed second",20);
   testpop();
   testpop();
   testpop();
   testpush("this is pushed first again",30);
   testpush("this is pushed second again",40);
   testpop();
   return 0;
}
Here is the output generated by this:
Before pop
Stack follows:
Number of entries found: 0
pop failed
After pop
Stack follows:
Number of entries found: 0

Before push
Stack follows:
Number of entries found: 0
After push of this is pushed first
Stack follows:
        10 this is pushed first
Number of entries found: 1

Before push
Stack follows:
        10 this is pushed first
Number of entries found: 1
After push of this is pushed second
Stack follows:
        20 this is pushed second
        10 this is pushed first
Number of entries found: 2

Before pop
Stack follows:
        20 this is pushed second
        10 this is pushed first
Number of entries found: 2
poped:         20 this is pushed second
After pop
Stack follows:
        10 this is pushed first
Number of entries found: 1

Before pop
Stack follows:
        10 this is pushed first
Number of entries found: 1
poped:         10 this is pushed first
After pop
Stack follows:
Number of entries found: 0

Before pop
Stack follows:
Number of entries found: 0
pop failed
After pop
Stack follows:
Number of entries found: 0

Before push
Stack follows:
Number of entries found: 0
After push of this is pushed first again
Stack follows:
        30 this is pushed first again
Number of entries found: 1

Before push
Stack follows:
        30 this is pushed first again
Number of entries found: 1
After push of this is pushed second again
Stack follows:
        40 this is pushed second again
        30 this is pushed first again
Number of entries found: 2

Before pop
Stack follows:
        40 this is pushed second again
        30 this is pushed first again
Number of entries found: 2
poped:         40 this is pushed second again
After pop
Stack follows:
        30 this is pushed first again
Number of entries found: 1

Time to write int push(stringval sval) and stringval *pop() as well as static stackentry *make_entry(stringval sval)

Here is the code we wrote in class on Tuesday:

static stackentry *make_entry(stringval stval) {
   stackentry *new_entry;

   new_entry =(stackentry *)malloc(sizeof(stackentry));
   if(new_entry == NULL)
      return NULL:
   new_entry->sval = stval;
   return new_entry;
}

int push(stringval sval){
   stackentry *new_entry;
   
   new_entry = make_entry(sval);
   if(new_entry == NULL)   
      return 0;
   new_entry->next = mystack;
   mystack = new_entry;
   return 1;
}

We should write pop now:

/* remove and return pointer to top item if OK, NULL if error */
stringval *pop() {
   stringval *svalp;
   ...
   return svalp;
}

Why was pop defined as it was?

To allow a NULL pointer to indicate an error.

An alternative is to define another stack function:

int stackempty(void);
which returns true if the stack is empty and false otherwise.
We can then define the pop operation as:
stringval pop(void);
which is only allowed to be called when the stack is not empty.


Change in Office Hours

Here are some comments about Exam 2.


Doubly linked lists, a queue implementation

In a queue, items are inserted at one end and taken out of the other.

A node entry might look like this:

typedef struct nodeentry {
   stringval sval;
   struct nodeentry *tofront;
   struct nodeentry *torear;
} nodeentry;
with a pointer to both the next and the previous entry.
In either case, a NULL pointer is used to indicate an end of the list.

The queue itself will need a pointer to the start and a pointer to the end:

typedef struct {
   nodeentry *front;
   nodeentry *rear;
} queue;
When the queue is empty, both of these pointers are NULL.

The names front and rear are arbitrary.
In this implementation, tofront points to the front and torear points to the rear.
We remove from the front and insert at the rear.

The two main operations are insert and remove:

/* insert a node at the end of the list */
int qinsert(stringval sval);
which returns true if successful and false otherwise.
/* remove a node from the front of the list */
stringval qremove(void);
Which returns the removed structure. It assumes that the queue is not empty.
We check for empty with:
int qempty(void);
which returns true if empty and false otherwise.

When writing the queue routines, you should think about the testing process.
One of the problems with a doubly linked list is that it can become inconsistent.
You might want to write a routine to check that the queue is valid:

int qcheck();
will return true if the queue is OK and false otherwise.
It will also print a message indicating what is wrong with the queue.

Think about what you need to verify for a doubly linked list to be consistent.



Assume that we have a queue object with the following definition:
static queue q = {NULL,NULL};
Here is a version of this funtion:
/* Return true if OK, false if bad */
int qcheck() {
   nodeentry *ne;
   int count;
   if ( (q.front == NULL) && (q.rear != NULL) ) {
      printf("front is NULL but rear is not\n");
      return 0;
   }
   else if ( (q.rear == NULL) && (q.front != NULL) ) {
      printf("rear is NULL but front is not\n");
      return 0;
   }
   else if (q.front == NULL) return 1;
   if (q.front->tofront != NULL) {
      printf("First node does not point forward to NULL\n");
      return 0;
   }
   if (q.rear->torear != NULL) {
      printf("Last node does not point back to NULL\n");
      return 0;
   }
   ne = q.rear;
   count = 0;
   while (ne->tofront != NULL) {
      if (ne->tofront->torear != ne) {
         printf("Node %d points to node which does not point back to it\n",
                count);
         return 0;
      }  
      ne = ne->tofront;
   }
   if (ne != q.front) {
      printf("Last node doing forward links is not pointed to by front\n");
      return 0;
   }
   return 1;
}
Another useful routine for debugging is one which prints out the queue:
void showqueue() {
   int count;
   nodeentry *ne;

   if (q.front == NULL) {
      printf("Queue is empty\n");
      return;
   }
   count = 0;
   ne = q.front;
   while (ne != NULL) {
      count++;
      ne = ne->torear;
   }
   printf("Number of entries in queue: %d\n",count);
   ne = q.front;
   count = 1;
   while (ne != NULL) {
      printf("%5d: %5d %s\n",count,ne->sval.val,ne->sval.str);
      ne = ne->torear;
      count++;
   }
}

Here is a program for testing the queue:

#include <stdio.h>
#include "queue.h"

static void checkit() {
   if (qcheck())
      printf("Queue is OK\n");
}

static void testremove() {
   stringval sval;
   printf("Before remove queue is\n");
   showqueue();
   printf("After remove\n");
   if (qempty()) 
      printf("Remove failed\n");
   else {
      sval = qremove();
      printf("Removed:  %5d %s\n",sval.val,sval.str);
   }
   showqueue();
   checkit();
   printf("\n");
}

static void testinsert(int val, char *str) {
   stringval sval;
   sval.val = val;
   sval.str = str;
   printf("Before insert queue is\n");
   showqueue();
   printf("After insert of %d %s\n",val,str);
   if (!qinsert(sval))
      printf("Insert failed\n");
   showqueue();
   checkit();
   printf("\n");
}
   
int main() {
   checkit();
   showqueue();
   printf("\n");

   testremove();
   testinsert(10,"This is a test");
   testinsert(20,"This is only a test");
   testinsert(30,"If this were a real queue, it would be used for something");
   testremove();
   testremove();
   testinsert(40,"OK, we are almost done");
   testremove();
   testremove();
   testremove();
   return 0;
}

Here is the output it generates:
Queue is OK
Queue is empty

Before remove queue is
Queue is empty
After remove
Remove failed
Queue is empty
Queue is OK

Before insert queue is
Queue is empty
After insert of 10 This is a test
Number of entries in queue: 1
    1:    10 This is a test
Queue is OK

Before insert queue is
Number of entries in queue: 1
    1:    10 This is a test
After insert of 20 This is only a test
Number of entries in queue: 2
    1:    10 This is a test
    2:    20 This is only a test
Queue is OK

Before insert queue is
Number of entries in queue: 2
    1:    10 This is a test
    2:    20 This is only a test
After insert of 30 If this were a real queue, it would be used for something
Number of entries in queue: 3
    1:    10 This is a test
    2:    20 This is only a test
    3:    30 If this were a real queue, it would be used for something
Queue is OK

Before remove queue is
Number of entries in queue: 3
    1:    10 This is a test
    2:    20 This is only a test
    3:    30 If this were a real queue, it would be used for something
After remove
Removed:     10 This is a test
Number of entries in queue: 2
    1:    20 This is only a test
    2:    30 If this were a real queue, it would be used for something
Queue is OK

Before remove queue is
Number of entries in queue: 2
    1:    20 This is only a test
    2:    30 If this were a real queue, it would be used for something
After remove
Removed:     20 This is only a test
Number of entries in queue: 1
    1:    30 If this were a real queue, it would be used for something
Queue is OK

Before insert queue is
Number of entries in queue: 1
    1:    30 If this were a real queue, it would be used for something
After insert of 40 OK, we are almost done
Number of entries in queue: 2
    1:    30 If this were a real queue, it would be used for something
    2:    40 OK, we are almost done
Queue is OK

Before remove queue is
Number of entries in queue: 2
    1:    30 If this were a real queue, it would be used for something
    2:    40 OK, we are almost done
After remove
Removed:     30 If this were a real queue, it would be used for something
Number of entries in queue: 1
    1:    40 OK, we are almost done
Queue is OK

Before remove queue is
Number of entries in queue: 1
    1:    40 OK, we are almost done
After remove
Removed:     40 OK, we are almost done
Queue is empty
Queue is OK

Before remove queue is
Queue is empty
After remove
Remove failed
Queue is empty
Queue is OK

Time to write int qempty(void), int qinsert(stringval sval) and stringval qremove()

int qinsert(stringval sval){
   nodeentry *new_entry;
  
   new_entry = (nodeentry *)malloc(sizeof(nodeentry)); 
      if(new_entry == NULL) 
         return 0; 
   new_entry->sval = sval; 
   new_entry->tofront = q.rear; 
   new_entry->torear = NULL; 
 
   if(q.rear == NULL){ 
      q.rear = new_entry; 
      q.front = new_entry; 
      return 1; 
   } 
   q.rear->torear = new_entry; 
   q.rear=new_entry;
   return 1;
}

6.6 Table Lookup

This section is about hashing which we already covered.

Here is another way to do hashing.

For each hash index, keep all entries with that index in a linked list.
Since we will have to both insert and delete entries, a doubly link list would be good.

The hash table is now an array of linked lists.
Each entry in the array will be of type queue. Recall

typedef struct nodeentry {
   stringval sval;
   struct nodeentry *tofront;
   struct nodeentry *torear;
} nodeentry;

typedef struct {
   nodeentry *front;
   nodeentry *rear;
} queue;
We will need the same functions as in programming assignment 3:
/* Allocate space and return a pointer to an empty hash table */
queue *hashinit();

/* Insert sval into the hashtable htp.
   Return 1 on success and 0 on failure. */
int hashinsert(stringval sval, queue *htp);

/* Remove str from the hashtable htp.
   Return 1 on success and 0 on failure. */
int hashremove(char *str, queue *htp);

/* Return a pointer to the stringval containing str in the hashtable htp.
   Return NULL if not found. */
stringval *hashfind(char *str, queue *htp);

One of the problems with this (as with Assignment 3) is that the program that uses the hash table needs to have some information about its implementation. This is contained in the queue typedef.

We can hide this by passing a pointer of type void instead of a pointer of type queue.

The public functions in hashtable.c would look like:

void *hashinit();
int hashinsert(stringval sval, void *htp);
int hashremove(char *s, void *htp);
stringval *hashfind(char *str, void *htp);
Here is how hashinsert might start:
int hashinsert(stringval sval, void *htp) {
   int pos;
   queue *ht_start;
   ...

   ht_start = (queue *)htp;
   pos = hashfunction(sval.str);
   ...
The queue to use for this string is just ht_start[pos].

The file hashtable.h would just contain the typedef for stringval and the prototypes for the public functions above.

The constant HASHSIZE and the typedefs for nodeentry and queue should appear only in hashtable.c.

Let us write hashinit.





When you implement the rest of the has table functions, the following would be useful:
/* return pointer to nodeentry if in, NULL if not */
static nodeentry *checkinq(char *s, queue q);


6.8 Unions

Unions are like structures.
The difference is that only one member can hold a value.
The members may share the same storage.

Example:

union sigval {
   int   sival_int;
   void  *sigval_ptr;
}
This can hold either an integer or a pointer, but not both.
It is up to the application to know which one to use.


Next Topic: Input and Output