链表之CIRCLEQ

头文件

头文件来自我最近看的项目:apache-mynewt-core-1.9.0\sys\sys\include\sys\queue.h

仅截取部分。

/*
 * Circular queue declarations.
 */
#define	CIRCLEQ_HEAD(name, type)					\
struct name {								\
	struct type *cqh_first;		/* first element */		\
	struct type *cqh_last;		/* last element */		\
}

#define	CIRCLEQ_HEAD_INITIALIZER(head)					\
	{ (void *)&(head), (void *)&(head) }

#define	CIRCLEQ_ENTRY(type)						\
struct {								\
	struct type *cqe_next;		/* next element */		\
	struct type *cqe_prev;		/* previous element */		\
}

/*
 * Circular queue functions.
 */
#define	CIRCLEQ_EMPTY(head)	((head)->cqh_first == (void *)(head))

#define	CIRCLEQ_FIRST(head)	((head)->cqh_first)

#define	CIRCLEQ_FOREACH(var, head, field)				\
	for ((var) = CIRCLEQ_FIRST((head));				\
	    (var) != (void *)(head) || ((var) = NULL);			\
	    (var) = CIRCLEQ_NEXT((var), field))

#define	CIRCLEQ_FOREACH_REVERSE(var, head, field)			\
	for ((var) = CIRCLEQ_LAST((head));				\
	    (var) != (void *)(head) || ((var) = NULL);			\
	    (var) = CIRCLEQ_PREV((var), field))

#define	CIRCLEQ_INIT(head) do {						\
	CIRCLEQ_FIRST((head)) = (void *)(head);				\
	CIRCLEQ_LAST((head)) = (void *)(head);				\
} while (0)

#define	CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do {		\
	CIRCLEQ_NEXT((elm), field) = CIRCLEQ_NEXT((listelm), field);	\
	CIRCLEQ_PREV((elm), field) = (listelm);				\
	if (CIRCLEQ_NEXT((listelm), field) == (void *)(head))		\
		CIRCLEQ_LAST((head)) = (elm);				\
	else								\
		CIRCLEQ_PREV(CIRCLEQ_NEXT((listelm), field), field) = (elm);\
	CIRCLEQ_NEXT((listelm), field) = (elm);				\
} while (0)

#define	CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do {		\
	CIRCLEQ_NEXT((elm), field) = (listelm);				\
	CIRCLEQ_PREV((elm), field) = CIRCLEQ_PREV((listelm), field);	\
	if (CIRCLEQ_PREV((listelm), field) == (void *)(head))		\
		CIRCLEQ_FIRST((head)) = (elm);				\
	else								\
		CIRCLEQ_NEXT(CIRCLEQ_PREV((listelm), field), field) = (elm);\
	CIRCLEQ_PREV((listelm), field) = (elm);				\
} while (0)

#define	CIRCLEQ_INSERT_HEAD(head, elm, field) do {			\
	CIRCLEQ_NEXT((elm), field) = CIRCLEQ_FIRST((head));		\
	CIRCLEQ_PREV((elm), field) = (void *)(head);			\
	if (CIRCLEQ_LAST((head)) == (void *)(head))			\
		CIRCLEQ_LAST((head)) = (elm);				\
	else								\
		CIRCLEQ_PREV(CIRCLEQ_FIRST((head)), field) = (elm);	\
	CIRCLEQ_FIRST((head)) = (elm);					\
} while (0)

#define	CIRCLEQ_INSERT_TAIL(head, elm, field) do {			\
	CIRCLEQ_NEXT((elm), field) = (void *)(head);			\
	CIRCLEQ_PREV((elm), field) = CIRCLEQ_LAST((head));		\
	if (CIRCLEQ_FIRST((head)) == (void *)(head))			\
		CIRCLEQ_FIRST((head)) = (elm);				\
	else								\
		CIRCLEQ_NEXT(CIRCLEQ_LAST((head)), field) = (elm);	\
	CIRCLEQ_LAST((head)) = (elm);					\
} while (0)

#define	CIRCLEQ_LAST(head)	((head)->cqh_last)

#define	CIRCLEQ_NEXT(elm,field)	((elm)->field.cqe_next)

#define	CIRCLEQ_PREV(elm,field)	((elm)->field.cqe_prev)

#define	CIRCLEQ_REMOVE(head, elm, field) do {				\
	if (CIRCLEQ_NEXT((elm), field) == (void *)(head))		\
		CIRCLEQ_LAST((head)) = CIRCLEQ_PREV((elm), field);	\
	else								\
		CIRCLEQ_PREV(CIRCLEQ_NEXT((elm), field), field) =	\
		    CIRCLEQ_PREV((elm), field);				\
	if (CIRCLEQ_PREV((elm), field) == (void *)(head))		\
		CIRCLEQ_FIRST((head)) = CIRCLEQ_NEXT((elm), field);	\
	else								\
		CIRCLEQ_NEXT(CIRCLEQ_PREV((elm), field), field) =	\
		    CIRCLEQ_NEXT((elm), field);				\
} while (0)

例子

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "bsd_queue.h"  // 就是我上面贴的代码

struct entry {
    int data;
    CIRCLEQ_ENTRY(entry) entries;           /* Queue */
};

CIRCLEQ_HEAD(circlehead, entry);


int
main(void)
{
    struct entry *n1, *n2, *n3, *np;
    struct circlehead head;                 /* Queue head */
    int i;

    CIRCLEQ_INIT(&head);                    /* Initialize the queue */

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

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

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

    n3 = malloc(sizeof(struct entry));      /* Insert before */
    CIRCLEQ_INSERT_BEFORE(&head, n2, n3, entries);

    CIRCLEQ_REMOVE(&head, n2, entries);     /* Deletion */
    free(n2);
                                            /* Forward traversal */
    i = 0;
    CIRCLEQ_FOREACH(np, &head, entries)
        np->data = i++;
                                            /* Reverse traversal */
    CIRCLEQ_FOREACH_REVERSE(np, &head, entries)
        printf("%i\n", np->data);
                                            /* Queue deletion */
    n1 = CIRCLEQ_FIRST(&head);
    while (n1 != (void *)&head) {
        n2 = CIRCLEQ_NEXT(n1, entries);
        free(n1);
        n1 = n2;
    }
    CIRCLEQ_INIT(&head);

    exit(EXIT_SUCCESS);
}

代码分析

我们一点一点看上面的例子。

先来一张我自己绘制的示意图。红色表示我觉得不太“正常”的地方,指针的类型不匹配,需要强制转换。请添加图片描述

CIRCLEQ_ENTRY 和 CIRCLEQ_HEAD

struct entry {
    int data;
    CIRCLEQ_ENTRY(entry) entries;           /* Queue */
};

CIRCLEQ_HEAD(circlehead, entry);

宏展开是:

struct entry {
    int data;
    struct { 
        struct entry *cqe_next; 
        struct entry *cqe_prev; 
    } entries;
};
struct circlehead { 
    struct entry *cqh_first; 
    struct entry *cqh_last; 
};

和其他链表不同,这里没有用二级指针,cqe_prev 和 cqh_last 都是 struct entry * 类型。(为什么呢)

我前面写的文章都是研究宏展开后的代码,这次我们换一个思路,直接看宏的定义

CIRCLEQ_HEAD_INITIALIZER

#define	CIRCLEQ_HEAD_INITIALIZER(head)					\
	{ (void *)&(head), (void *)&(head) }

初始化一个链表后,表头的两个指针都指向自己。

CIRCLEQ_FIRST 和 CIRCLEQ_LAST

#define	CIRCLEQ_FIRST(head)	((head)->cqh_first)
#define	CIRCLEQ_LAST(head)	((head)->cqh_last)

这两个很好理解,不解释了。

CIRCLEQ_PREV 和 CIRCLEQ_NEXT

#define	CIRCLEQ_PREV(elm,field)	((elm)->field.cqe_prev)
#define	CIRCLEQ_NEXT(elm,field)	((elm)->field.cqe_next)

field 就是最开始定义的 entries

CIRCLEQ_INIT

#define	CIRCLEQ_INIT(head) do {						\
	CIRCLEQ_FIRST((head)) = (void *)(head);				\
	CIRCLEQ_LAST((head)) = (void *)(head);				\
} while (0)

初始化一个链表后,表头的两个指针都指向自己。如下图

请添加图片描述

CIRCLEQ_EMPTY

#define	CIRCLEQ_EMPTY(head)	((head)->cqh_first == (void *)(head))

CIRCLEQ_INSERT_TAIL

#define	CIRCLEQ_INSERT_TAIL(head, elm, field) do {			\
	CIRCLEQ_NEXT((elm), field) = (void *)(head);			\
	CIRCLEQ_PREV((elm), field) = CIRCLEQ_LAST((head));		\
	if (CIRCLEQ_FIRST((head)) == (void *)(head))			\
		CIRCLEQ_FIRST((head)) = (elm);				\
	else								\
		CIRCLEQ_NEXT(CIRCLEQ_LAST((head)), field) = (elm);	\
	CIRCLEQ_LAST((head)) = (elm);					\
} while (0)

把 elm 插入到链表的尾部,都需要改变哪些指针呢?

  1. elm 的 field.cqe_next,因为它是最后一个,所以它应该等于 (void *)(head)
  2. elm 的 field.cqe_prev
  3. 插入后,elm 前面的那个节点的 field.cqe_next(如果前面的节点是表头,那就修改CIRCLEQ_FIRST(head)
  4. 修改 (head)->cqh_last 指向 elm

代码第 2、3 行分别解决了 1、2

代码第 4、5 行解决了 3 的括号中的情况

第 7 行解决了 3,elm 前面的那个节点就是原来最后的节点,CIRCLEQ_LAST(head),它的 cqe_next 就是 CIRCLEQ_NEXT(CIRCLEQ_LAST((head)), field)

第 8 行解决了 4

CIRCLEQ_INSERT_HEAD

#define	CIRCLEQ_INSERT_HEAD(head, elm, field) do {			\
	CIRCLEQ_NEXT((elm), field) = CIRCLEQ_FIRST((head));		\
	CIRCLEQ_PREV((elm), field) = (void *)(head);			\
	if (CIRCLEQ_LAST((head)) == (void *)(head))			\
		CIRCLEQ_LAST((head)) = (elm);				\
	else								\
		CIRCLEQ_PREV(CIRCLEQ_FIRST((head)), field) = (elm);	\
	CIRCLEQ_FIRST((head)) = (elm);					\
} while (0)

把 elm 插入到链表的头部,都需要改变哪些指针呢?

  1. elm 的 field.cqe_next
  2. elm 的 field.cqe_prev,它应该等于 (void *)(head)
  3. (head)->cqh_first
  4. 插入后,elm 后面的那个节点的 field.cqe_prev(如果插入 elm 前链表为空,那就修改CIRCLEQ_LAST(head)

第 2 行解决了 1

第 3 行解决了 2

第 4-5 行解决了 4 的括号中的情况

第 7 行解决了 4

第 8 行解决了 3

代码 4-7 行说明,因为没有用二级指针,所以头插的时候需要分情况考虑。但是不用二级指针也有好处,就是代码更好理解。

CIRCLEQ_INSERT_AFTER

#define	CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do {		\
	CIRCLEQ_NEXT((elm), field) = CIRCLEQ_NEXT((listelm), field);	\
	CIRCLEQ_PREV((elm), field) = (listelm);				\
	if (CIRCLEQ_NEXT((listelm), field) == (void *)(head))		\
		CIRCLEQ_LAST((head)) = (elm);				\
	else								\
		CIRCLEQ_PREV(CIRCLEQ_NEXT((listelm), field), field) = (elm);\
	CIRCLEQ_NEXT((listelm), field) = (elm);				\
} while (0)

要把 elm 插入到 listelm 元素的后面,要修改哪些指针呢?

  1. elm 的 field.cqe_next
  2. elm 的 field.cqe_prev
  3. listelm 的 field.cqe_next
  4. 插入之前,listelm 后面的那个节点的 field.cqe_prev(如果 listelm 后面没有节点了,就修改 CIRCLEQ_LAST(head)

2-3 行分别解决了 1、2

4-5 行解决了 4 括号里的情况

第 7 行解决了 4

第 8 行解决了 3

CIRCLEQ_INSERT_BEFORE

#define	CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do {		\
	CIRCLEQ_NEXT((elm), field) = (listelm);				\
	CIRCLEQ_PREV((elm), field) = CIRCLEQ_PREV((listelm), field);	\
	if (CIRCLEQ_PREV((listelm), field) == (void *)(head))		\
		CIRCLEQ_FIRST((head)) = (elm);				\
	else								\
		CIRCLEQ_NEXT(CIRCLEQ_PREV((listelm), field), field) = (elm);\
	CIRCLEQ_PREV((listelm), field) = (elm);				\
} while (0)

要把 elm 插入到 listelm 元素的前面,要修改哪些指针呢?

  1. elm 的 field.cqe_next
  2. elm 的 field.cqe_prev
  3. listelm 的 field.cqe_prev
  4. 插入之前,listelm 前面的那个节点的 field.cqe_next(如果 listelm 前面是表头,就修改 CIRCLEQ_FIRST(head)

2-3 行分别解决了 1、2

4-5 行解决了 4 括号里的情况

第 7 行解决了 4

第 8 行解决了 3

CIRCLEQ_FOREACH

#define	CIRCLEQ_FOREACH(var, head, field)				\
	for ((var) = CIRCLEQ_FIRST((head));				\
	    (var) != (void *)(head) || ((var) = NULL);			\
	    (var) = CIRCLEQ_NEXT((var), field))

顺序遍历,只要 (var) != (void *)(head) 就会往下进行,如果等于 head,根据 || 运算符的求值规则,会执行 (var) = NULL,这个表达式的结果为 false,于是终止 for 循环

CIRCLEQ_FOREACH_REVERSE

#define	CIRCLEQ_FOREACH_REVERSE(var, head, field)			\
	for ((var) = CIRCLEQ_LAST((head));				\
	    (var) != (void *)(head) || ((var) = NULL);			\
	    (var) = CIRCLEQ_PREV((var), field))

逆序遍历,和顺序遍历类似,不解释了。

CIRCLEQ_REMOVE

#define	CIRCLEQ_REMOVE(head, elm, field) do {				\
	if (CIRCLEQ_NEXT((elm), field) == (void *)(head))		\
		CIRCLEQ_LAST((head)) = CIRCLEQ_PREV((elm), field);	\
	else								\
		CIRCLEQ_PREV(CIRCLEQ_NEXT((elm), field), field) =	\
		    CIRCLEQ_PREV((elm), field);				\
	if (CIRCLEQ_PREV((elm), field) == (void *)(head))		\
		CIRCLEQ_FIRST((head)) = CIRCLEQ_NEXT((elm), field);	\
	else								\
		CIRCLEQ_NEXT(CIRCLEQ_PREV((elm), field), field) =	\
		    CIRCLEQ_NEXT((elm), field);				\
} while (0)

移除节点 elm,需要修改那些指针呢?

  1. elm 前面那个节点的 field.cqe_next

  2. elm 后面那个节点的 field.cqe_prev

  3. 对于 1,如果 elm 前面的节点是 head,那就需要修改 CIRCLEQ_FIRST(head)

  4. 对于 2, 如果 elm 后面没有节点了,那就需要修改 CIRCLEQ_LAST(head)

代码 2-3 行解决了 4

5-6 行解决了 2

代码 7-8 行解决了 3

10-11 解决了 1

【end】

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值