数据结构05 ------线性表(内核链表)

        内核链表实际上就是双向循环链表的升级版,因为双向循环链表的数据域与指针域是在同一个结构体中的,数据域与指针域始终是捆绑在一块的。由于不同的项目,数据域的结构可能是不同的,这样的话,由于数据域的不同,所以之前写好的链表操作就不能直接拿来使用,这样就不具备通用性。所以,大佬们就将数据域与指针域给分离出来,然后底层都实现封装好了不管数据域是什么,我们只对指针域进行操作。这样的话,不管是什么项目,关于链表操作,我们拿内核链表直接用就行了,灰常的方便、通用。

双向链表的结构体是这样的:

 struct link{
        datatype data;            //数据域
        struct link *prev;        //指针域 前面那个节点的地址
        struct link *next;        //指针域 后面那个节点的地址
    };

内核链表的结构体是这样的:

struct list_head{                //指针域的结构体
        struct list_head *next, *prev;
};

在 struct list_head 的基础上 加入 数据域 即可

struct kernel {
            datatype data;               //数据域
            struct list_head list;       //指针域的结构体

};

 将数据域剥离,只留下指针域

将指针域打包成一个结构体struct list_head,我们只需要对指针域结构体进行操作就行了。

内核链表分析:

typedef int datatype;

struct list_head{
    struct list_head *next,*prev;
};

typedef struct kernel{
    
    datatype data;    
    struct list_head list;    
    
} kernel_t,*pkernel_t;


节点初始化:

    第一种:
    #define LIST_HEAD_INIT(name) { &(name), &(name) }

    #define LIST_HEAD(name) \         //这个 \ 表示语句太长,后面的语句写在下一行
        struct list_head name = LIST_HEAD_INIT(name)
        
    分析:
        struct list_head name = { &(name), &(name) };         //这种方式是将链表定义在栈中,而不是定义在堆中,一般不用
        

第二种:
    static inline void INIT_LIST_HEAD(struct list_head *list)
    {
        list->next = list;
        list->prev = list;
    }
    
===============================================================================


节点的插入 :

//注意:开头带下划线的函数,都是系统内核函数,一般我们是不调用这样的函数的,这样的函数是给系统内部用的。

static inline void __list_add(struct list_head *new,
                  struct list_head *prev,
                  struct list_head *next)
    {
        next->prev = new;
        new->next = next;
        new->prev = prev;
        prev->next = new;
    }
    解释:
       new作为桥梁 将prev 和 next 链接起来了,new在中间,即将new节点插入到prev节点和next节点之间。其中new  prev  next 事先有没有关系 无所谓。


第一种:
    static inline void list_add(struct list_head *new, struct list_head *head)
    {
        __list_add(new, head, head->next);
    }
    解释:
        new插在了head后面,即new节点插到head节点和head->next节点之间
 

第二种:
    static inline void list_add_tail(struct list_head *new, struct list_head *head)
    {
        __list_add(new, head->prev, head);
    }
    解释:
        new插在了head前面,即new节点插到head节点和head->next节点之间。
    
    
==================================================================================


节点的删除:


    static inline void __list_del(struct list_head * prev, struct list_head * next)
    {
        next->prev = prev;
        prev->next = next;
    }
    解释:
     将prev 和 next这两个节点直接链接起来  next 在prev 的后面
        
        
   static inline void __list_del_entry(struct list_head *entry)
    {
        __list_del(entry->prev, entry->next);
    }
    解释:    
        将entry剪切出来了
        
第一种:
    static inline void list_del(struct list_head *entry)
    {
        __list_del(entry->prev, entry->next);
        entry->next = LIST_POISON1;   //NULL
        entry->prev = LIST_POISON2;   //NULL
    }

/*虚拟内存中,地址0x0000 0000到0x0804 8000 之间为保留区,这块空间里面都为NULL,所以,下面的地址0x00100100、0x00200200都在保留区的区间内,所以它们都为NULL,所以宏定义LIST_POISON1、LIST_POISON2都为NULL指针*/
   #define LIST_POISON1  ((void *) 0x00100100 + 0)
    #define LIST_POISON2  ((void *) 0x00200200 + 0)
    解释:
        1》将entry剪切出来了
        2》entry将prev和next指向NULL

        
第二种:
    static inline void list_del_init(struct list_head *entry)
    {
        __list_del_entry(entry);
        INIT_LIST_HEAD(entry);
    }
    解释:
        1》将entry剪切出来了
        2》entry自己指向自己 

        
================================================

节点的移动:


    static inline void list_move(struct list_head *list, struct list_head *head)
    {
        __list_del_entry(list);
        list_add(list, head);
    }
    解释:
        1》将list剪切出来了
        2》list插在了head后面
        
        
   static inline void list_move_tail(struct list_head *list,
                  struct list_head *head)
    {
        __list_del_entry(list);
        list_add_tail(list, head);
    }
    解释:
        1》将list剪切出来了
        2》list插在了head前面

=========================================================================​​​​​​​

难点:根据小结构体(即 struct list_head)寻找大结构体(即struct kernel):

struct kernel {
            datatype data;               //数据域
            struct list_head list;       //指针域的结构体

};

#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)


#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)


#define container_of(ptr, type, member) ({            \
    const typeof( ((type *)0)->member )  *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})

说明:
    ptr : 小结构体地址  (从外部传入)
    type :大结构体类型
    member : 大结构体中小结构体的名字

 

   
代码分析:


    container_of(ptr, type, member)
    const typeof( ((type *)0)->member )    *__mptr = (ptr);
分析:

        我们先来分析: const typeof( ((type *)0)->member )   *__mptr = (ptr);   首先typeof( ((type *)0)->member ) *是一个指针类型,其中typoof() 关键字是C语言中的一个新扩展,这个特性在linux内核中应用非常广泛。它可以取得变量的类型,或者表达式的类型。所以,typeof( ((type *)0)->member ) 就表示取( (type *)0 )->member的类型。再来分析( (type *)0 )->membe,type是由我们手动填写的大结构体类型,其中0就是0x0000 0000地址,所以(type *)0,就是将0x0000 0000地址强制转换为type *类型,也就是说,从地址0x0000 0000开始扩展,扩展成type类型大小的空间。这就像:

int a;

char *p;

(int *)p = &a;

        将a变量的地址赋值给p指针,此时p就是a变量的首地址,但是由于p指针为char类型,所以p指针指向的变量所在空间只能是char大小的空间,所以p = &a;只能指向a变量的前面char大小的空间,而不能指向整个int大小空间,所以我们使用强制转换(int *),这样就可以指向整个int大小空间。如果a变量的地址为0x0000 0000,即&a == 0x0000 0000,那么此时(int *)p = &a;就表示为:指针p 指向 以地址0x0000 0000为首地址,其空间为int大小,变量名字为a的变量。其中,由于p就是&a,所以p == 0x0000 0000,所以(int *)p = &a,我们可以改写为:(int *) 0x0000 0000,它等价于(int *)p = &a;

所以,(type *)0  就等价于:

        type a;        //假设a的地址等于0x0000 0000,

        (type *)p = &a;

即:指针p 指向 以地址0x0000 0000为首地址,其空间为type类型大小,变量名字为a的变量。


 

        由于type是大结构体类型,所以( (type *)0 )->member表示大结构体下面的member成员,即大结构体下面的小结构体的名字(这个member也是需要我们来手动填写)。                                         也就是(kernel_t *)0)->list

        所以 const typeof( ((type *)0)->member )   *__mptr = (ptr);表示:定义一个名字叫__mptr的指针,该指针类型为((type *)0)->member类型,即小结构体的类型。ptr为小结构体的地址,将小结构体的地址交给__mptr指针保存。其中(ptr)的括号可以不要。

        所以,const typeof( ((type *)0)->member ) *__mptr = (ptr);  等价于 const   struct list_head *__mptr = (ptr); 

        我们再来分析(type *)( (char *) __mptr  -  offsetof(type,member) );

        首先我们来分析offsetof(type,member)


#define   offsetof(TYPE, MEMBER)   ((size_t) &((TYPE *)0)->MEMBER)
 分析:
                (size_t) &((kernel_t *)0)->list
                ---> data所在区域的字节数 (对齐)
        分析:
                    (TYPE *)0)->MEMBER,type :大结构体类型,member : 大结构体中小结构体的名字,即(kernel_t *)0)->list(前面已描述过),  &(TYPE *)0)->MEMBER,即&(kernel_t *)0)->list,取小结构体的地址(注意:这里的小结构体地址并不是真正的小结构体首地址,而是在0x0000 0000基础上来计算的小结构体地址,它的作用是用来计算大结构体中数据域的大小,真正的小结构体首地址是ptr)

         我们可以发现,从地址&list到地址0x0000 0000,这块空间长度就是大结构体的数据域的空间长度,所以数据域的空间长度为 &list - 0x0000 0000 = &list,所以&list为数据域空间的长度。由于地址在32位系统中是4个字节,而在64位系统中为8个字节,不同的操作系统,地址的长度为一个字长。所以需要在地址前面加上强制转换(unsinged long), unsinged long的长度为一个字长。

        所以写成(size_t) &((kernel_t *)0)->list,其中size_t 就是 unsinged long。(size_t) &((kernel_t *)0)->list此时就表示数据域的字节数长度了(一个地址存储一个字节),如:地址0x0000 0008 - 0x0000 0000表示它们之间有8个字节数据。

我们再来看  (char *) __mptr  -  offsetof(type,member) :

        首先对于(char *) __mptr ,__mptr这个指针保存的是真正的小结构体的首地址,我们将其强制转换为char *类型,目的如下:

        int *p1;

        char *p2

        则p1 - 1表示的是p1地址减4,因为p1指针是int类型。而p2 - 1则是p2地址减1,因为p2指针是char类型。所以(char *) __mptr  -  offsetof(type,member),就表示真正的小结构体地址减去数据域的字节数长度,便可得到真正的数据域的首地址了,大结构体的数据域的首地址便是大结构体的首地址,这样我们就可以得到大结构体的首地址了。由于该地址是指向大结构体的,所以该地址形成的指针的指针类型为type *类型(即:kernel_t *类型),所以该地址需要强制转换为type *类型

        (type *)( (char *) __mptr  -  offsetof(type,member) );

        例:小结构体地址为0x0000 1234,即__mptr == 0x0000 ef36,而数据域的空间长度为0x0000 0016,即offsetof(type,member) == 0x0000 0016。那么大结构体的数据域首地址为:

        (char *) __mptr  -  offsetof(type,member) == 0x0000 ef36 - 0x0000 0016 == 0x0000 ef20,所以,大结构体的地址就为0x0000 ef20,指针为(type *)0x0000 ef20。

例子:

list_entry: 通过小结构体地址pos ,得到其所在的大结构体地址node    
例子:
            struct list_head *pos = 0xabcdef;
    
            pkernel_t node = list_entry(pos,kernel_t,list);

=============================================================

链表的遍历:

//这个宏定义是一个for循环
#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

说明:
    pos :     小结构体指针 中间变量
    head :   头节点小结构体指针  变量
    
    
分析:
    小结构体 正序 遍历  操作

===========================================================
#define list_for_each_prev(pos, head) \
    for (pos = (head)->prev; pos != (head); pos = pos->prev)
        
说明:
    pos :     小结构体指针 中间变量
    head :     头节点小结构体指针  变量
    
    
分析:
    小结构体 逆序 遍历  操作


============================================================= 

/* head为头节点中小结构体的地址(因为head->next,只有小结构有next指针域),而head->next,则为头节点的下一个节点的小结构体地址,也就是第一个节点的小结构体地址。list_entry则取到第一个节点的大结构体地址*/
#define list_for_each_entry(pos, head, member)                \
    for (pos = list_entry((head)->next, typeof(*pos), member);    \
         &pos->member != (head);     \
         pos = list_entry(pos->member.next, typeof(*pos), member))
        
说明:    
    pos :          大结构体地址 中间变量
    head :        头节点小结构体指针  变量
    member :   大结构中小结构名字  list 

分析:
    大结构体 正序 遍历  操作

        
         
============================================================
#define list_for_each_entry_reverse(pos, head, member)            \
    for (pos = list_entry((head)->prev, typeof(*pos), member);    \
         &pos->member != (head);     \
         pos = list_entry(pos->member.prev, typeof(*pos), member))
         
         
说明:    
    pos :        大结构体地址 中间变量
    head :        头节点小结构体指针  变量
    member :    大结构中小结构名字  list 

分析:
    大结构体 逆序 遍历  操作

==========================================================================

内核链表的操作:

/* 内核链表的 增  删  改  查  */


/*
 * @bref    创建新的节点
 * @param    d : 数据
 * @retval    新节点地址
 */
static pkernel_t create_node(datatype d)
{
    pkernel_t p = (pkernel_t)malloc(sizeof(kernel_t));
    if(p==NULL){
        perror("malloc error");
        return NULL;
    }
    
    p->data = d;
    INIT_LIST_HEAD(&p->list); //让节点自己指向自己
    
    return p;
    
}

/*
 * @bref    初始化
 * @param    None
 * @retval    头节点地址
 */
pkernel_t kernel_init(void)
{
    //在堆中开辟空间
    pkernel_t p = (pkernel_t)malloc(sizeof(kernel_t));
    if(p==NULL){
        perror("malloc error");
        return NULL;
    }
    
    INIT_LIST_HEAD(&p->list);
    
    return p;
       
}


/*
 * @bref    头插
 * @param    p : 头节点
 * @param    d : 数据
 * @retval    None
 */
void kernel_insert_head(pkernel_t p,datatype d)
{
    //创建新的节点
    pkernel_t node = create_node(d);
    if(node == NULL)
        return;
    
    //将新的节点插入到头的后面
    list_add(&node->list, &p->list);
}

/*
 * @bref    尾插
 * @param    p : 头节点
 * @param    d : 数据
 * @retval    None
 */
void kernel_insert_tail(pkernel_t p,datatype d)
{
    //创建新的节点
    pkernel_t node = create_node(d);
    if(node == NULL)
        return;
    
    //将新的节点插入到头的前面
    list_add_tail(&node->list, &p->list);
}


/*
 * @bref    删除
 * @param    p : 头节点
 * @param    d : 需要删除的数据
 * @retval    None
 */
 void kernel_del(pkernel_t p,datatype d)
 {
     struct list_head *pos = NULL;
     pkernel_t node = NULL;
     
     //    for (pos = (head)->next; pos != (head); pos = pos->next)
    list_for_each(pos, &p->list)
    {
        //通过小结构体地址 得到 大结构体地址
        node = list_entry(pos,typeof(*p),list);
        
        // 判断
        if(node->data == d){
            
            //pos往前移动一个
            pos = pos->prev;
            
            //将node节点 截切 并删除
            list_del_init(&node->list);
            
            //释放
            free(node);
        }
    }
 }
 
 

 /*
 * @bref    修改(替换)
 * @param    p : 头节点
 * @param    old : 需要修改的数据
 * @param    new : 新数据
 * @retval    None
 */
 void kernel_update(pkernel_t p,datatype old,datatype new)
 {
     pkernel_t pos = NULL;
     pkernel_t node = NULL;
     
     //找寻old所在节点
     list_for_each_entry(pos, &p->list, list)
     {
         if(pos->data == old){
             //创建新的节点
             node = create_node(new);
             if(node==NULL)
                 return;
             
             //node 替换 pos节点
            list_replace_init(&pos->list,&node->list);
            
            // 释放pos 空间
            free(pos);
            
            //关键一步
            pos = node;        /* 为了让程序能够继续往下走,因为前面 free(pos);已经把pos节点给释放掉了,而pos是for循环的中间值,如果没有这一步,那么for无法继续循环下去。*/


         }
     } 
 }


/*
 * @bref    正序遍历
 * @param    p : 头节点
 * @retval    None
 */
void display(pkernel_t p)
{
    
    printf("内核链表正序遍历结果为:");
    pkernel_t pos = NULL;
    
    /*    for (pos = list_entry((head)->next, typeof(*pos), member);    \
         &pos->member != (head);     \
         pos = list_entry(pos->member.next, typeof(*pos), member))*/
    
    list_for_each_entry(pos,&p->list, list)        //用大结构体遍历
        printf("%d ",pos->data);
    
    printf("\n");
}

/*
 * @bref    逆序遍历
 * @param    p : 头节点
 * @retval    None
 */
void pre_display(pkernel_t p)
{
    
    printf("内核链表逆序遍历结果为:");
    pkernel_t pos = NULL;
    
    list_for_each_entry_reverse(pos,&p->list, list)        //用大结构体遍历
        printf("%d ",pos->data);
    
    printf("\n");
}

/************************************
 *display :  内核链表的正序遍历
 *@head : 头节点
*/
void display(pkernel_t head)
{
    printf("正序遍历的结果为:");
    struct list_head *pos = NULL; //for中的中间变量
    pkernel_t node = NULL;
    
    //for (pos = (head)->next; pos != (head); pos = pos->next)
    list_for_each(pos,&head->list)                       //用小结构体遍历
    {
        node = list_entry(pos, typeof(*head), list);
        printf("%d ",node->data);
    }
    printf("\n");
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值