【数据结构】list.h 常用函数实现详解


相关文章:

【数据结构】list.h 常用函数实现详解
【数据结构】list.h 详细使用教程 – 附带例子代码


在这里插入图片描述

一、概述

list.h文件里面实现了一个带头结点的循环双向链表,也实现了哈希表。不过在平时编程中,大多是使用其中的循环双向链表。 本文主要介绍这个循环双向链表的常用函数是怎样实现的。list.h文件位于Linux内核源码目录的include/linux/list.h,不同的内核版本,list.h文件会有些许不同,但总会有下面这些常用函数,这些函数也是本文分析重点

  • INIT_LIST_HEAD:链表头结点初始化;
  • list_add:链表头部插入条目;
  • list_add_tail:链表尾部插入条目;
  • list_del:删除条目
  • list_for_each:遍历条目
  • list_for_each_safe:遍历条目,防删除列表条目
  • list_entry:获取结构体指针
  • list_empty:判断链表是否为空

在这里插入图片描述

二、基础函数

这小节主要看 初始化函数 和 判断空链表函数 的实现。

介绍链表,一定需要先认识它的结点结构体,list.h的循环双链表结点结构体只包含了两个指针:next指针指向下一个结点;prev指向前一个结点。它没有像其他链表结点那样也包含了数据部分,那它是怎样存储数据并查找数据的呢?这个在后面讲解。结点结构体代码如下:

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

✨2.1 INIT_LIST_HEAD

链表头结点初始化函数实际上是一个宏,其代码实现如下:

#define INIT_LIST_HEAD(ptr) do { \
	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)

就是将头结点的next指针 和prev指针 都指向自己。
在这里插入图片描述


✨2.2 list_empty

这是判断空链表函数,空链表返回真,非空链表返回假。从上面的图可知,当头结点的next指向自己,就是空链表:

/**
 * list_empty - tests whether a list is empty
 * @head: the list to test.
 */
static inline int list_empty(const struct list_head *head)
{
	return head->next == head;
}

在这里插入图片描述

三、添加结点的函数

添加结点主要介绍三个函数:__list_addlist_addlist_add_tail

✨3.1 __list_add

__list_add 是添加结点的基础函数,是一个静态内联函数。是list.h内部操作函数,一般不直接调用。
函数参数:
新结点指针;
上个结点指针;
下个结点指针;

代码如下:

/*
 * 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_node,
		struct list_head *prev,
		struct list_head *next)
{
	next->prev = new_node;
	new_node->next = next;
	new_node->prev = prev;
	prev->next = new_node;
}

分析:代码是先把新结点与next连接,再把新结点与prev连接,其操作如下图:
在这里插入图片描述


✨3.2 list_add

list_add 是在链表头部插入结点,它调用了__list_add去实现的;

函数参数:
new_node:新结点指针
head:头结点指针

代码实现如下:

/**
 * 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_node, struct list_head *head)
{
	__list_add(new_node, head, head->next);
}

分析:在头部插入新结点就相当于在头结点和第一个结点之间插入,链表不为空的情况下,head->next指向第一个结点,操作如下图:
在这里插入图片描述
如果是空链表,head->next指向head,相当于调用 __list_add(new_node, head, head);,操作如下图:
在这里插入图片描述


✨3.3 list_add_tail

list_add_tail 是在链表尾部插入结点,也是它调用了__list_add去实现的;

函数参数:
new_node:新结点指针
head:头结点指针

代码实现如下:

/**
 * 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_node, struct list_head *head)
{
	__list_add(new_node, head->prev, head);
}

分析:在尾部插入新结点就相当于在头结点和最后结点之间插入,链表不为空的情况下,head->prev指向第一个结点,操作如下图:
在这里插入图片描述


在这里插入图片描述

四、删除结点的函数

删除结点主要介绍两个函数:__list_dellist_del

✨4.1 __list_del

__list_del 是删除结点的基础函数,是list.h内部操作函数,一般不直接调用。

函数参数:
prev:要删除的结点的上一个结点指针
next:要删除的结点的下一个结点指针

代码实现如下:

/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	prev->next = next;
}

在这里插入图片描述


✨4.2 list_del

list_del可以删除指定的结点,其参数是一个有效的结点指针。调用了__list_del(entry->prev, entry->next);,把当前结点的上个结点和下个结点作为参数传给 __list_del,删除了自己。

/**
 * list_del - deletes entry from list.
 * @entry: the element to delete from the list.
 * Note: list_empty on entry does not return true after this, the entry is
 * in an undefined state.
 */
static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	//entry->next = (struct list_head *)LIST_POISON1;
	//entry->prev = (struct list_head *)LIST_POISON2;
}

在这里插入图片描述

五、获取结构体指针、遍历链表

这小节介绍怎list.h的链表怎样通过struct list_head *获取到其所在结构体的指针。仔细看前面的代码可以观察到,无论是初始化、添加、删除结点都只操作了结点的prevnext指针。那怎么存储和获取数据呢?这小节就给出答案。

list.h 要求定义数据结构体时,必须包含struct list_head定义的成员,添加或删除结点时,把结构体的struct list_head成员的指针添加到链表或从链表删除;这样就可以把各个结点联系起来并存储了。但是从链表获取结点,也只是获取到struct list_head的指针,list.h 提供一个list_entry函数可以通过struct list_head结构体类型成员名称 来获取到结构体的指针,通过结构体指针就可以获取数据了。

✨5.1 list_entry

list_entry可以通过struct list_head结构体类型成员名称 来获取到结构体的指针。

函数参数:
ptrstruct list_head*指针,指向链表某个结点
type:包含了struct list_head成员的结构体类型
memberstruct list_head成员的名称

#ifndef offsetof
#define offsetof(type, f) ((size_t) \
		((char *)&((type *)0)->f - (char *)(type *)0))
#endif

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

/**
 * list_entry - get the struct for this entry
 * @ptr:	the &struct list_head pointer.
 * @type:	the type of the struct this is embedded in.
 * @member:	the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

分析:
1、先看 offsetof 这个宏是干什么的?
它的输入参数是一个结构体类型type 和 成员名称f,最后会获取到成员f在整个结构体类型type的偏移量,单位是字节。

2、再看看 container_of 宏的作用
它的输入参数是:struct list_head*的指针ptr、所在的结构体类型typestruct list_head成员名称member,先用typeof获取member的类型并定义该类型的指针 __mptr,然后将 ptr 赋值给 __mptr,最后用 __mptr 减去 (member 成员在该结构体的偏移量),得到了结构体的开始地址,并用(type *)做类型转换。

3、最后看 list_entry
它直接调用 container_of 宏,通过地址ptr、类型type、成员名称member,获取到type*结构体指针。

也就是说,我们调用时需要传入struct list_head*指针、包含了struct list_head成员的结构体类型struct list_head成员名称,就可以获取到 struct list_head*指针 所在的结构体的地址。

此外,有些较新版本的内核代码,支持获取首结点、尾结点,其代码实现如下,ptr只需要传入头结点指针即可:

#define list_first_entry(ptr, type, member) \
	list_entry((ptr)->next, type, member)
	
#define list_last_entry(ptr, type, member) \
	list_entry((ptr)->prev, type, member)

✨5.2 list_for_each

知道了怎样通过struct list_head*指针来获取结构体数据后,就可以来遍历链表了。这里先介绍list_for_each,但平时更多地使用list_for_each_safe

list_for_each函数参数:
pos:用于遍历的指针,会依次指向链表的各个结点
head:链表头结点

#ifndef ARCH_HAS_PREFETCH
static inline void prefetch(const void *x) {;}
#endif

/**
 * list_for_each	-	iterate over a list
 * @pos:	the &struct list_head to use as a loop counter.
 * @head:	the head for your list.
 */
#define list_for_each(pos, head) \
	for (pos = (head)->next; prefetch(pos->next), pos != (head); \
			pos = pos->next)

分析:这个宏展开后,是一个for循环,pos会依次指向链表的各个结点。实际使用时,通过pos去获取数据。


✨5.3 list_for_each_safe

list_for_each_safe是更常使用的,它可以避免在使用过程误删了结点。

函数参数:
pos:用于遍历的指针,会依次指向链表的各个结点;
n:用作临时存储的变量,在该for循环外无作用;
head:链表头结点;

/**
 * list_for_each_safe	-	iterate over a list safe against removal of list entry
 * @pos:	the &struct list_head to use as a loop counter.
 * @n:		another &struct list_head to use as temporary storage
 * @head:	the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
	for (pos = (head)->next, n = pos->next; pos != (head); \
			pos = n, n = pos->next)

分析:这个函数比list_for_each安全,每次循环前,会先把下个指针保存起来,避免pos被误删后找不到下个指针。


✨5.4 list_for_each_entry

list_for_each_entry 可以遍历给定类型的列表;

函数参数:
pos:要用作循环计数器的结构体类型指针
head:链表头结点指针
member:结构中 struct list_head 的成员名称

/**
 * list_for_each_entry	-	iterate over list of given type
 * @pos:	the type * to use as a loop counter.
 * @head:	the head for your list.
 * @member:	the name of the list_struct within the struct.
 */
#define list_for_each_entry(pos, head, member)				\
	for (pos = list_entry((head)->next, typeof(*pos), member);	\
			prefetch(pos->member.next), &pos->member != (head); 	\
			pos = list_entry(pos->member.next, typeof(*pos), member))

总结

本文介绍了 list.h 的函数实现,对想知道如何使用 list.h 有一定帮助。只要将上面的实现代码都复制起来,放到一个my_list.h的头文件,就可以得到一个简约版本的list.h,并且可以使用到上面所有函数。

// my_list.h

#ifndef _LINUX_LIST_H
#define _LINUX_LIST_H

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


#define INIT_LIST_HEAD(ptr) do { \
	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)

/**
 * list_empty - tests whether a list is empty
 * @head: the list to test.
 */
static inline int list_empty(const struct list_head *head)
{
	return head->next == head;
}

/*
 * 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_node,
		struct list_head *prev,
		struct list_head *next)
{
	next->prev = new_node;
	new_node->next = next;
	new_node->prev = prev;
	prev->next = new_node;
}

/**
 * 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_node, struct list_head *head)
{
	__list_add(new_node, 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_node, struct list_head *head)
{
	__list_add(new_node, head->prev, head);
}

/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	prev->next = next;
}

/**
 * list_del - deletes entry from list.
 * @entry: the element to delete from the list.
 * Note: list_empty on entry does not return true after this, the entry is
 * in an undefined state.
 */
static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	//entry->next = (struct list_head *)LIST_POISON1;
	//entry->prev = (struct list_head *)LIST_POISON2;
}

#ifndef offsetof
#define offsetof(type, f) ((size_t) \
		((char *)&((type *)0)->f - (char *)(type *)0))
#endif

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

/**
 * list_entry - get the struct for this entry
 * @ptr:	the &struct list_head pointer.
 * @type:	the type of the struct this is embedded in.
 * @member:	the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

#define list_first_entry(ptr, type, member) \
	list_entry((ptr)->next, type, member)
	
#define list_last_entry(ptr, type, member) \
	list_entry((ptr)->prev, type, member)

#ifndef ARCH_HAS_PREFETCH
static inline void prefetch(const void *x) {;}
#endif

/**
 * list_for_each	-	iterate over a list
 * @pos:	the &struct list_head to use as a loop counter.
 * @head:	the head for your list.
 */
#define list_for_each(pos, head) \
	for (pos = (head)->next; prefetch(pos->next), pos != (head); \
			pos = pos->next)

/**
 * list_for_each_safe	-	iterate over a list safe against removal of list entry
 * @pos:	the &struct list_head to use as a loop counter.
 * @n:		another &struct list_head to use as temporary storage
 * @head:	the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
	for (pos = (head)->next, n = pos->next; pos != (head); \
			pos = n, n = pos->next)
			
/**
 * list_for_each_entry	-	iterate over list of given type
 * @pos:	the type * to use as a loop counter.
 * @head:	the head for your list.
 * @member:	the name of the list_struct within the struct.
 */
#define list_for_each_entry(pos, head, member)				\
	for (pos = list_entry((head)->next, typeof(*pos), member);	\
			prefetch(pos->member.next), &pos->member != (head); 	\
			pos = list_entry(pos->member.next, typeof(*pos), member))


#endif //_LINUX_LIST_H

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
05-31
在计算机编程中,List是一种常见的数据结构,用于存储一组元素。List通常是一个有序的集合,其中每个元素都可以通过其在列表中的位置(索引)来访问。 在Java中,List是一个接口,定义了一些常用的列表操作方法,如添加、删除、获取元素等。Java中常用List实现类有ArrayList和LinkedList,它们都实现List接口并提供了各自的特性。 例如,我们可以通过以下代码创建一个ArrayList并向其中添加一些元素: ``` List<String> list = new ArrayList<>(); list.add("apple"); list.add("banana"); list.add("orange"); ``` 这里我们首先创建了一个ArrayList对象,并将其存储在变量list中。然后,我们使用add()方法向列表中添加了三个字符串元素。需要注意的是,我们可以使用List接口来声明变量,这样可以使得代码更加通用和灵活。 除了添加元素,List还可以进行其他常用的操作,如删除元素、获取列表大小、遍历列表等。例如: ``` list.remove("banana"); int size = list.size(); for (String element : list) { System.out.println(element); } ``` 这里我们使用remove()方法删除了列表中的一个元素,使用size()方法获取了列表的大小,使用for-each循环遍历了列表中的所有元素,并将其输出到控制台上。 需要注意的是,List是一个接口,它定义了一些基本的操作方法,但并不限制List实现方式。因此,在使用List时需要根据实际情况选择合适的实现类,并根据需要对其进行扩展和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wkd_007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值