说明
内核链表的诞生原因呢,就是为了把数据域和指针域分开来
就形成了下面这样的链表
struct list
{
struct list *prev; //存放前缀节点的指针
struct list *next; //存放后缀节点的指针
};
那有的读者会疑问,那数据放哪里?
//节点结构体
struct node
{
// 以整型数据为例
//数据域
int data;
//指针域
struct list_head list; //小结构体,里面有两个结构体指针
};
同样的道理,我们创建一个含有数据域的结构体,并且这个结构体包含了内核链表头文件的自带的结构体类型list_head;然后他们的关系就形成了下面这个图
在上面这个图里面,我们就只需要创建大结构体,而小结构体在list.h里面已经有了。
1.申请一个节点
//节点初始化 -- 在堆空间申请一个struct node结构体大小,并对成员进行初始化
struct node *init_node(void)
{
//在堆空间开辟sizeof(struct node)大小空间--大结构体
struct node *new = (struct node *)malloc(sizeof(struct node));
if(new == NULL)
{
printf("malloc fail\n");
return NULL;
}
new->data = 0;
//小结构体里面是两个指针,两个指针指向自己,形成双向循环循环
//手写初始化代码
// new->list.prev = &new->list;
// new->list.next = &new->list;
//使用内核链表API进行初始化小结构体
//传入小结构体的地址
INIT_LIST_HEAD(&new->list);
return new;
}
2.创建链表
申请完节点就可以创建链表啦,一句代码就可以创建。
struct node *head = init_node();
3.插入数据
插入数据的时候就不用自己封装函数啦,直接调用头文件提供的,如下
new = init_node();//生成节点
if(new == NULL)
{
printf("节点生成失败,请重新操作\n");
break;
}
printf("节点地址:%p\n", new);
printf("请输入数据:");
scanf("%d", &new->data);
//节点插入链表头
list_add(&new->list, &head->list);
很多观众可以不是很清楚,这两个参数表示什么意思,下面我们来看看头文件里的介绍
在这个函数里,可以看到参数的类型要求是struct list_head *类型,也就是小结构体。所以我们要传入的是大结构体里面的小结构体的地址。所以要给new->list取地址。
可以看到整个插入函数是插入在头节点后面的。当然还有插入到头节点前面的
4.查找数据
当我们查找数据的时候,往往要遍历数据,这时候内核链表又给我们提供了遍历链表的函数,在下面这个查找数据的函数中,用到了那个遍历链表的函数
struct node * find_data_in_list(struct node *head, int find_data)
{
struct node *tmp = NULL;
struct list_head *pos = NULL;
list_for_each(pos, &head->list)
{
//通过小结构体得到大结构体地址
tmp = list_entry(pos, struct node, list);
if(tmp->data == find_data)
return tmp;
}
return NULL;
}
我们看看遍历链表函数在内核链表中的解释
注释这里说到,pos是用来表示遍历内核链表时的位置。head是传入头节点。
这里注释还有说到,不安全的遍历方式,这个不安全的遍历方式是指,他遍历的过程中,没有把下一个位置的保存起来,而安全的遍历方式是会保存起来的。下面是安全的遍历
可以看到,他有一个n来保存下一个位置。如果删除所有指定的数据时,不使用安全的遍历方式,容易造成段错误,在下面删除就用到了这个安全的遍历方式。
那还有一个问题,list_entry这个函数是干嘛的呢?是这样的
我们在遍历时传入的数据全都是小结构体的地址,所以当我们要获取数据时,就得获取大结构体的地址,才可以访问到大结构体的数据域,因此我们就要利用这个函数,通过传入小结构体地址来获取大结构体的地址。
5.删除数据
这个函数是删除所有值为del_data节点的数据
void delete_data_int_list(struct node *head, int del_data)
{
struct node *tmp = NULL;
struct list_head *pos = NULL;
struct list_head *n = NULL;
list_for_each_safe(pos, n, &head->list)
{
tmp = list_entry(pos, struct node, list);
if(tmp->data == del_data)
{
list_del(&tmp->list);
free(tmp);
}
}
}
然后删除函数也不用我们去封装
在这个函数,我们只要传入小结构体的地址,就可以删除这个节点了。是不是很方便
6.修改数据
int modify_data(struct node *head,int find_data, int data)
{
int flag = 0;
struct node *tmp = NULL;
struct list_head *pos = NULL;
list_for_each(pos, &head->list)
{
//通过小结构体得到大结构体地址
tmp = list_entry(pos, struct node, list);
if(tmp->data == find_data)
{
tmp->data = data;
flag = 1;
}
}
return flag;
}
经过了上面的知识,修改数据就比较简单了,总的来说就是,传入小结构体遍历链表,获取大结构体的地址,通过判断每一个大结构体的数据域是否是要修改的数据。
结束语
差不多了,那个list.h里面还有很多好用的函数,大家可以去看看,有移动的,判断是否为空的,合并两条链表的。把他们运用熟练起来的话,那就很厉害了。
标签:node,head,struct,list,链表,内核,数据结构,data From: https://blog.csdn.net/m0_62978644/article/details/140874950