In your case, the head and tail can simply point to the beginning and end of the linked list. With a single linked list, only a head is really needed. It has the simplest, linked list, you can make using only such a structure as:
typedef struct listnode {
and as long as the list always points to the beginning of the list and the last element has the next NULL, you are fine and you can use current_node to move around the list. But sometimes, to simplify moving the list and store any other data about the list, a head and tail token is used and wrapped in their own structure, as you did. So, your add and initialize functions will be something like (minus error detection)
// Initialize linked list void initialize(list_t *list) { list->head = NULL; list->tail = NULL; } void add(list_t *list, int code, char name[], int cost) { // set up the new node product_data_t *node = (product_data_t*)malloc(sizeof(product_data_t)); node->code = code; node->cost = cost; strncpy(node->product_name, name, sizeof(node->product_name)); node->next = NULL; if(list->head == NULL){ // if this is the first node, gotta point head to it list->head = node; list->tail = node; // for the first node, head and tail point to the same node }else{ tail->next = node; // append the node tail = node; // point the tail at the end } }
In this case, since it is the only linked list, the tail is really only useful for adding items to the list. To insert an item, you will need to go through the list starting at the head. Where the tail really comes in handy, there is a doubly linked list; it allows you to navigate through the list starting at both ends. You can go through this list, for example
// return a pointer to element with product code product_data_t* seek(list_t *list, int code){ product_data_t* iter = list->head; while(iter != NULL) if(iter->code == code) return iter; iter = iter->next; } return NULL; // element with code doesn't exist }
Often, the head and tail are fully engineered nodes that are used as sentinels to indicate the beginning and end of a list. They themselves do not store data (more precisely, their data is a watch marker), they are just holders of space for the front and rear. This can facilitate the coding of some algorithms associated with linked lists, due to the presence of two additional elements. In general, linked lists are flexible data structures with several ways to implement them.
Oh yes, and nik is right, playing with linked lists is a great way to get good directions with pointers and indirection. And they are also a great way to practice recursion! After you get good results with a linked list, try building a tree and use recursion to jump to the tree.