几种常用的队列函数:
TAILQ_HEAD() //定义队列头
TAILQ_ENTRY () //队列实体定义
TAILQ_INIT() // 初始化队列
TAILQ_FOREACH() // 对队列进行遍历操作
TAILQ_INSERT_BEFORE() // 在指定元素之前插入元素
TAILQ_INSERT_TAIL() // 在队列尾部插入元素
TAILQ_EMPTY() // 检查队列是否为空
TAILQ_REMOVE() //从队列中移除元素
(1)为了实现一个链表队列,首先我们得定义一个结构体作为链表队列结点,结构体定义如下:
1 typedef struct Node {
2 /*数据类型*/
3 int id;
4
5 /*link:链接器*/
6 TAILQ_ENTRY(Node) link;
7 } Node;
上面结构体定义中我们使用了 TAILQ_ENTRY 来定义结点间的链接,TAILQ_ENTRY宏定义如下:
1 #define TAILQ_ENTRY(type)
2 struct {
3 type *tqe_next; /* next element */
4 type **tqe_prev; /* address of previous next element */
}
将TAILQ_ENTRY宏定义在我们定义的Node结构体中展开如下:
1 typedef struct Node {
2 /*some attr*/
3 int id;
4
5 /*link*/
6 struct {
7 Node *tqe_next;
8 Node **tqe_prev;
9 } link;
10 } Node;
在link属性中包含了两个元素,tqe_next和tqe_prev,其中tqe_next指向下一个Node结点,,tqe_pre是指向前一个结点的link.tqe_next属性,所以它才会是一个二级指针的形式。直接上图,假设我们已经使用上述结构体创建了一个队列,该队列的指针指向如下图所示:
(2)创建好链表队列结点结构体后,我们需要创建一个链表队列头方便我们对队列进行操作,创建代码如下:
1 TAILQ_HEAD(head, Node) head;
创建链表头使用的宏定义如下:
1 #define TAILQ_HEAD(name, type) \
2 struct name { \
3 type * tqh_first; /* first element */ \
4 type ** tqh_last; /* addr of last next element */ \
5 }
宏定义展开后代码如下:
1 struct head {
2 Node * tqh_first;
3 Node ** tqh_last;
4 } head;
该头也有两属性,第一个tqh_first指向队列的第一个元素,第二个属性也是一个二级指针,虽然命名为last,但是不是指向链表队列最后一个元素,而是指向链表队列最后一个元素的link.next属性,如下图所示:
(3)准备好队列头的结点结构体后,可以开始初始化一个链表队列的,初始化链表队列代码如下:
1 TAILQ_INIT(head);
队列初始化宏定义如下:
#define TAILQ_INIT(head) do { \
(head)->tqh_first = NULL; \
(head)->tqh_last = &(head)->tqh_first; \
} while (/*CONSTCOND*/0)
以上代码比较简单,就是对队列头的两个属性赋初始值
(4)在链表队列中插入元素,既可以在链表队列的头部插入元素,也可以在链表队列的尾部插入元素,还可以在链表队列的中间插入元素(变异的队列。。。),接下来对插入元素的几种方式分别介绍。
(1)在链表队列尾部插入元素代码如下,动态分配一个Node结点node01,并将其插入到队列尾部
Node * node01 = (Node *)malloc(sizeof(Node));
TAILQ_INSERT_TAIL(head, node01, link);
所使用的TAILQ_INSERT_TAIL定义如下:
```c
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
(elm)->field.tqe_next = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &(elm)->field.tqe_next; \
} while (/*CONSTCOND*/0)
将在链表队列尾部插入元素的代码展开如下:
```c
do {
(node01)->link.tqe_next = ((void *)0);
(node01)->link.tqe_prev = (head)->tqh_last;
*(head)->tqh_last = (node01);
(head)->tqh_last = &(node01)->link.tqe_next;
} while ( 0)
插入元素宏定义展开后总共4行代码,下图图示了这4行代码实现流程:
假设队列中已经有3个结点,我们需要在尾部插入第4个结点
第1行代码将第四个结点的tqe_next设置为(void *)0,可以直接理解为置为NULL。
第2行代码中将tqe_pre指向前一个结点的tqe_next属性,因为head中的tqh_last指向了链表最后一个元素的tqe_next属性,所以这一步只需要将head->tqh_last的值直接赋给tqe_pre即可。
第3行代码实现将原队列最后一个元素tqe_next指向新增的node结点。
第4行代码就是将tqh_last指向新增结点的tqe_next属性。
通过上述4行代码我们便完成了在队列尾部新增结点的操作。通过上述操作我们可以发现,在链表尾部新增结点只需要4步,并不会因为链表节点数变多而导致时间复杂度增大,这就是head节点的意义,通过一个二级指针tql_last指向最后一个元素的tqe_next, 使得在队列尾部插入元素的时间复杂度为O(1)。
遍历节点
遍历节点
使用QLIST_FOREACH或者QLIST_FOREACH_SAFE,QLIST_FOREACH_SAFE是为了防止遍历过程中删除了节点,从而导致le_next被释放掉,中断了遍历。