CPS 125 Alexander Ferworn Week 9 Structures and Linked Lists What we will cover this week ---------------------------- o Dynamic Memory Allocation o Structures o Singly Linked Lists--the directors cut o Singly linked lists--the "Reader's Digest" version Dynamic Memory Allocation ------------------------- o DMA allows you to allocation memory at run time rather than compile time. o Compile time memory is fine but you might not know how much to ask for. If you ask for too little you will run out of space and have to recompile and if you ask for too much some will be wasted. o This means that after the program has been compiled you can create things like arrays of different sizes using only as much space as you need. o Remember all those examples where you had to declare an array to be bigger than anything you might encounter ...those days may be over. o There are several library function (all in <stdlib.h>) which can help you. malloc() Allocate a specified number of bytes in memory. Returns a pointer to the beginning of those bytes. calloc() Same as malloc but set the bytes to zero realloc() (not covered). Changes the size of a previously allocated block free() Frees up memory that was previously allocated An example using DMA to do sorting; #include <stdio.h> #include <stdlib.h> void main() { int *list, sort_num,j; printf("How many numbers would you like to sort?\n"); scanf("%d",&sort_num); list = (int *) malloc(sort_num * sizeof(int)); /* could use list = (int *) calloc(sort_num, sizeof(int)); */ for(i=0;i<sort_num;i++) scanf("%d",list+j); <put call to bubble_sort routine here> } o DMA works for arrays because all the locations in the block of memory are contiguous. o If you made more than one call to malloc() there is no guarantee that the new block will be contiguous to the old block and probably is not! o The way to get around this problem is to use something called a linked list (discussed later). another example this time using a function and treating the pointer to the block of memory like an array. #include <stdio.h> #include <stdlib.h> /* program to ask user how big they want an array. Dump fives into it and call a function to output the array */ void output(int array[],int size); void main() { int size,j; int *block; printf("Enter the size of the array you want: "); scanf("%d",&size); block = (int *) malloc(size * sizeof(int)); if(block == NULL) exit(0); /* malloc() returns NULL if no memory was allocated */ for(j=0;j<size;j++) *(block + j) = 5; output(block,size); } void output(int array[],int size) { int j; for(j=0;j<size;j++) printf("%d ",array[j]); } Structures in C --------------- o A structure is a collection of variables under a single name. o These variables can be of different types, and each has a name which is used to select it from the structure. o A structure is a convenient way of grouping several pieces of related information together. o A structure can be defined as a new named type, thus extending the number of available types. o It can use other structures, arrays or pointers as some of its members, though this can get complicated unless you are careful. Defining a Structure -------------------- o A structure is called an aggregate type meaning that you can add other types to it to form bundles of related information. For example, you could create a structure containing all the information about your car and treat it as a single variable. o A structure type is usually defined at the start of a file using a typedef statement. o typedef defines and names a new type, allowing its use throughout the program. o typedefs usually occur just after the #define and #include statements in a file. Here is an example structure definition. typedef struct student_tag{ char name[64]; char course[128]; int age; int year; } STUDENT; o This defines a new type STUDENT. o The "student_tag" name is call a structure tag and is optional for now but will be used when we talk about linked lists...basically it is another way of referring to the structure. o Variables of type STUDENT can be declared as follows. STUDENT st_rec; o Notice how similar this is to declaring an int or float. o The variable name is st_rec, it has members called name, course, age and year. Accessing Members of a Structure -------------------------------- o Each member of a structure can be used just like a normal variable, but its name will be a bit longer. o member "name" of structure st_rec will behave just like a normal array of char, however we refer to it by the name st_rec.name Here the dot is an operator which selects a member from a structure. o Where we have a pointer to a structure we could dereference the pointer and then use dot as a member selector. o This method is a little clumsy to type. Since selecting a member from a structure pointer happens frequently, it has its own operator -> which acts as follows. o Assume that st_ptr is a pointer to a structure of type STUDENT We would refer to the name member as st_ptr -> name Structures as Function Arguments -------------------------------- o A structure can be passed as a function argument just like any other variable. o Where we wish to modify the value of members of the structure, we must pass a pointer to that structure. This is just like passing a pointer to an int type argument whose value we wish to change. o If we are only interested in one member of a structure, it is probably simpler to just pass that member. This will make for a simpler function, which is easier to re-use. o Of course if we wish to change the value of that member, we should pass a pointer to it. o When a structure is passed as an argument, each member of the structure is copied. o This can prove expensive where structures are large or functions are called frequently. o Passing and working with pointers to large structures may be more efficient in such cases. An example of an array of structures being passed into a function. /* Function to count the number of students of a particular age (treating structure like array)*/ typedef struct student_tag{ char name[64]; char course[128]; int age; int year; } STUDENT; int agecount(STUDENT student[],int age,int num_students) { int count=0,k; for(k=0;k<num_students;k++) if(student[k].age == age)count++; return count; } The same example but this time using pointers int agecount(STUDENT *student,int age,int num_students) { int count=0,k; for(k=0;k<num_students;k++) { if(student->age == age)count++; student++; } return count; } Nested Structures ----------------- o When one of the fields in a structure is a structure, you have a nested structure. for example typedef struct { int year; int month; int day; int hour; int minute; int second; } TIME; typedef struct { char[30+1] name; TIME birth_time; } BIRTHDAY_BOY; o we refer to elements of the nested structure with the dot or -> notation as follows ... BIRTHDAY_BOY tom; BIRTHDAY_BOY *ed; ed = &tom; tom.birth_time.year = 1997; ed->birth_time.minute = 30; Further Uses of Structures -------------------------- o As we have seen, a structure is a good way of storing related data together. o It is also a good way of representing certain types of information. o Complex numbers in mathematics inhabit a two dimensional plane (stretching in real and imaginary directions). o These could easily be represented here by typedef struct { double real; double imag; } complex; o doubles have been used for each field because their range is greater than floats and because the majority of mathematical library functions deal with doubles by default. o Apart from holding data, structures can be used as members of other structures. o Arrays of structures are possible, and are a good way of storing lists of data with regular fields, such as databases. o Another possibility is a structure whose fields include pointers to its own type. o These can be used to build chains (programmers call these linked lists as we will see) Linked Lists ------------ o Structures are capable of handling grouped data and so far we have used arrays to group together structures. o If we don't kow how big an array might be at compile time we know we can use DMA o Successive calls to malloc() will not provide us with contiguous space. o It would be nice to be able to link these blocks of memory in some way so that we can move from the first block to the second and so on. o A linked list can be used to do this. o A linked list is a chain of structures that is linked one to another, like sausages. o In the simplest linking scheme each structure contains an extra field called a "link" which is used to point to the next structure. An example : o Here is a revised STUDENT type using this notion typedef struct student{ char name[64]; char course[128]; int age; int year; struct student *next; } STUDENT; o In this case we modify our usual typedef statement to include "struct student". o student is called a "tag name" and allows us to refer back to the structure (recursively). o the pointer field "next" allows us to point at other variables of type STUDENT (struct student) Logical Representation ---------------------- o Pictorially, a linked list looks like the diagram below. [DATA][NEXT]------->[DATA][NEXT]------->[DATA][NEXT]------->... Typical Operations ------------------ o In a typical linked list you need to perform the following operations; o Create a list o Add elements to the end of the list o Insert elements into the middle of the list o Remove elements from the list o Find a particular element in the list Stuff you need o First we need to create a file called "student.h" which will contain the stuff we need to create the functions we need. o The contents of this file are given below #define NULL (void *) 0 typedef struct student{ char name[64]; char course[128]; int age; int year; struct student *next; } STUDENT; Creating a list of students #include "student.h" STUDENT *create_list_element() { STUDENT *p; p = (STUDENT *) malloc(sizeof(STUDENT)); p->next = NULL; return p; } o Pictorially this looks like [DATA][NULL] Adding Elements --------------- /* a global variable that needs to be */ static STUDENT *head = NULL; #include "student.h" /* add the student pointed to by "e" into the list pointed to by "head" */ void add_element(STUDENT *e) { STUDENT *p; /* If there is no head make it */ if(head == NULL) { head = e; return; } /* Otherwise find the last element in the list */ for(p=head;p->next != NULL; p = p->next); p->next = e; } o Pictorially this looks like this after adding one node. [head]----->[DATA][NULL] Stopping for a moment --------------------- o Now we can do wierd stuff like the following which creates 10 STUDENTS and adds them to the list. #include "student.h" static STUDENT *head = NULL; void main() { int j; for(j=0;j<10;j++) add_element(create_list_element() ); } Inserting an Element -------------------- o To insert a new element you must say where you want to insert it. o The following function accepts 2 pointer args p and q, it inserts p after q. #include "student.h" void insert_after(STUDENT *p, STUDENT *q) { /* sanity check */ if(p==NULL || q==NULL || p==q ||q->next==p) { printf("Can't insert after\n"); return; } p->next = q->next; q->next = p; } Pictorially this looks like; q q->next Before: ...----->[ ][ ]-------------->[ ][NULL] q q->next->next After: ...----->[ ][ ]-+ +-->[ ][NULL] | p | +->[ ][ ]-+ Deleting an Element ------------------- o This is a bit tricky since you need to find the element before the one you want to delete so that you can assemble the list after the delete. o You need to use the free() function. #include "student.h" static STUDENT head; void delete_element(STUDENT *goner) { STUDENT *p; if(goner == head) head = goner->next; else { /* find preceding element */ for(p=head;(p!=NULL) && (p->next!=goner); p=p->next); if(p==NULL) { printf("Can't find element to delete\n"); return; } p->next = p->next->next; /* same as (p->next)->next */ } free(goner); } o Pictorially this looks like p goner p->next->next Before: ...----->[ ][ ]------->[ ][ ]------->[ ][NULL] p p->next After: ...----->[ ][ ]--------------------->[ ][NULL] Finding an Element ------------------ o There is no general purpose find because you normally search for an element based on its data fields. o The following function searches for a student name. #include "student.h" static STUDENT head; STUDENT *find(char *name) { STUDENT *p; for(p=head;p!=NULL;p=p->next) if(!strcmp(p->name,name)) return p; return NULL; } Ok Ok Ok...you don't get it...try this example ---------------------------------------------- /************************************************************** An example of creating a simple singly linked list using both standard variables and pointers. The way to use this thing is to clip it out of this file and compile it up to see what it does. More importantly--do it by hand and draw the associated linked diagram so you can what is going on. **************************************************************/ /************************************************************* THINGS WE NEED TO START THE PROBLEMS **************************************************************/ #include <stdio.h> #include <stdlib.h> /* This is the structure type that we will use to make the list */ typedef struct tagname { int data; struct tagname *next; } LIST; void traverse(char *title,LIST *q) { /* traverse the list starting at head and going until we run out of nodes at NULL. */ printf("%s\n",title); printf("head->"); while(q) { printf("%d-> ",q->data); q = q->next; } printf(" NULL\n\n"); } void main() { int j; /* This will point to the head of the list we will make. Since initially the list is empty we make it point to NULL*/ LIST *head=NULL; /* Let's create two variables of LIST type which will add to our list */ LIST first, second; /* We will also make a pointer to the LIST type which will allow us to malloc() a node */ LIST *third,*p; /************************************************************* STEP 1: DUMP THE EMPTY LIST **************************************************************/ traverse("DUMP EMPTY LIST",head); /************************************************************* STEP 2: ADDING ONE ELEMENT TO THE LIST **************************************************************/ /* We make head point to first and since we have a variable (as opposed to an address) we can use the dot notation and assign a value of 1 to the data field. */ head = &first; first.data = 1; first.next = NULL; traverse("ADD ONE ELEMENT TO LIST",head); /************************************************************* STEP 2: ADDING ANOTHER ELEMENT TO THE LIST **************************************************************/ /* We make the first node point to the second node We put 2 in the data field of second and again terminate the list */ first.next = &second; second.data = 2; second.next = NULL; traverse("ADD SECOND ELEMENT TO LIST",head); /************************************************************* STEP 3: ADDING ANOTHER ELEMENT TO THE LIST (THIS TIME SPACE FROM MALLOC() **************************************************************/ /* Since third is a pointer which currently points at nothing we must malloc() some space before the next field of second can point to it.*/ third = (LIST *) malloc(sizeof(LIST)); second.next = third; /* note we don't need the & since third is already an address since it is a pointer */ third->data = 3; /* note the use of pointer notations */ third->next = NULL; traverse("ADD THIRD ELEMENT TO LIST USING POINTER",head); /************************************************************* STEP 4: DELETE second FROM THE LIST **************************************************************/ first.next = third; /* link first to third via the next field (DON'T CALL free()) */ traverse("DELETE THE SECOND ELEMENT FROM THE LIST",head); /************************************************************* STEP 5: DELETE third FROM THE LIST AND ADD second **************************************************************/ first.next = &second; second.next = NULL; free(third); traverse("DELETE THE LAST AND REPLACE IT",head); /************************************************************* STEP 6: ADDING THREE MORE NODES TO THE LIST **************************************************************/ p = &second; for(j=4;j<=6;j++) { third = (LIST *) malloc(sizeof(LIST)); third->data = j; third->next = NULL; p->next = third; p = p->next; traverse("ADD ANOTHER NODE",head); } /************************************************************* STEP 7: INSERTING AN ELEMENT BETWEEN second AND THE NEXT ELEMENT **************************************************************/ third = (LIST *) malloc(sizeof(LIST)); third->data = 3; third->next = second.next; second.next = third; traverse("INSERT AN ELMENT BETWEEN SECOND AND NEXT NODES",head); }