COP 3223H meeting -*- Outline -*- * Singly-Linked Lists ** motivation ------------------------------------------ MOTIVATION FOR LISTS For the contacts app in the homework we limited the number of contacts. Why? Could we ask the user how many contacts they want to use? So, we want ------------------------------------------ ... So we could allocate an array to hold them (because arrays are of fixed size in C) ... No, they don't know, and their needs may change over time ... a data structure that can hold any number of items (only depending on the size of our computer's memory) and that can grow and shrink at runtime ** lists ------------------------------------------ LISTS Idea: - Use - Each list node points to - Like the Lisp Lists we used in Python ------------------------------------------ ... dynamically allocated memory ... the next node (or is NULL) *** data structure (header file) ------------------------------------------ DATA STRUCTURE Idea: - Use NULL pointer to - Use pointer to a struct to // file list.h #ifndef LIST_H #define LIST_H 1 #include /* ... declarations of list functions...*/ #endif ------------------------------------------ ... represent the empty list (Nil in the Python LispList type) ... represent a non-empty list (Cons in the Python LispList type) Note that we include stdlib.h so that we can talk about NULL ... typedef double ELEM; // so can change this easily struct node { ELEM value; struct node *next; }; typedef struct node *list; *** operations on lists ------------------------------------------ CREATION AND INITIALIZATION // requires: malloc has enough space to allocate a new node // ensures: result is a new node with first element val and tail tl extern list list_cons(ELEM val, list tl) { ------------------------------------------ ... list ret = (list)malloc(sizeof(node_t)); if (ret != NULL) { ret->value = val; ret->next = tl; } else { fprintf(stderr, "ERROR: malloc failed!"); abort(); } return ret; } *** accessors ------------------------------------------ ACCESSOR FUNCTIONS // ensures: result is true just when lst is an empty list extern bool list_isEmpty(list lst) { // requires: lst is not NULL // ensures: result is the first element of lst extern ELEM list_first(list lst) { // requires: lst is not NULL // ensures: result is the tail of lst extern list list_tail(list lst) { ------------------------------------------ ... return lst == NULL; } Q: Does list_isEmpty work for all kinds of lists? Yes, even empty ones. Can show list_first without the error checking first ... if (lst != NULL) { return lst->value; } else { fprintf(stderr, "ERROR: applying list_first to an empty list.\n"); abort(); } } Q: How would you write list_tail? ... if (lst != NULL) { return lst->next; } else { fprintf(stderr, "ERROR: applying list_tail to an empty list.\n"); abort(); } } *** mutators for nodes ------------------------------------------ MUTATOR OPERATIONS // requires: lst is not NULL // effect: the element stored at the first node is changed to val extern void list_set_first(list lst, ELEM val) { // requires: lst is not NULL // effect: the tail of the first node is changed to tl extern void list_set_tail(list lst, list tl) { ------------------------------------------ Could show list_set_first without the error checking, first ... if (lst != NULL) { lst->value = val; } else { fprintf(stderr, "ERROR: applying list_set_first to an empty list.\n"); abort(); } } Q: How would you write list_set_tail? ... if (lst != NULL) { lst->next = tl; } else { fprintf(stderr, "ERROR: applying list_set_tail to an empty list.\n"); abort(); } } ** error handling ------------------------------------------ ERROR HANDLING What should happen if NULL is passed to list_first or list_tail? Possibilities: - return - print an - return - set - throw ------------------------------------------ ... a specified error value (such as NULL) (if there is one for return type; this doesn't work for void or bool) ... error message and either continue or abort execution Q: Why might aborting execution be bad? Makes the code less generally useful, Some applications can't stop running (e.g., airplane controls) ... a struct with a struct containing the return value and a Boolean flag that indicates whether the function was successful Q: Why might returning a struct like that be problematic? The result must always be checked, and it makes function composition difficult (but one could use a "monad") ... a global flag (a Boolean) that indicates if there was an error Q: Why might setting a global flag be problematic? The flag must always be checked and reset, Doesn't work with concurrent threads, makes function composition difficult again ... an exception, meaning control is transferred up the runtime stack to the first exception handler that is ready to catch this exception. If the exception isn't caught, the progrma aborts. Q: Why might throwing exceptions be problematic? It's not a feature of C (although it is in C++). In C we can pick the mechanism that best suits the application.