数据结构内核链表的代码

说明

内核链表的诞生原因呢,就是为了把数据域和指针域分开来

就形成了下面这样的链表

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里面还有很多好用的函数,大家可以去看看,有移动的,判断是否为空的,合并两条链表的。把他们运用熟练起来的话,那就很厉害了。

  • 13
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值