内核链表的特点:
-
双向链表:每个节点都包含指向前驱节点和后继节点的指针,使得在链表中进行插入、删除、查找等操作非常高效。
-
嵌入式设计:内核链表的一个显著特点是链表节点通常嵌入在结构体中,通过
list_head
结构体实现。这样设计的好处是,可以让任意结构体通过list_head
成为链表的一部分。 -
高效操作:内核链表支持 O(1) 复杂度的插入和删除操作,非常适合处理大量数据结构的场景。
-
安全性:内核链表在删除元素时会设置
prev
和next
指针为空,防止误操作。
核心数据结构:
内核链表主要通过以下两个数据结构来实现:
struct list_head {
struct list_head *next, *prev;
};
这个结构体用于保存链表的两个指针,分别指向下一个和上一个节点。
常用操作:
1. 定义链表结构
定义一个双向链表的节点结构,类似于内核中的 list_head
,包含两个指针,分别指向前驱节点和后继节点。此外,还可以嵌入一些实际的数据。
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点结构
struct my_list_head {
struct my_list_head *prev; // 前驱节点
struct my_list_head *next; // 后继节点
};
// 数据节点结构,包含数据和链表指针
struct my_node {
int data;
struct my_list_head list; // 嵌入链表指针
};
2. 初始化链表头
链表头是一个特殊节点,指向链表的第一个和最后一个节点。我们可以用一个宏来简化链表的初始化操作。
// 初始化链表头宏
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); \
(ptr)->prev = (ptr); \
} while (0)
3. 插入节点
模仿内核链表的 list_add
和 list_add_tail
函数,来实现节点插入操作。将节点插入到链表尾部的函数。
// 在链表末尾插入节点
void list_add_tail(struct my_list_head *new, struct my_list_head *head) {
struct my_list_head *last = head->prev;
new->next = head;
new->prev = last;
last->next = new;
head->prev = new;
}
4. 删除节点
删除节点时需要更新前驱节点和后继节点的指针,确保链表的结构正确。
// 删除节点
void list_del(struct my_list_head *entry) {
struct my_list_head *prev = entry->prev;
struct my_list_head *next = entry->next;
prev->next = next;
next->prev = prev;
}
5. 遍历链表
我们可以实现一个遍历链表的函数,遍历所有的节点并打印其中的数据。
// 遍历链表并打印数据
void print_list(struct my_list_head *head) {
struct my_list_head *pos;
struct my_node *node;
for (pos = head->next; pos != head; pos = pos->next) {
node = (struct my_node *)((char *)pos - offsetof(struct my_node, list));
printf("Node data: %d\n", node->data);
}
}
6. 释放节点
遍历链表时可以删除并释放节点的内存。这里提供一个清空链表的函数。
// 清空链表并释放内存
void clear_list(struct my_list_head *head) {
struct my_list_head *pos, *next;
struct my_node *node;
for (pos = head->next; pos != head; pos = next) {
next = pos->next;
node = (struct my_node *)((char *)pos - offsetof(struct my_node, list));
list_del(pos);
free(node);
}
}
可以在 main
函数中测试链表的插入、遍历、删除等操作。
int main() {
// 初始化链表头
struct my_list_head my_list;
INIT_LIST_HEAD(&my_list);
// 添加节点
for (int i = 1; i <= 3; i++) {
struct my_node *new_node = (struct my_node *)malloc(sizeof(struct my_node));
new_node->data = i;
list_add_tail(&new_node->list, &my_list);
}
// 打印链表
printf("List contents:\n");
print_list(&my_list);
// 清空链表
clear_list(&my_list);
return 0;
}
- 链表节点结构:我们定义了
my_list_head
来表示链表节点的指针部分,类似于内核中的list_head
。同时,my_node
包含了数据和嵌入的链表指针。 - 初始化链表:通过
INIT_LIST_HEAD
宏,将链表初始化为一个空的循环双向链表。 - 插入节点:
list_add_tail
函数将新节点插入到链表的末尾。新节点会连接到链表的最后一个节点,并更新链表头的prev
指针。 - 删除节点:
list_del
函数会将指定节点从链表中移除,并更新前驱节点和后继节点的指针。 - 遍历链表:通过
print_list
函数,我们可以遍历链表中的每个节点,并打印它们的数据。 - 释放节点:
clear_list
函数遍历整个链表,删除所有节点,并释放它们的内存。
实际操作
假设我们要处理一个"学生信息"的数据类型,每个学生包括姓名、年龄、成绩等信息。我们可以使用链表来存储这些学生的信息。
1. 定义复杂数据结构
扩展 my_node
结构体,包含更复杂的数据(如姓名、年龄和成绩)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 链表节点结构
struct my_list_head {
struct my_list_head *prev;
struct my_list_head *next;
};
// 复杂数据类型——学生信息
struct student {
char name[50];
int age;
float grade;
struct my_list_head list; // 嵌入链表结构
};
2. 初始化链表
我们可以使用宏 INIT_LIST_HEAD
来初始化链表头。
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); \
(ptr)->prev = (ptr); \
} while (0)
3. 插入节点
接下来,定义一个函数来插入新的学生节点到链表中。该函数将学生的信息存储在节点中,并将节点插入到链表尾部。
// 在链表末尾插入节点
void list_add_tail(struct my_list_head *new, struct my_list_head *head) {
struct my_list_head *last = head->prev;
new->next = head;
new->prev = last;
last->next = new;
head->prev = new;
}
// 插入新的学生节点
void add_student(struct my_list_head *head, const char *name, int age, float grade) {
struct student *new_student = (struct student *)malloc(sizeof(struct student));
strcpy(new_student->name, name);
new_student->age = age;
new_student->grade = grade;
list_add_tail(&new_student->list, head);
}
4. 遍历链表
遍历链表,打印每个学生的信息:
// 遍历链表并打印每个学生的信息
void print_students(struct my_list_head *head) {
struct my_list_head *pos;
struct student *stu;
for (pos = head->next; pos != head; pos = pos->next) {
stu = (struct student *)((char *)pos - offsetof(struct student, list));
printf("Name: %s, Age: %d, Grade: %.2f\n", stu->name, stu->age, stu->grade);
}
}
5. 删除节点
实现一个函数来从链表中删除指定学生的信息:
// 删除节点并释放内存
void delete_student(struct student *stu) {
stu->list.prev->next = stu->list.next;
stu->list.next->prev = stu->list.prev;
free(stu);
}
// 按名字删除学生节点
void remove_student(struct my_list_head *head, const char *name) {
struct my_list_head *pos, *next;
struct student *stu;
for (pos = head->next; pos != head; pos = next) {
next = pos->next;
stu = (struct student *)((char *)pos - offsetof(struct student, list));
if (strcmp(stu->name, name) == 0) {
printf("Removing student: %s\n", stu->name);
delete_student(stu);
return;
}
}
printf("Student not found: %s\n", name);
}
6. 清空链表
清空链表时,遍历所有节点并删除:
// 清空链表并释放所有节点内存
void clear_students(struct my_list_head *head) {
struct my_list_head *pos, *next;
struct student *stu;
for (pos = head->next; pos != head; pos = next) {
next = pos->next;
stu = (struct student *)((char *)pos - offsetof(struct student, list));
delete_student(stu);
}
}
7. 测试数据的链表操作
int main() {
// 初始化链表头
struct my_list_head student_list;
INIT_LIST_HEAD(&student_list);
// 插入学生信息
add_student(&student_list, "Alice", 20, 88.5);
add_student(&student_list, "Bob", 21, 92.3);
add_student(&student_list, "Charlie", 19, 75.4);
// 打印所有学生的信息
printf("All students:\n");
print_students(&student_list);
// 删除一个学生
remove_student(&student_list, "Bob");
// 打印剩下的学生信息
printf("\nAfter removing Bob:\n");
print_students(&student_list);
// 清空链表
clear_students(&student_list);
return 0;
}
8. 输出结果
运行上述程序后,输出如下:
All students:
Name: Alice, Age: 20, Grade: 88.50
Name: Bob, Age: 21, Grade: 92.30
Name: Charlie, Age: 19, Grade: 75.40
Removing student: Bob
After removing Bob:
Name: Alice, Age: 20, Grade: 88.50
Name: Charlie, Age: 19, Grade: 75.40