内核链表用法

以一个贯穿始终的 student 结构体和名为 student_list_head 的链表头为例

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "list.h" // 假设你已包含 list.h
#include <stdlib.h>

// 示例数据节点:学生
struct student {
int id;
char *name;
struct list_head list; // 嵌入的“连接扣”,关系管理成员
};

// 贯穿所有示例的链表头指针
// 它的创建方式见下面的初始化部分
struct list_head student_list_head;

初始化

INIT_LIST_HEAD - 动态初始化链表头

对一个已分配内存、但未初始化的链表头进行初始化。这是动态创建链表时的必用函数

1
2
3
4
5
6
7
8
9
10
11
12
// 假设链表头是另一个动态结构体的一部分
struct class_roster {
char *class_name;
struct list_head student_list; // 链表头在这里
};

struct class_roster *my_class = malloc(sizeof(struct class_roster));

// 对其中的链表头成员进行初始化
if (my_class != NULL) {
INIT_LIST_HEAD(&my_class->student_list);
}

高级工具

list_entry - 从“连接扣”反查“本人”

内核链表的核心魔法。从指向list_head成员的指针,反向计算出其宿主结构体的起始地址。list_for_each_entry等宏在内部调用它

1
2
3
4
5
6
// 假设只拿到了一个 list_head 成员的指针 list_ptr
struct list_head *list_ptr = &some_student->list;

// 手动转换回 student 结构体指针
struct student *the_student;
the_student = list_entry(list_ptr, struct student, list);

list_add_tail - 在链表尾部添加 (最常用)

将新节点添加到链表头(head)的前面,实现尾部插入效果

1
2
3
4
5
struct student *new_stu = malloc(sizeof(struct student));
new_stu->id = 101;

// 将 new_stu 添加到 student_list_head 的尾部
list_add_tail(&new_stu->list, &student_list_head);

list_add - 在链表头部添加

将新节点添加到链表头(head)的后面,实现头部插入效果

1
2
3
4
5
struct student *new_stu = malloc(sizeof(struct student));
new_stu->id = 99;

// 将 new_stu 添加到 student_list_head 的头部
list_add(&new_stu->list, &student_list_head);

list_del - 从链中断开节点

将指定节点从链表中“摘出”。节点内存并未释放,可以重新插入或手动释放

1
2
3
4
5
6
// 假设 stu_to_delete 是要删除的节点指针
// 1. 从链中断开
list_del(&stu_to_delete->list);

// 2. 手动释放内存
free(stu_to_delete);

list_for_each_entry_safe - 安全遍历 (删除时专用)

遍历链表,同时允许在循环体内安全地删除当前节点

1
2
3
4
5
6
7
8
9
10
struct student *p, *next_p;

// 安全遍历,准备删除 id 为 101 的学生
// 参数为 当前元素 下一个元素 头节点管理点 关系管理元素名
list_for_each_entry_safe(p, next_p, &head.list, list) {
if (p->id == 101) {
list_del(&p->list);
free(p);
}
}

移动

一步完成“摘出”和“插入”

list_move - 移动节点到指定位置之后

将节点移动到目标节点的后面

1
2
// 把 rose_node 移动到 jack_node 的后面
list_move(&rose_node->list, &jack_node->list);

list_move_tail - 移动节点到指定位置之前

将节点移动到目标节点的前面。常用于将节点移动到队尾

1
2
// 把 bill_node 移动到整个链表的末尾 (即链表头的前面)
list_move_tail(&bill_node->list, &student_list_head);

查 & 遍历

list_for_each_entry - 遍历所有节点 (最常用)

安全、方便地遍历链表,在循环中直接获得指向大结构体的指针

1
2
3
4
5
6
7
8
struct student *p;

// 遍历由 student_list_head 管理的每一个学生
// 当前内容 头节点管理节点 节点类型
list_for_each_entry(p, &head.list, list) {
// 循环内,p 就是指向当前 student 结构体的指针
printf("ID: %d, Name: %s\n", p->id, p->name);
}

list_for_each - 遍历链表节点指针

遍历链表的所有 list_head 节点(不直接获取宿主结构体指针),适合只操作链表连接结构时使用。

1
2
3
4
5
6
7
8
9
10
struct list_head *pos;

// 遍历由 student_list_head 管理的链表中的每一个 list_head 节点
// 参数为 当前位指针 链表头节点
list_for_each(pos, &student_list_head) {
// pos 指向当前节点的 list_head 成员
// 若需获取宿主结构体,需用 list_entry 宏转换:
struct student *p = list_entry(pos, struct student, list);
printf("ID: %d, Name: %s\n", p->id, p->name);
}

list_for_each 是基于指针操作的低层次遍历宏,不做类型安全转换

list_empty - 检查链表是否为空

判断链表是否只包含头结点。返回 true (1) 或 false (0)

1
2
if (list_empty(&student_list_head)) 
printf("学生列表是空的。\n");