通用链表(内核双向循环链表list_head)

     在数据结构上学习链表一般都是这么定义的:

      struct _DList

      {

                DataType data;

                ……

                ……

                struct _DList *next;

                struct _DList *prev;

      };

 

      如果我们一遇到要使用链表的时候,我们都会本能的去想到这个结构来定义我们的链表。但这个标准结构的链表是针对性的链表,是专用的链表,只适合你来用,有没有的一种办法做一个通用的链表,再提供一套通用的操作函数呢?

 

      1、以前我在李先静先生的博文上看到过一种实现通用链表的方法,就是链表里存储是指针(void *data),链表里的这个指针可以指向任意对象,而且不必要求没个结点指向的是相同的类型的数据,再提供一套操作链表的函数接口,如创建、添加、删除、销毁等函数。但问题来了遍历函数怎么写呢?李先生也提供了一个非常好的方法,因为实现者不知道链表结点的内容,但调用者知道啊,这样实现者提供的接口只做遍历,具体遍历里面做什么事情,由调用者去写,实现者在接口里回调调用者的函数。

      李先生这种实现通用链表非常巧妙,特别是对提供的遍历接口函数中回调调用者函数。

      struct _DList

      {

                void *data;

                struct _DList *next;

                struct _DList *prev;

      };

 

 

      2、这两天在做课程设计的时候学到了另一种实现通用链表的方法:

      内核中的双向循环链表list_head。

      list_head并没有把要存储的内容放到链表结点里,list_head里只定义了指向上一个的指针和指向下一个的指针:

      struct list_head

      {

                struct list_head *next;

                struct list_head *prev;

      };

 

      看到这个结构,你或许会问那我们的数据放哪呢?别急!

      list_head只为我们定义了链表的钩子,我们要把我们的数据挂到这个钩子上面去:

      struct _DList

      {

                Datatype data;

                ……

                ……

                struct list_head list;

      };

 

      这样我们就把我们的数据挂到钩子上面去了,我们的数据也就链起来了。

 

      你发现了没有这样定义的链表结点,通过next与prev访问得到的并不是结点的地址(结点最上面的地址),只是结点成员list的地址。但是我可以通过list成员在结点里的偏移求出结点的地址。

      在list.h的头文件里面提供了这样一个宏:


       #define list_entry(ptr, type, member) /

                    ((type*))((char *)(ptr) - (unsigned long)(&((type *)0)->member)))

 

       ptr是通过next与prev得到的地址,type是你定义结点的类型(这里就是struct _DList),member是结点里钩子struct list_head的标识符(这里是list)

 

       现在我们来分析下这个宏:

       1)、((type*))这是一个强制类型转换,因为得到的地址要强制转换成结点的类型地址,才能通过这个地址访问结点里的成员;

       2)、我们再看后面这个括号里(…………),(char*)(ptr)这是通过next与prev把得到的地址强制转换成char*型,这样地址偏移步长每次只有一个字节(至于为什么不用我再多说了吧),地址减什么是有意义,我想大家都知道吧,所以后面这一堆……什么就是地址的偏移。

       3)、再来我来说说减去的这个偏移怎么算的吧。

               a)、(unsigned long)这又是一个强制类型转换,为什么这里也用个强制转换等我把它转换的内容分析完了再来说为什么。

               b)、 &(…………) 取地址,这个没什么好说的,但是取的是什么的地址呢?

                        ((type *)0)把 地址0x0 转换成结点类型地址,这样可以通过结点 地址((type *)0) 访问结点里面所有的成员。

                        ((type *)0)->member,也就是指向结点里的list_head的钩子的,再对其取地址&。

               c)、说到这里也许你已经明白了,钩子list_head的地址我们求出来了,他的地址是相对于 地址0x0而言的,所以我们对

                       这个地址(unsigned long)强制类型转换,它就变成了它在结点里的偏移。这也就回到了上面的问题了。

        4)、(char*)(ptr)减去这个偏移,不就是结点的地址吗!

 

 

        我对这个宏的设计者感到真是太佩服!这个宏的设计真是太妙了!

 

        另外在list.h的文件其他一些对链表常用操作的宏和函数,这里我只介绍其中的一个:

 

        对于链表的销毁与结点释放,就要遍历整个链表,但是我不能使用这个宏 list_for_each(pos, list)

 

        而要使用 list_for_each_safe(pos, pnext, list) 宏,这个没什么好讲的,用上面那个宏的时候结点都删了,怎么继续遍历下个节点啊!

 

        以上都是我个人的见解,如有雷同纯属你抄我的,呵呵~~开玩笑啦!但转载请说明出去!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值