//****************************************************************************// //********* Linked Lists & Function Pointers - November 16th, 2017 **********// //**************************************************************************// - So, end of the semester, so much work, yadda yadda - welcome to Fall! ---------------------------------------------------- - So, we were talking about linked lists yesterday - but one thing that we didn't cover was what we should do if malloc() returns NULL int addToFront(...) { (...) NODE *temp = malloc(sizeof(NODE)); if (temp == NULL) { (...????...) } } - Well, we could have a status code like "2 = malloc_fail", and return THAT once we're done! - Then, when we're calling the method, we could surround the function call in a macro like IF_MALLOC_FAIL_PRINT_ERROR, or something - NOTE: There's something called "Hungarian Notation" that says that for a pointer to a type, you put a "p" in the variable anme (e.g. "pObject"); if it's a pointer to a pointer, you put TWO p's ("ppObject"); it's kind of fallen out of favor, but if you like it, feel free to use it - "A tip if you're a teacher: NEVER name a variable for a linked list 'ppHead'" - Now, to REMOVE the head, you first check that head != null; if it doesn't, you then get the data in "head", set the actual head to "head.next", and free the old head - So, once we're done with that, what can we do with our linked list? Well, we could create a STACK really easily! - We could also create a QUEUE, but there's an issue with that; we need to add node's to the back, but in order to do that, we have to reach the last node, which is an O(N) operation! - To make this easier, we'll create a TAIL pointer as well! - When we add the 1st node to the list, we point the tail to the HEAD node; after that, we leave the tail as-is until it gets to the end - Now, to FREE the list, unlike in Java, we can't just set our Head pointer to null; we have to actually go through all the items and free them: void freeList(LIST *lst) { NODE *cur = lst->head; NODE *temp; while(cur != NULL) { temp = cur->next; free(cur); cur = temp; } lst->head = NULL; lst->tail = NULL; } - NOTE: You should ONLY ever free data that YOU malloc()'d (do NOT try to free() something on the stack), which can make things tricky when you have a struct w/ pointers to non-malloc'd data. What should you do?...well, disappointingly, it depends. In general, if you malloc'd it yourself and you're only using it here, free it; otherwise, things get complicated, and YOU have to make the choice of what to do. - To make the linked-list generic, we change the "data" variable's datatype from int* to void*, making it a void pointer instead - Remember, void pointers ONLY hold the address, and CANNOT be dereferenced w/o being cast to a new pointer type - Now, let's take a quick detour to look at function pointers - Let's say you work for a company that's making a new GameBoy game for kids ("which is a TERRIBLE idea, you know, but they're paying you bags of money so you kinda just shrug and accept it"), and it's a game that lets kids draw things. One day, your boss comes in and tells you it's getting better: they're gonna use Mode 4 to let kids ANIMATE their movies! - A quick thing about MODE 4: it's a mode in the GameBoy that lets you draw animations via page-flipping; it's faster, but requires some special techniques - So, you start making 175 new MODE4-versions of your existing MODE3 functions...but that's a lot of copy-paste - You think of making an if-statement in each existing function to check if it's MODE3 or MODE4...but all those extra "if" statements are slowing down the game - ...you know what, screw examples, let's just get to the meat of what function pointers ARE: - This is a function prototype: int f1(int, int); - This is function protype that returns an int pointer: int *f2(int*, int); - This is also a function prototype: void setPixel(int, int, u16); - This is function pointer: void *fp(int, int, u16); //Wait, the compiler will think that's a protoype! This won't work! void (*fp)(int, int, u16); //Phew! NOW the compiler knows "fp" is a pointer to a void _____(int, int, u16) function! fp = setPixel; //Sets fp to the function address! (*fp)(3, 4, BLUE); //dereferences the pointer, then calls the function w/ those arguments! - ...BUT WAIT, THERE'S MORE! As syntactic sugar, C only requires us to write THIS to call a function in a function pointer: fp(3, 4, blue); //this'll work just fine! - Some people prefer this, since it's writing less; other people say you should EXPLICITLY de-reference the function pointer with the asterisk first to make it clear what you're doing - "I actually wrote Kernighan (who now teaches at Princeton) a note to see if he prefers the star or the syntactic sugar, and he wrote back! Basically, he said 'well, I prefer the star, but you can do whatever the heck you want'"