C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
147: Implementing a Singly Linked List
Imagine you're on a scavenger hunt. You start with a single slip of paper in your hand. That paper has two things on it: a secret message and a set of directions to where the next slip of paper is hidden. You go to that location, find the second slip, read its message, and get the directions to the third. This continues until you find a slip that says "The End" and provides no further directions.
That's exactly how a singly linked list works. Unlike an array, where every element sits right next to its neighbor in a neat row of memory, linked list elements (which we call "nodes") can be scattered anywhere in your RAM. They stay connected because each node holds a pointer to the next one.
Defining the Node Blueprint
In C, we can't just "make" a node; we have to define what one looks like using a struct. Each node needs to hold the actual data you care about and a pointer to another struct of the same type. This is called a self-referential structure.
typedef struct Node {
int data; // The "secret message"
struct Node* next; // The "directions to the next slip"
} Node;
I prefer using a typedef here. It saves us from writing struct Node every single time we declare a variable, which makes the code much cleaner as the logic gets more complex.
Stitching Nodes Together
To get the list started, you only need to keep track of one thing: the head. The head is just a pointer to the first node. If the head is NULL, your list is empty.
Adding a new element to the front of the list is the most efficient way to grow it. You create a new node, point its next pointer to the current head, and then tell the head to point to this new node. It's a quick swap that doesn't require shifting any other elements around.
void push(Node** head_ref, int new_data) {
// 1. Allocate memory for the new node
Node* new_node = (Node*)malloc(sizeof(Node));
// 2. Put in the data
new_node->data = new_data;
// 3. Make the new node point to the old head
new_node->next = (*head_ref);
// 4. Move the head to point to the new node
(*head_ref) = new_node;
}
Notice I used Node** head_ref. I'm passing a pointer to the head pointer because we need to actually change the head's address in the calling function. If I just passed Node* head, I'd be changing a local copy, and the original head back in main() would still be pointing to the old starting position.
Walking the Chain
To see what's inside your list, you "traverse" it. You start at the head and follow the next pointers in a loop until you hit NULL. This is the "The End" slip of paper from our analogy.
void printList(Node* node) {
while (node != NULL) {
printf("%d -> ", node->data);
node = node->next;
}
printf("NULL\n");
}
Tearing it All Down
Here is where I see most people mess up. You can't just free(head) and call it a day. If you do that, you've deleted the first node, but you've also deleted the only pointer you had to the second node. You've just created a massive memory leak.
You have to be careful: save the pointer to the next node before you kill the current one.
void deleteList(Node** head_ref) {
Node* current = *head_ref;
Node* next;
while (current != NULL) {
next = current->next; // Save the next node
free(current); // Now it's safe to delete the current one
current = next; // Move to the saved next node
}
*head_ref = NULL;
}📋 Practical Task
Implementing a "Pop" Function to Remove the Head
Now that you know how to push nodes onto a linked list, I want you to write the opposite. Create a function called pop that removes the first node (the head) from the list and returns the data that was stored inside it.
Requirements:
- The function signature should be:
int pop(Node** head_ref). - You must handle the case where the list is already empty (perhaps by printing an error or returning a sentinel value like -1).
- Crucially, you must
free()the memory of the removed node to avoid leaks. - The
head_refmust be updated so the second node in the list becomes the new head.
Test your function by pushing three numbers (10, 20, 30) onto a list, then calling pop and printing the returned value to ensure you get 30 back first.
There are no comments for now.