本文介绍Linux中List.h文件,并对其详细分析,此文件的双向链表短小精湛,值得借鉴。此文件中还有哈希表的应用。
目录
一、通过函数定义的方式对双向循环链表的操作
1. List.h源码位置
2. list_head结构
3. 头结点的初始化
4. 双向循环链表的插入操作
(1)基本插入函数
(2)首部插入函数
(3)尾部插入函数
(4)与非循环单链表的插入操作的区别
5.双向循环链表的删除操作
(1)删除
(2)与非循环单链表删除操作的区别
6.双向循环链表的替换操作
(1)替换
7.双向循环链表的移动操作
8.双向循环链表的状态检测
9.双向循环链表的旋转操作
10.双向循环链表的切分操作
11.双向循环链表的合并操作
二、通过宏定义的方式对双向循环链表进行遍历
1.基本的宏
(1)list_entry宏
(2)list_first_entry宏
2.双向循环链表的遍历
(1)从左向右遍历
(2)从右向左遍历
(3)安全的向前向后遍历
(4)使用遍历
(5)list_prepare_entry宏
(6)下一个结点开始遍历
(7)当前结点开始遍历
(8)安全版本
三、哈希链表的操作
(注:基于Linux3.5版内核)
一、通过函数定义的方式对双向循环链表的操作
1. List.h源码位置
List.h文件位于include/linux目录,内容为双向循环链表的操作,对双向循环链表的定义如下:(与单链表相比,双链表的每个结点同时具有前驱指针和后驱指针,操作会更方便)
(1)首节点,存放第一个有效数据的结点。
(2)尾结点,存放最后一个有效数据的结点,在双向循环链表中,头结点的前驱指针指向尾结点,尾结点的后驱指针指向头结点。
(3)头结点:
1)头结点数据类型与首结点类型一样,首结点什么类型,头结点就是什么类型。
2)头结点一般不存放有效数据。
3)头结点是首结点前面的那个结点。
4)为了方便操作链表而提出的。
(4)头指针,指向头结点的指针,即存放头结点地址。
2. list_head结构
linux/types.h文件中定义了list_head结构,原型如下:
struct list_head
{
struct list_head *next;
struct list_head *prev;
};
在实际应用中,此结构经常作为成员与其他数据类型一起组成一个新的结构体,比如类似如下结构:
struct userinfo{
char username[20];
char password[20];
struct list_head list;
};
在以上述结构为结点的双向循环链表中,userinfo结构的list成员就是用来链接链表的各个成员的。
3. 头结点的初始化
在下面代码中,前面两个宏实现的功能与后面那个内联函数实现的功能一样。目的都是用来初始化双向循环链表的头指针,让其头指针指向的list_head型结构体变量的前/后驱指针成员指向头指针本身,即为空链表。
1. #define LIST_HEAD_INIT(name) { &(name), &(name) }
2. #define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)
3.
4. static inline void INIT_LIST_HEAD(struct list_head *list)
5. {
6. list->next = list;
7. list->prev = list;
8. }
比如在应用时,需使用上述任意一种方法初始化头指针,这里使用内联函数初始化头指针,如下代码:
struct userinfo userlist;
INIT_LIST_HEAD(&(userlist.list));
注意:函数参数传递是值传递(一个副本),上述参数传递list_head型结构体变量的地址。详见csdn博客上的文章。
4. 双向循环链表的插入操作(一个参数为待插入元素地址,一个为链表头结点地址)
对于插入操作,总共定义了三个函数,后两个函数都会调用前一个函数,详见下面的代码。第一个为基础函数,第二个为在首部插入,适用于堆栈;第三个为在尾部插入结点,适用于队列。
/*
* Insert a new entry between two known consecutive entries.
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
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;
}
/*
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
Static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
Static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
(1)基本插入函数
分析其参数,第一个参数new,是一个类型为list_head的指针,指向要增加的结点,而prev和next也都是list_head类型的指针,顾名思义,就是指向增加的位置的前面和后面的结点。
在函数内部,首先是把要增加位置的后面的结点的prev元素赋值为new,再把new的next元素指向后面结点,意思是先把后面的结点与新增结点相连;然后是把new的prev元素指向前面结点,再把前面结点的next赋值为new,意思是把前面的结点与新增结点相连。如下图:
(2)首部插入函数
与尾部插入函数的区别是传入__list_add函数的第2、3个参数不一样,这样应用非常巧妙。
根据上述代码,首部插入函数中调用__list_add 函数传入的参数分别为:new, head, head->next。(这三个参数可以另解为:新结点、头结点、首结点)
这里的head一般指struct list_head类型变量的地址,如第3中&(userlist.list),类似于非循环单链表的头指针。
首先,当链表为空链表时,head指针( 即上述例子中的&(userlist.list) )的前驱指针和后驱指针都指向了它自己。根据首部插入函数中调用__list_add 函数传入的参数可以很顺利地分析出在首部插入的过程,此时首结点即为new指向的结点。
其次,当链表至少有一个除头结点外的结点时,也能很顺利地分析出首部插入过程,此时的首结点也为new指向的结点。
(3)尾部插入函数
与首部插入函数的区别是传入__list_add函数的第2、3个参数不一样,这样应用非常巧妙。
根据上述代码,首部插入函数中调用__list_add 函数传入的参数分别为:new, head->prev, head。(新结点,尾结点,头结点)
这里的head一般指struct list_head类型变量的地址,如第3中&(userlist.list),类似与非循环单链表的头指针。
首先,当链表为空链表时,head指针( 即上述例子中的&(userlist.list) )的前驱指针和后驱指针都