数据结构之线性表(bsd, sys/queue.h)

2023-10-27

数据结构之线性表

Author:Once Day Date:2023年5月27日

参考文档:

1.概述

  • 线性表(linear list)存在唯一的头部数据和尾部数据,而且中间的每个数据都有一个前驱元素和后驱元素。
  • 线性表是有限个元素的序列,如一维数组和链表。
  • 每个数据元素可由若干个数据项构成。

数学表达可如下:
( a 1 , a 2 , . . . . , a n − 1 , a n ) (a_1,a_2,....,a_{n-1},a_n) (a1,a2,....,an1,an)
**数据元素n即为线性表的长度。**也可以看到每个元素都有个确切的位置,并且长度也可根据需要增长和缩短。

本文介绍的线性表以linux自带的sys/queue.h为例子,该列表来自于FreeBSD中的一个头文件。一般由glibc中携带。源码查看链接如下:

头文件queue.h为C语言中的链表提供了更加标准规范的编程接口。如今的版本多为伯克利加州大学1994年8月的8.5版本。

1.1 不同队列的功能介绍

A singly-linked list is headed by a single forward pointer. The elements are singly linked for minimum space and pointer manipulation overhead at the expense of O(n) removal for arbitrary elements. New elements can be added to the list after an existing element or at the head of the list. Elements being removed from the head of the list should use the explicit macro for this purpose for optimum efficiency. A singly-linked list may only be traversed in the forward direction. Singly-linked lists are ideal for applications with large datasets and few or no removals or for implementing a LIFO queue.

单链表(SLIST)的头是一个正向指针。元素是单链接的,以最小的空间和指针操作开销为代价,对任意元素进行O(n)移除。新元素可以添加到现有元素之后,也可以添加到列表的头部。从列表头部删除的元素应该使用显式宏来实现此目的,以获得最佳效率。单链表只能在正向上遍历。单链表适用于具有大数据集且很少或没有删除的应用程序,或实现后进先出队列。

A singly-linked tail queue is headed by a pair of pointers, one to the head of the list and the other to the tail of the list. The elements are singly linked for minimum space and pointer manipulation overhead at the expense of O(n) removal for arbitrary elements. New elements can be added to the list after an existing element, at the head of the list, or at the end of the list. Elements being removed from the head of the tail queue should use the explicit macro for this purpose for optimum efficiency. A singly-linked tail queue may only be traversed in the forward direction. Singly-linked tail queues are ideal for applications with large datasets and few or no removals or for implementing a FIFO queue.

单链尾队列(STAILQ)由一对指针引导,一个指向列表的头部,另一个指向列表的尾部。元素是单链接的,以最小的空间和指针操作开销为代价,对任意元素进行O(n)移除。可以将新元素添加到列表中,在现有元素之后,在列表的头部,或在列表的末尾。要从尾部队列的头部删除的元素应该为此目的使用显式宏,以获得最佳效率。单链尾队列只能在正向上遍历。单链接尾部队列非常适合具有大数据集和很少或没有删除或实现FIFO队列的应用程序。

一些bsd提供SIMPLEQ而不是STAILQ。它们是相同的,但由于历史原因,它们在不同的bsd上命名不同。STAILQ起源于FreeBSD,而SIMPLEQ起源于NetBSD。出于兼容性的考虑,有些系统提供两组宏。Glibc提供了STAILQ和SIMPLEQ,除了缺少一个相当于STAILQ_CONCAT()的SIMPLEQ之外,它们是相同的

A list is headed by a single forward pointer (or an array of forward pointers for a hash table header). The elements are doubly linked so that an arbitrary element can be removed without a need to traverse the list. New elements can be added to the list before or after an existing element or at the head of the list. A list may be traversed in either direction.

列表(LIST)的头由单个前向指针(或哈希表头的前向指针数组)组成。元素是双向链接的,因此可以删除任意元素而无需遍历列表。新元素可以添加到列表中,在现有元素之前或之后,也可以添加到列表的头部。列表可以在任意一个方向上遍历。

A tail queue is headed by a pair of pointers, one to the head of the list and the other to the tail of the list. The elements are doubly linked so that an arbitrary element can be removed without a need to traverse the list. New elements can be added to the list before or after an existing element, at the head of the list, or at the end of the list. A tail queue may be traversed in either direction.

尾队列(TAILQ)由一对指针引导,一个指向列表的头部,另一个指向列表的尾部。元素是双向链接的,因此可以删除任意元素而无需遍历列表。可以将新元素添加到列表中,在现有元素之前或之后,在列表的开头或结尾。尾队列可以在任何一个方向上遍历。

最后还有一个CIRCLEQ的循环队列,一般用不着,上述的四个队列已经足够使用了。

总共有五种类型的数据结构:单链表、列表、单链尾队列和尾队列。所有五个结构都支持以下功能:

  • 在列表的头部插入一个新条目。

  • 在列表中的任何元素之后插入一个新条目。

  • 从列表的头部删除一个条目。

  • 向前遍历列表。

所有双重链接类型的数据结构(列表和尾队列)还允许:

  • 在列表中的任何元素之前插入一个新条目。

  • 删除列表中的任何条目。

然而:

  • 每个元素需要两个指针而不是一个。

  • 操作的代码大小和执行时间(移除除外)大约是单链接数据结构的两倍。

  • 列表是最简单的双链数据结构,在单链表上只支持上述功能。

尾部队列增加了以下功能:

  • 可以在列表末尾添加条目。

  • 它们可以反向遍历,这是有代价的。

然而:

  • 所有的列表插入和删除都必须指定列表的头。

  • 每个头条目需要两个指针而不是一个。

  • 代码大小比单链表大15%,操作运行速度比单链表慢20%。

另一种类型的数据结构——循环队列——违反了C语言的混叠规则,导致编译错误。所有使用它们的代码都应转换为另一种结构;尾队列通常是最容易转换的。

1.2 功能对比表

下面是一些功能标记:

  • + 意味着这个宏是可用的。
  • - 意味着这个宏不可用。
  • s 意味着这个宏可用但是速率较低(o(n)复杂度)
功能支持 SLIST LIST STAILQ TAILQ
_HEAD + + + +
_CLASS_HEAD + + + +
_HEAD_INITIALIZER + + + +
_ENTRY + + + +
_CLASS_ENTRY + + + +
_INIT + + + +
_EMPTY + + + +
_END + + + +
_FIRST + + + +
_NEXT + + + +
_PREV - + - +
_LAST - - + +
_LAST_FAST - - - +
_FOREACH + + + +
_FOREACH_FROM + + + +
_FOREACH_SAFE + + + +
_FOREACH_FROM_SAFE + + + +
_FOREACH_REVERSE - - - +
_FOREACH_REVERSE_FROM - - - +
_FOREACH_REVERSE_SAFE - - - +
_FOREACH_REVERSE_FROM_SAFE - - - +
_INSERT_HEAD + + + +
_INSERT_BEFORE - + - +
_INSERT_AFTER + + + +
_INSERT_TAIL - - + +
_CONCAT s s + +
_REMOVE_AFTER + - + -
_REMOVE_HEAD + - + -
_REMOVE s + s +
_SWAP + + + +

2.具体说明

2.1 SLIST单向列表详细介绍

BSD queue.h中的SLIST是一种单向链表,它提供了一种简单而高效的数据结构,适用于许多常见的程序场景。下面是从效率、安全性和使用方式三个方面对SLIST的总结:

  1. 效率:SLIST的效率非常高,因为它是一个非常简单的数据结构,它的插入、删除和遍历操作都可以在常数时间内完成。这使得SLIST非常适合于需要高效率的程序场景,如操作系统内核、网络服务器等。
  2. 安全性:在多线程环境下,对于同一个SLIST链表的并发访问,通常需要进行同步和互斥,以确保数据的完整性和正确性。因此,在多线程环境下使用SLIST时,确实需要进行上锁保护。
  3. 使用方式:使用SLIST非常简单,只需要包含头文件<sys/queue.h>即可。SLIST提供了一系列宏,可以用来操作链表,例如SLIST_INIT、SLIST_INSERT_HEAD、SLIST_REMOVE等。这些宏可以在代码中直接使用,非常方便。此外,SLIST还提供了一些辅助宏,例如SLIST_EMPTY和SLIST_NEXT等,可以用来查询链表的状态和访问链表元素。

总的来说,BSD queue.h中的SLIST是一种非常高效、可靠、易用的数据结构,适用于许多常见的程序场景。在程序开发过程中,程序员可以使用SLIST来提高程序的效率和安全性,同时减少开发时间和代码复杂度。

单链表的头是由SLIST_HEAD()宏定义的结构。该结构包含一个指向列表第一个元素的指针。元素是单链接的,以最小的空间和指针操作开销为代价,对任意元素进行O(n)移除。新元素可以添加到现有元素之后,也可以添加到列表的头部。SLIST_HEAD结构体的声明如下:

#define SLIST_HEAD(name, type)                      \
    struct name {                                   \
        struct type *slh_first; /* first element */ \
    }

SLIST_HEAD(HEADNAME, TYPE) head;

其中HEADNAME是要定义的结构的名称,struct TYPE是要链接到列表中的元素的类型。指向列表头的指针稍后可以声明为:

struct HEADNAME *headp;

HEADNAME功能通常不使用,导致以下奇怪的代码(使用匿名结构体):

SLIST_HEAD(, TYPE) head, *headp;

下面是配套其他操作函数:

  • SLIST_ENTRY()宏声明了一个连接列表中的元素的结构。

  • SLIST_INIT()宏初始化由head引用的列表。

  • 列表也可以通过使用SLIST_HEAD_INITIALIZER()宏静态初始化,如下所示:

    SLIST_HEAD(HEADNAME, TYPE) head = SLIST_HEAD_INITIALIZER(head);
    
  • SLIST_INSERT_HEAD()宏将新元素elm插入到列表的头部。

  • SLIST_INSERT_AFTER()宏将新元素elm插入到元素listelm之后。

  • SLIST_REMOVE_HEAD()宏删除由head指向的列表的第一个元素。

  • SLIST_REMOVE_AFTER()宏删除elm之后的列表元素。

  • SLIST_REMOVE()宏删除由head指向的列表中的元素elm。

  • SLIST_FIRST()和SLIST_NEXT()宏可以用来遍历列表:

    for (np = SLIST_FIRST(&head); np != NULL; np = SLIST_NEXT(np, FIELDNAME))
    

或者,为了简单起见,可以使用SLIST_FOREACH()宏:

SLIST_FOREACH(np, head, FIELDNAME)

宏SLIST_FOREACH_SAFE()向前遍历由head引用的列表,将每个元素依次赋值给var。然而,与SLIST_FOREACH()不同的是,SLIST_FOREACH_SAFE()允许删除var并在循环中安全地释放它,而不会干扰遍历。

应该使用SLIST_EMPTY()宏来检查简单列表是否为空。

SLIST_FOREACH()不允许在循环中删除或释放var,因为它会干扰遍历。SLIST_FOREACH_SAFE()存在于bsd中,但不存在于glibc中,它通过允许var安全地从列表中删除并从循环中释放而不干扰遍历来修复此限制。

下面是具体的宏函数定义类型展示:

#include <sys/queue.h>

SLIST_ENTRY(TYPE);

SLIST_HEAD(HEADNAME, TYPE);
SLIST_HEAD SLIST_HEAD_INITIALIZER(SLIST_HEAD head);
void SLIST_INIT(SLIST_HEAD *head);

int SLIST_EMPTY(SLIST_HEAD *head);

void SLIST_INSERT_HEAD(SLIST_HEAD *head,
                       struct TYPE *elm, SLIST_ENTRY NAME);
void SLIST_INSERT_AFTER(struct TYPE *listelm,
                       struct TYPE *elm, SLIST_ENTRY NAME);

struct TYPE *SLIST_FIRST(SLIST_HEAD *head);
struct TYPE *SLIST_NEXT(struct TYPE *elm, SLIST_ENTRY NAME);

SLIST_FOREACH(struct TYPE *var, SLIST_HEAD *head, SLIST_ENTRY NAME);

void SLIST_REMOVE(SLIST_HEAD *head, struct TYPE *elm,
                       SLIST_ENTRY NAME);
void SLIST_REMOVE_HEAD(SLIST_HEAD *head,
                       SLIST_ENTRY NAME);
2.2 SLIST单向列表使用实例

BSD queue.h中的SLIST是一种单向链表的实现,用于在C语言中管理数据结构。使用SLIST需要进行以下步骤:

  1. 定义一个结构体,该结构体应该包含一个SLIST_ENTRY成员,以便将结构体连接到链表中。
  2. 使用SLIST_ENTRY宏定义一个结构体成员,以便将结构体链接到链表中。
  3. 使用SLIST_HEAD宏定义一个链表头,该链表头包含一个SLIST_ENTRY成员。
  4. 使用SLIST_INIT宏将链表头初始化为一个空链表。
  5. 使用SLIST_INSERT_HEAD、SLIST_INSERT_AFTER、SLIST_REMOVE等宏来操作链表中的元素。

SLIST与LIST的主要区别在于SLIST是单向链表,只能从头部插入和删除元素,而LIST是双向链表,可以从头部和尾部插入和删除元素。在使用BSD queue.h中的SLIST时,需要注意以下事项:

  1. 不支持双向遍历:SLIST是单向链表,只能从头部开始遍历,无法反向遍历。
  2. 不支持随机访问:SLIST只能从头部插入和删除元素,因此无法通过索引或指针直接访问链表中的元素。
  3. 不安全:SLIST没有进行越界检查,因此存在指针越界问题,需要注意安全性。
  4. 不支持动态内存分配:SLIST不支持在运行时动态地分配内存,因此需要在编译时确定链表的大小。
  5. 适用性限制:SLIST只适用于单向链表的数据结构,因此不适用于其他类型的数据结构。

总的来说,BSD queue.h中的SLIST是一种简单、高效的单向链表实现。在使用时需要注意其适用性限制和安全性问题。

下面是一个使用示例:

SLIST_HEAD(listhead, entry) head;
struct entry {
	...
	SLIST_ENTRY(entry) entries;	/* Simple list. */
	...
} *n1, *n2, *np;

SLIST_INIT(&head);			/* Initialize simple list. */

n1 = malloc(sizeof(struct entry));	/* Insert at the head. */
SLIST_INSERT_HEAD(&head, n1, entries);

n2 = malloc(sizeof(struct entry));	/* Insert after. */
SLIST_INSERT_AFTER(n1, n2, entries);

SLIST_FOREACH(np, &head, entries)	/* Forward traversal. */
	np-> ...

while (!SLIST_EMPTY(&head)) {	 	/* Delete. */
	n1 = SLIST_FIRST(&head);
	SLIST_REMOVE_HEAD(&head, entries);
	free(n1);
}
2.3 LIST双向列表详细介绍

列表的头是由LIST_HEAD()宏定义的结构。该结构包含一个指向列表第一个元素的指针。元素是双向链接的,因此可以在不遍历列表的情况下删除任意元素。可以将新元素添加到列表中,在现有元素之后,在现有元素之前,或者在列表的头部。LIST_HEAD结构体的声明如下:

LIST_HEAD(HEADNAME, TYPE) head;

其中HEADNAME是要定义的结构的名称,struct TYPE是要链接到列表中的元素的类型。指向列表头的指针稍后可以声明为:

struct HEADNAME *headp; # (名称head和headp是用户可选择的。)

HEADNAME功能通常不使用,导致以下奇怪的代码:

LIST_HEAD(, TYPE) head, *head;

具有下面的操作:

  • LIST_ENTRY()宏声明了一个连接列表中的元素的结构。

  • LIST_INIT()宏初始化由head引用的列表。

  • 列表也可以通过使用LIST_HEAD_INITIALIZER()宏静态初始化,如下所示:

    LIST_HEAD(HEADNAME, TYPE) head = LIST_HEAD_INITIALIZER(head);
    
  • LIST_INSERT_HEAD()宏将新元素elm插入到列表的头部。

  • LIST_INSERT_AFTER()宏将新元素elm插入到元素listelm之后。

  • LIST_INSERT_BEFORE()宏将新元素elm插入到元素listelm之前。

  • LIST_REMOVE()宏从列表中删除元素elm。

  • LIST_REPLACE()宏将列表元素elm替换为新元素elm2。

  • LIST_FIRST()和LIST_NEXT()宏可以用来遍历列表:

    for (np = LIST_FIRST(&head);np != NULL;np = LIST_NEXT(np, FIELDNAME))
    

或者,为了简单起见,可以使用LIST_FOREACH()宏:

LIST_FOREACH(np, head, FIELDNAME)

宏LIST_FOREACH_SAFE()向前遍历由head引用的列表,将每个元素依次赋值给var。然而,与LIST_FOREACH()不同的是,它允许删除var并将其安全地从循环中释放出来,而不会干扰遍历。

应该使用LIST_EMPTY()宏来检查列表是否为空。

双向列表的使用与单向列表没有多少区别,下面是宏函数定义原型:

#include <sys/queue.h>

LIST_ENTRY(TYPE);

LIST_HEAD(HEADNAME, TYPE);
LIST_HEAD LIST_HEAD_INITIALIZER(LIST_HEAD head);
void LIST_INIT(LIST_HEAD *head);

int LIST_EMPTY(LIST_HEAD *head);

void LIST_INSERT_HEAD(LIST_HEAD *head,
                       struct TYPE *elm, LIST_ENTRY NAME);
void LIST_INSERT_BEFORE(struct TYPE *listelm,
                       struct TYPE *elm, LIST_ENTRY NAME);
void LIST_INSERT_AFTER(struct TYPE *listelm,
                       struct TYPE *elm, LIST_ENTRY NAME);

struct TYPE *LIST_FIRST(LIST_HEAD *head);
struct TYPE *LIST_NEXT(struct TYPE *elm, LIST_ENTRY NAME);

LIST_FOREACH(struct TYPE *var, LIST_HEAD *head, LIST_ENTRY NAME);

void LIST_REMOVE(struct TYPE *elm, LIST_ENTRY NAME);

下面是使用实例:

LIST_HEAD(listhead, entry) head;
struct entry {
	...
	LIST_ENTRY(entry) entries;	/* List. */
	...
} *n1, *n2, *np;

LIST_INIT(&head);			/* Initialize list. */

n1 = malloc(sizeof(struct entry));	/* Insert at the head. */
LIST_INSERT_HEAD(&head, n1, entries);

n2 = malloc(sizeof(struct entry));	/* Insert after. */
LIST_INSERT_AFTER(n1, n2, entries);

n2 = malloc(sizeof(struct entry));	/* Insert before. */
LIST_INSERT_BEFORE(n1, n2, entries);
					/* Forward traversal. */
LIST_FOREACH(np, &head, entries)
	np-> ...

while (!LIST_EMPTY(&head)) {		/* Delete. */
	n1 = LIST_FIRST(&head);
	LIST_REMOVE(n1, entries);
	free(n1);
}
2.4 STAILQ单向尾队列详细介绍

单链接的尾部队列由STAILQ_HEAD()宏定义的结构作为头部。该结构包含一对指针,一个指向尾队列中的第一个元素,另一个指向尾队列中的最后一个元素。元素是单链接的,以最小的空间和指针操作开销为代价,对任意元素进行O(n)移除。新元素可以添加到尾队列,在现有元素之后,在尾队列的头部或尾队列的末端。STAILQ_HEAD结构声明如下:

STAILQ_HEAD(HEADNAME, TYPE) head;

其中HEADNAME是要定义的结构的名称,struct TYPE是要链接到尾队列的元素的类型。指向尾部队列头部的指针稍后可以声明为:

struct HEADNAME *headp;		# (名称head和headp是用户可选择的。)

下面是一些常见的操作:

  • STAILQ_ENTRY()宏声明了一个连接尾队列中的元素的结构。

  • STAILQ_INIT()宏初始化由head引用的尾部队列。

  • 尾部队列也可以通过使用STAILQ_HEAD_INITIALIZER()宏静态初始化。

  • STAILQ_INSERT_AFTER()宏将新元素elm插入到元素listelm之后。

  • STAILQ_INSERT_HEAD()宏将新元素elm插入到尾部队列的头部。

  • STAILQ_INSERT_TAIL()宏将新元素elm插入到尾部队列的末尾。

  • STAILQ_REMOVE_AFTER()宏删除紧跟在elm后面的队列元素。与STAILQ_REMOVE不同,这个宏不会遍历整个尾部队列。

  • STAILQ_REMOVE_HEAD()宏从尾部队列中删除第一个元素。为了获得最佳效率,从尾部队列头部删除的元素应该显式地使用这个宏,而不是通用的STAILQ_REMOVE宏。

  • STAILQ_REMOVE()宏从尾部队列中删除元素elm。应该避免使用这个宏,因为它遍历整个列表。如果在高使用率的代码路径中需要这个宏,或者在长尾队列上操作,那么应该使用双链接的尾队列。

  • STAILQ_CONCAT()宏将由head2引用的尾部队列的所有元素连接到由head1引用的尾部队列的末尾,在进程中清空head2。这比删除和插入单个元素更有效,因为它实际上不遍历head2。

    STAILQ_FOREACH()宏用于队列遍历:

STAILQ_FOREACH()宏用于队列遍历,宏STAILQ_FOREACH_SAFE()向前遍历由head引用的队列,将每个元素依次赋值给var。然而,与STAILQ_FOREACH()不同的是,它允许删除var并在循环中安全地释放它,而不会干扰遍历。

可以使用STAILQ_FIRST()、STAILQ_NEXT()和STAILQ_LAST()宏手动遍历尾队列或尾队列的任意部分。应该使用STAILQ_EMPTY()宏来检查尾队列是否为空。

下面是这些函数的宏函数原型定义:

#include <sys/queue.h>

STAILQ_ENTRY(TYPE);

STAILQ_HEAD(HEADNAME, TYPE);
STAILQ_HEAD STAILQ_HEAD_INITIALIZER(STAILQ_HEAD head);
void STAILQ_INIT(STAILQ_HEAD *head);

int STAILQ_EMPTY(STAILQ_HEAD *head);

void STAILQ_INSERT_HEAD(STAILQ_HEAD *head,
                        struct TYPE *elm, STAILQ_ENTRY NAME);
void STAILQ_INSERT_TAIL(STAILQ_HEAD *head,
                        struct TYPE *elm, STAILQ_ENTRY NAME);
void STAILQ_INSERT_AFTER(STAILQ_HEAD *head, struct TYPE *listelm,
                        struct TYPE *elm, STAILQ_ENTRY NAME);

struct TYPE *STAILQ_FIRST(STAILQ_HEAD *head);
struct TYPE *STAILQ_NEXT(struct TYPE *elm, STAILQ_ENTRY NAME);

STAILQ_FOREACH(struct TYPE *var, STAILQ_HEAD *head, STAILQ_ENTRY NAME);

void STAILQ_REMOVE(STAILQ_HEAD *head, struct TYPE *elm, TYPE,
                        STAILQ_ENTRY NAME);
void STAILQ_REMOVE_HEAD(STAILQ_HEAD *head,
                        STAILQ_ENTRY NAME);

void STAILQ_CONCAT(STAILQ_HEAD *head1, STAILQ_HEAD *head2);

下面是使用示例:

STAILQ_HEAD(listhead, entry) head = STAILQ_HEAD_INITIALIZER(head);
struct entry {
	...
	STAILQ_ENTRY(entry) entries;	/* Singly-linked tail queue. */
	...
} *n1, *n2, *np;

n1 = malloc(sizeof(struct entry));	/* Insert at the head. */
STAILQ_INSERT_HEAD(&head, n1, entries);

n2 = malloc(sizeof(struct entry));	/* Insert at the tail. */
STAILQ_INSERT_TAIL(&head, n2, entries);

n2 = malloc(sizeof(struct entry));	/* Insert after. */
STAILQ_INSERT_AFTER(&head, n1, n2, entries);

					/* Deletion. */
STAILQ_REMOVE(&head, n2, entry, entries);
free(n2);
					/* Deletion from the head. */
n3 = STAILQ_FIRST(&head);
STAILQ_REMOVE_HEAD(&head, entries);
free(n3);
					/* Forward traversal. */
STAILQ_FOREACH(np, &head, entries)
	np-> ...
					/* Safe forward traversal. */
STAILQ_FOREACH_SAFE(np, &head, entries, np_temp) {
	np-> ...
	STAILQ_REMOVE(&head, np, entry, entries);
	free(np);
}
					/* Delete. */
while (!STAILQ_EMPTY(&head)) {
	n1 = STAILQ_FIRST(&head);
	STAILQ_REMOVE_HEAD(&head, entries);
	free(n1);
}
2.5 TAILQ双向尾队列详细介绍

尾队列的头部由TAILQ_HEAD()宏定义的结构组成。该结构包含一对指针,一个指向尾队列中的第一个元素,另一个指向尾队列中的最后一个元素。元素是双重链接的,因此可以在不遍历尾队列的情况下删除任意元素。新元素可以添加到队列中,在现有元素之后,在现有元素之前,在队列的头部,或在队列的末尾。一个TAILQ_HEAD结构体声明如下:

TAILQ_HEAD(HEADNAME, TYPE) head;

其中HEADNAME是要定义的结构的名称,struct TYPE是要链接到尾队列的元素的类型。指向尾部队列头部的指针稍后可以声明为:

struct HEADNAME *headp; // (名称head和headp是用户可选择的。)

下面是一些操作:

  • TAILQ_ENTRY()宏声明了一个连接尾队列中的元素的结构。

  • TAILQ_INIT()宏初始化由head引用的尾部队列。

  • 尾队列也可以通过使用TAILQ_HEAD_INITIALIZER()宏进行静态初始化。

  • TAILQ_INSERT_HEAD()宏将新元素elm插入到尾队列的头部。

  • TAILQ_INSERT_TAIL()宏将新元素elm插入到尾队列的末尾。

  • TAILQ_INSERT_AFTER()宏将新元素elm插入到元素listelm之后。

  • TAILQ_INSERT_BEFORE()宏将新元素elm插入到元素listelm之前。

  • TAILQ_REMOVE()宏从尾部队列中删除元素elm。

  • TAILQ_REPLACE()宏将列表元素elm替换为新元素elm2。

  • TAILQ_CONCAT()宏将由head2引用的尾部队列的所有元素连接到由head1引用的尾部队列的末尾,在进程中清空head2。这比删除和插入单个元素更有效,因为它实际上不遍历head2。

  • TAILQ_FOREACH()和TAILQ_FOREACH_REVERSE()用于遍历尾部队列。TAILQ_FOREACH()从第一个元素开始,一直到最后一个元素。TAILQ_FOREACH_REVERSE()从最后一个元素开始,向第一个元素前进。

宏TAILQ_FOREACH_SAFE()和TAILQ_FOREACH_REVERSE_SAFE()分别沿正向或反向遍历由head引用的列表,依次将每个元素赋值给var。然而,与它们不安全的对应项不同,它们既允许删除var,也允许在循环中安全地释放它,而不会干扰遍历。

可以使用TAILQ_FIRST()、TAILQ_NEXT()、TAILQ_LAST()和TAILQ_PREV()宏手动遍历尾队列或尾队列的任意部分。

应该使用TAILQ_EMPTY()宏来检查尾队列是否为空

下面是函数原型:

#include <sys/queue.h>

TAILQ_ENTRY(TYPE);

TAILQ_HEAD(HEADNAME, TYPE);
TAILQ_HEAD TAILQ_HEAD_INITIALIZER(TAILQ_HEAD head);
void TAILQ_INIT(TAILQ_HEAD *head);

int TAILQ_EMPTY(TAILQ_HEAD *head);

void TAILQ_INSERT_HEAD(TAILQ_HEAD *head,
                        struct TYPE *elm, TAILQ_ENTRY NAME);
void TAILQ_INSERT_TAIL(TAILQ_HEAD *head,
                        struct TYPE *elm, TAILQ_ENTRY NAME);
void TAILQ_INSERT_BEFORE(struct TYPE *listelm,
                        struct TYPE *elm, TAILQ_ENTRY NAME);
void TAILQ_INSERT_AFTER(TAILQ_HEAD *head, struct TYPE *listelm,
                        struct TYPE *elm, TAILQ_ENTRY NAME);

struct TYPE *TAILQ_FIRST(TAILQ_HEAD *head);
struct TYPE *TAILQ_LAST(TAILQ_HEAD *head, HEADNAME);
struct TYPE *TAILQ_PREV(struct TYPE *elm, HEADNAME, TAILQ_ENTRY NAME);
struct TYPE *TAILQ_NEXT(struct TYPE *elm, TAILQ_ENTRY NAME);

TAILQ_FOREACH(struct TYPE *var, TAILQ_HEAD *head,
                        TAILQ_ENTRY NAME);
TAILQ_FOREACH_REVERSE(struct TYPE *var, TAILQ_HEAD *head, HEADNAME,
                        TAILQ_ENTRY NAME);

void TAILQ_REMOVE(TAILQ_HEAD *head, struct TYPE *elm,
                        TAILQ_ENTRY NAME);

void TAILQ_CONCAT(TAILQ_HEAD *head1, TAILQ_HEAD *head2,
                        TAILQ_ENTRY NAME);

下面是实际示例:

TAILQ_HEAD(tailhead, entry) head;
struct entry {
	...
	TAILQ_ENTRY(entry) entries;	/* Tail queue. */
	...
} *n1, *n2, *np;

TAILQ_INIT(&head);			/* Initialize queue. */

n1 = malloc(sizeof(struct entry));	/* Insert at the head. */
TAILQ_INSERT_HEAD(&head, n1, entries);

n1 = malloc(sizeof(struct entry));	/* Insert at the tail. */
TAILQ_INSERT_TAIL(&head, n1, entries);

n2 = malloc(sizeof(struct entry));	/* Insert after. */
TAILQ_INSERT_AFTER(&head, n1, n2, entries);

n2 = malloc(sizeof(struct entry));	/* Insert before. */
TAILQ_INSERT_BEFORE(n1, n2, entries);
					/* Forward traversal. */
TAILQ_FOREACH(np, &head, entries)
	np-> ...
					/* Manual forward traversal. */
for (np = n2; np != NULL; np = TAILQ_NEXT(np, entries))
	np-> ...
					/* Delete. */
while ((np = TAILQ_FIRST(&head))) {
	TAILQ_REMOVE(&head, np, entries);
	free(np);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构之线性表(bsd, sys/queue.h) 的相关文章

  • E (1052) : DS树--带权路径和

    文章目录 一 题目描述 二 输入与输出 1 输入 2 输出 三 参考代码 一 题目描述 计算一棵二叉树的带权路径总和 即求赫夫曼树的带权路径和 已知一棵二叉树的叶子权值 该二叉树的带权路径和APL等于叶子权值乘以根节点到叶子的分支数 然后求
  • 数组实现循环队列(增设队列大小size)

    目录 一 前言 1 如何实现循环 2 如何判断队列为空 3 如何判断队列为满 二 循环队列的结构定义 三 循环队列的创建及其初始化 四 入队 五 出队 六 取队头元素 七 取队尾元素 八 循环队列判空 九 循环队列判满 十 循环队列销毁 一
  • 不会做的题汇总

    摘苹果 题目描述 小红来到苹果园 帮园长摘苹果 园长请小红把摘完的苹果的最小的那个去掉 如果有 多个最小的苹果 那么都要去掉 剩余的苹果算一下平均一个苹果有多重 平均重 量请保留1位小数 输入 输入有2行 第一行 一个整数n代表小红摘的n个
  • 八大排序(插入排序 | 选择排序 | 冒泡排序)

    在我们内存中我们一般会有一些没有顺序的数据 我们成为内排序 而今天分享八大排序的是时间复杂度为O N 2 的插入排序 选择排序和教学意义比较强的冒泡排序 插入排序 这是插入排序的动图 通过动图我们也是可以看到我们的插入排序的思想 从当前的位
  • 【数据结构】双链表的定义和操作

    目录 1 双链表的定义 2 双链表的创建和初始化 3 双链表的插入节点操作 4 双链表的删除节点操作 5 双链表的查找节点操作 6 双链表的更新节点操作 7 完整代码 嗨 我是 Filotimo 很高兴与大家相识 希望我的博客能对你有所帮助
  • 回溯算法第零篇【递归、搜索与回溯】【回溯算法入门必看】

    本篇文章的目的 1 给小伙伴们对回溯算法中的名词进行解释 2 消除递归的恐惧 回溯是递归的一个分支 给小伙伴们一个建议 整篇文章都要看完 一字不漏 全是干货 注意 分析回溯的思想之前 我们得知道一个关系 递归包含搜索 搜索包含回溯 所以我们
  • 代码随想录算法训练营第42天| ● 01背包问题,你该了解这些! ● 01背包问题,你该了解这些! 滚动数组 ● 416. 分割等和子集

    416 分割等和子集 已解答 中等 相关标签 相关企业 给你一个 只包含正整数 的 非空 数组 nums 请你判断是否可以将这个数组分割成两个子集 使得两个子集的元素和相等 示例 1 输入 nums 1 5 11 5 输出 true 解释
  • 回溯算法第零篇【递归、搜索与回溯】【回溯算法入门必看】

    本篇文章的目的 1 给小伙伴们对回溯算法中的名词进行解释 2 消除递归的恐惧 回溯是递归的一个分支 给小伙伴们一个建议 整篇文章都要看完 一字不漏 全是干货 注意 分析回溯的思想之前 我们得知道一个关系 递归包含搜索 搜索包含回溯 所以我们
  • 面试150-13(Leetcode238除自身以外数组的乘积)

    代码 class Solution public int productExceptSelf int nums int n nums length int res new int n int product 1 int zerocnt 0
  • 代码随想录算法训练营第42天| ● 01背包问题,你该了解这些! ● 01背包问题,你该了解这些! 滚动数组 ● 416. 分割等和子集

    416 分割等和子集 已解答 中等 相关标签 相关企业 给你一个 只包含正整数 的 非空 数组 nums 请你判断是否可以将这个数组分割成两个子集 使得两个子集的元素和相等 示例 1 输入 nums 1 5 11 5 输出 true 解释
  • 数据结构学习笔记(七)搜索结构

    文章目录 1 前言 2 概念 3 静态搜索结构 3 1 静态搜索表 3 2 顺序搜索表 3 2 1 基于有序顺序表和顺序搜索和折半搜索 4 二叉搜索树
  • 《LeetCode力扣练习》代码随想录——双指针法(反转链表---Java)

    LeetCode力扣练习 代码随想录 双指针法 反转链表 Java 刷题思路来源于 代码随想录 206 反转链表 双指针 Definition for singly linked list public class ListNode int
  • 冒泡排序/选择排序/插入排序/快速排序/归并排序/桶排序/堆排序/希尔排序/计数排序/基数排序/二分查找/广度优先搜索/深度优先搜索

    排序算法 冒泡排序 Bubble Sort 通过重复地比较相邻的元素并交换它们 使得最大 或最小 的元素逐渐移动到列表的一端 从而实现排序 选择排序 Selection Sort 在未排序的部分中 选择最小 或最大 的元素 并将其放置在已排
  • 【数据结构和算法】小行星碰撞

    其他系列文章导航 Java基础合集 数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一 题目描述 二 题解 2 1 什么情况会用到栈 2 2 方法一 模拟 栈 三 代码 3 1
  • 排序:计数排序

    一 概念 计数排序是非比较排序 是对哈希直接定址法的变形应用 二 思想 利用数组统计相同数据出现的次数 例如整型数据m出现n次 就在数组m位置记录数据为n 最后从头遍历数组打印数据即可 通俗来讲就是 数组下标即为数据 下标所指位置的值即为数
  • 206.翻转链表

    翻转链表 力扣 LeetCode 官网 全球极客挚爱的技术成长平台 备战技术面试 力扣提供海量技术面试资源 帮助你高效提升编程技能 轻松拿下世界 IT 名企 Dream Offer https leetcode cn problems re
  • 链表的中间节点

    链表的中间节点 力扣 LeetCode 官网 全球极客挚爱的技术成长平台 备战技术面试 力扣提供海量技术面试资源 帮助你高效提升编程技能 轻松拿下世界 IT 名企 Dream Offer https leetcode cn problems
  • 矩阵基本操作3

    题目描述 问题描述 定义一个N M N M lt 100 的矩阵 将一个该矩阵的行和列的元素互换 存到另一个二维数组中 输入格式 一行两个整数 N M 中间用空格隔开 表示矩阵有N行 M列 接下来共N行M列表示矩阵 输出格式 输出转置以后的
  • 数据结构——排序

    前言 哈喽小伙伴们好久不见 也是顺利的考完试迎来了寒假 众所周知 不怕同学是学霸 就怕学霸放寒假 假期身为弯道超车的最佳时间 我们定然是不能懒散的度过 今天我们就一起来学习数据结构初阶的终章 七大排序 本文所有的排序演示都为升序排序 目录
  • 最大流-Dinic算法,原理详解,四大优化,详细代码

    文章目录 零 前言 一 概念回顾 可略过 1 1流网络 1 2流 1 3最大流 1 4残留网络 1 5增广路

随机推荐

  • 日前日内多阶段多时间尺度源荷储协调调度(matlab代码)

    多阶段多时间尺度的源荷储协调调度的优势是考虑新能源出力的波动性与随机性 减少需求响应负荷的不确定性会对电网制定的日前调度计划准确性的影响 也就是能够更加精准的进行调度和分析 优化结果的可用性更强 在这方面论文里面 金力的 考虑特性分布的储能
  • 如何利用excel中的数据源制作数据地图

    关于这个问题 制作数据地图的方法已不新奇 总体来说有这么几类方案 一类方案 直接在excel里制作 优势 个人小数据量应用较为方便简单 缺点 需要熟悉VBA 且更强大的功能对VBA水平要求较高 1 绘制地图图形 VBA宏语言 思路 用插入图
  • MOS管相关知识

    MOS管 MOS管的英文全称叫MOSFET Metal Oxide Semiconductor Field Effect Transistor 即金属氧化物半导体型场效应管 属于场效应晶体管中的绝缘栅型 MOS管是场效应管的一种 在一般电子
  • 版本号自动生成方法

    只需把 AssemblyInfo cs文件中的 assembly AssemblyVersion 1 0 0 0 改成 assembly AssemblyVersion 1 0 另外还需要把 assembly AssemblyFileVer
  • 什么是负载均衡,看完文章秒懂

    一 负载均衡简介 1 1 大型网站面临的挑战 大型网站都要面对庞大的用户量 高并发 海量数据等挑战 为了提升系统整体的性能 可以采用垂直扩展和水平扩展两种方式 垂直扩展 在网站发展早期 可以从单机的角度通过增加硬件处理能力 比如 CPU 处
  • 运行时报错“version `GLIBCXX_3.4.29‘ not found”底层原理分析

    文章目录 1 报错的现象 2 为什么程序有的报找不到某个版本的动态库 有的报找不到动态库文件 2 1 找不到动态库 2 2 找不到某个版本的动态库 2 2 1 报错的原因 2 2 2 动态库的版本是如何指定的 程序又是如何记录依赖的动态库版
  • DecimalField的使用

    DecimalField 类 DecimalField max digits 无 decimal places 无 选项 固定精度的十进制数 在Python中表示一个 十进制的实例 有两个必需的参数 DecimalField max dig
  • 浏览器插件下载“Download failed. Please check your connection.”解决方法

    第一步 查看错误原因 Download failed Please check your connection 下载失败 请检查您的连接 第二步 根据问题根源查看connects相关设置 第三步 分析原因 我是开启了Manual proxy
  • NeRF必读:Instant-NGP----RTX3090单卡就能玩转NeRF

    前言 NeRF从2020年发展至今 仅仅三年时间 而Follow的工作已呈井喷之势 相信在不久的将来 NeRF会一举重塑三维重建这个业界 甚至重建我们的四维世界 开头先吹一波 NeRF的发展时间虽短 有几篇工作却在我研究的领域开始呈现万精油
  • C语言---双向链表(详解)---数据结构

    双向链表所需要头文件 首先重定义类型名 意义我前几篇讲过几次了 这里就不在赘述了 顺序表 单链表的开头都有说明 然后我们需要一个结构体 结构体包含 存储数据的 a 指向一个节点的指针 next 指向上一个节点的指针 prve 双向链表实现的
  • pgadmin4更改数据类型和主键

    在 pgAdmin 4 中更改数据类型和主键需要以下步骤 打开 pgAdmin 4 连接到您想要修改的数据库 找到并打开您想要修改的表 单击该表后单击 设计 按钮 找到要修改的列 单击该列后单击 编辑 删除 按钮 在弹出的窗口中 更改 数据
  • hai子兄弟表示法(C语言实现)——树的存储结构

    孩子兄弟表示法实际就是创建一棵二叉树 include
  • 统计中位数为 K 的子数组

    给你一个长度为n的数组nums 该数组由从1 到n的不同整数组成 另给你一个正整数k 统计并返回nums中的 中位数等于k的非空子数组的数目 注意 数组的中位数是按递增顺序排列后位于中间的那个元素 如果数组长度为偶数 则中位数是位于中间靠左
  • 音频系统POP音的原理和解决方法

    音频系统POP音的原理和解决方法 目录 文章目录 音频系统POP音的原理和解决方法 目录 音频IC与功放IC的电源时序与功能模块使能时序 功放IC输入端INP与INN的阻抗匹配 增大VBIAS滤波电容 BTL输出和SE输出 减小输出端耦合电
  • JWT令牌验证

    目录 一 JWT介绍 二 安装依赖 三 登陆接口 1 令牌工具类 2 接口代码 四 说明 一 JWT介绍 JWT全称 JSON Web Token 官网 JSON Web Tokens jwt io 定义了一种简洁的 自包含的格式 用于在通
  • Cortex-M3/M4内核STM32的LR寄存器和PC寄存器

    文章目录 怎么控制STM32跳转到指定程序 STM32的LR寄存器和PC寄存器 结语 怎么控制STM32跳转到指定程序 首先 使用标号加goto语句可以使程序强制跳转 而goto的原理实际上是汇编语言里面的强制跳转 我们看STM32的启动文
  • 顺序表企业级应用

    高并发 WEB 服务器中顺序表的应用 高性能的 web 服务器 Squid 每秒可处理上万并发的请求 从网络连接到服务器的客 户端与服务器端在交互时会保持一种会话 和电话通话的场景类似 服务器端为了管 理好所有的客户端连接 给每个连接都编了
  • PARL 强化学习框架学习

    最近参加了百度的的PARL深度强化学习课程 算是对强化学习有了一定了解 因为之前并没有学习过强化学习相关的知识 粗略入门 体验了PARL框架 确实对新手比较友好 入门学习了比较基础的算法 如SARSA Q Learning DQN PG D
  • Matlab 2020b 64bit

    Matlab 2020b 64bit 链接 https pan baidu com s 1PfAaWPGEzyXBBvYWe48Fng pwd kigc 提取码 kigc 来自百度网盘超级会员V7的分享
  • 数据结构之线性表(bsd, sys/queue.h)

    数据结构之线性表 Author Once Day Date 2023年5月27日 参考文档 Linux内嵌链表 sys queue h 详解 tissar的博客 CSDN博客 嵌入式大杂烩周记第 3 期 sys queue h 知乎 zhi