在libevent中有一个基础结构TAILQ到处都可看到,TAILQ是由一系列宏定义的,这种数据结构的设计以及工程技巧是非常值得学习的,结合我自己的理解,深入的分析TAILQ。建议开始阅读文本前,在一个不易被打扰的环境,且有一份笔和纸,自己画一画节点间的连接关系图。
为了方便表述,有几个自定义名词如下:
头指针节点:指的是TAILQ的管理节点,而不是链表中的元素节点
元素节点:即链表中的节点,本文中也会用entry表示
头节点:即链表中的第一个元素节点
尾节点:即链表中的最后一个元素节点
TAILQ_HEAD
TAILQ有一个头指针节点
来"管理"整个链表
#define TAILQ_HEAD(name, type) \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* addr of last next element */ \
}
tqh_first指向 链表的头节点
,tqh_last指向 链表的尾节点
。一般在链表这种数据结构中,使用头指针节点来管理链表是更好的方式,一来可以充分指针的灵活性,二来可以占用更少的内存空间。
TAILQ_ENTRY
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
}
链表中的元素节点的结构,可以发现都是指针,并没看到实际存储节点数据的成员,这里也是一个设计技巧,目的为了提高entry的灵活性。为了更加形象的理解,来一张生活气息的图:
可以将TAILQ_ENTRY的结构体看作是一个个钩子(指针),搭好这个架子(链表框架),至于每个钩子上挂什么,就看使用者自己分配,是想挂鸡呢还是挂鸭都可以。
TAILQ_INIT
// 初始化头指针节点
#define TAILQ_INIT(head) do { \
(head)->tqh_first = NULL; \
(head)->tqh_last = &(head)->tqh_first; \
} while (0)
TAILQ_INIT初始化链表头指针节点
,该头指针节点
由两个成员组成:*tqh_first,**tqh_last,头指针节点
的作用是将整个链表首尾节点连接起来形成闭环。
从初始化可以看出,将hqh_first指向NULL,tqh_last指向tqh_first;
TAILQ_INSERT_TAIL
尾插
// 将新节点(elm)尾插入队列
#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 (0)
(elm)->field.tqe_next = NULL;
// 将新节点的tqe_next指向NULL
(elm)->field.tqe_prev = (head)->tqh_last;
// 将新节点的tqe_prev指向tqh_last,因为tqh_last的值是 旧尾节点的tqe_next的地址,所以新节点的tqe_prev是指向 旧尾节点的tqe_next,这样就将新节点和旧尾节点关联起来(从后往前)。
// 如果是一个没有元素节点的空链表,新节点的tqe_pre是指向"头指针节点"的tqh_first
*(head)->tqh_last = (elm);
// *tqh_last 指向新节点的地址,而*tqh_last为旧尾节点的tqe_next,也就是将旧尾节点的tqe_next指向 新的尾节点,如此便将新尾节点和旧尾节点关联起来(从前往后)。
(head)->tqh_last = &(elm)->field.tqe_next;
// "头指针节点"的tqh_last指向 新节点的tqe_next
// 至此,新节点就尾插进队列
从文字上看,可能还是不太理解,你可以自己画一画节点之间的连接关系,也可以看看下面这个图:
TAILQ_REMOVE
// 将节点(elm)移出队列
#define TAILQ_REMOVE(head, elm, field) do { \
if (((elm)->field.tqe_next) != NULL) \
(elm)->field.tqe_next->field.tqe_prev = \
(elm)->field.tqe_prev; \
else \
(head)->tqh_last = (elm)->field.tqe_prev; \
*((elm)->field.tqe_prev) = (elm)->field.tqe_next; \
} while (0)
if (((elm)->field.tqe_next) != NULL)// elm不是尾节点
(elm)->field.tqe_next->field.tqe_prev = (elm)->field.tqe_prev;
//将elm的后一个节点的tqe_prev 指向 elm的前一个节点的tqe_next;即将elm的前一个节点和后一个节点关联起来
else //elm 是尾节点
(head)->tqh_last = (elm)->field.tqe_prev;
// tqh_last指向elm的前一个节点;即断开elm节点和tqh_last的关联,同时将elm的前一个节点作为新的尾节点
*((elm)->field.tqe_prev) = (elm)->field.tqe_next;// 将elm的前一个节点的tqe_next指向elm的后一个节点
说明:移除只是将 待移除节点 与前后节点的连接断开,若要释放该节点的内存还需要free。
看到这里,我们了解了TAILQ的 初始化、尾插新节点、删除指定节点。可能对于有些同学来说,以上内容还是有点儿抽象,所以接下来,我会给出简单的示例代码来帮助理解。当然,如果以上内容你能完全理解,就可以跳过该示例代码。
示例
link.h
struct link_entry {
TAILQ_ENTRY(link_entry) next;
char * user_data; //可以挂鸡或鸭
};
struct link {
TAILQ_HEAD(link_q, link_entry) entries;
int link_size;
// something else
};
struct link* link_new(void);
void link_free(struct link* link);
int link_add(struct link* link, char* user_data);
void link_print(struct link* link);
link.c
struct link* link_new(void)
{
struct link* link = malloc(sizeof(struct link));
if (NULL == link)
return NULL;
TAILQ_INIT(&link->entries);
link->link_size = 0;
return link;
}
TAILQ_INIT展开后,如下图所示:
int link_add(struct link* link, char* user_data)
{
struct link_entry* entry = malloc(sizeof(struct link_entry));
if (NULL == entry)
return -1;
TAILQ_INSERT_TAIL(&link->entries, entry, next);
entry->user_data = user_data;
return 0;
}
TAILQ_INSERT_TAIL展开后,如下图所示:
void link_free(struct link* link)
{
struct link_entry *entry;
while ((entry = TAILQ_FIRST(&link->entries)) != NULL)
{
TAILQ_REMOVE(&link->entries, entry, next);
link_entry_free(entry);
}
free(link);
}
TAILQ_REMOVE展开后,如下图所示:
void link_print(struct link* link)
{
struct link_entry* entry = NULL;
TAILQ_FOREACH(entry, &link->entries, next)
{
printf("user data:%s\n", entry->user_data);
}
}
main.c
int main(int argc, char** argv)
{
char* user_data1 = (char*)malloc(16);
char* user_data2 = (char*)malloc(16);
strcpy(user_data1, "duck");
strcpy(user_data2, "chicken");
struct link* link = link_new();
link_add(link, user_data1);
link_add(link, user_data2);
link_print(link);
link_free(link);
free(user_data1);
free(user_data2);
return 0;
}
// 打印输出
user data:duck
user data:chicken
TAILQ_INSERT_AFTER
后插
因为后插可能会涉及到尾节点,那么头指针节点
的tqh_last有可能要被修改,所以需要head
// 将新节点(elm)插入到listelm节点后
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
(elm)->field.tqe_next->field.tqe_prev = \
&(elm)->field.tqe_next; \
else \
(head)->tqh_last = &(elm)->field.tqe_next; \
(listelm)->field.tqe_next = (elm); \
(elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
} while (0)
do {
if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL) // listelm不是尾节点,使elm新节点的tqe_next指向listelm节点的下一个节点
(elm)->field.tqe_next->field.tqe_prev = &(elm)->field.tqe_next; // 使listelm的下一个节点的tqe_prev指向elm新节点的tqe_next
else
(head)->tqh_last = &(elm)->field.tqe_next; // listelm是尾节点,使"头指针节点"的tqh_last指向新节点的tqe_next,即elm变成尾节点
(listelm)->field.tqe_next = (elm);// listelm的tqe_next指向新节点elm
(elm)->field.tqe_prev = &(listelm)->field.tqe_next;// 新节点elm的tqe_prev指向listelm的tqe_next
} while (0)
TAILQ_INSERT_BEFORE
前插
// 将新节点(elm)插入到listelm节点前
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
(elm)->field.tqe_next = (listelm); \
*(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
} while (0)
do {
(elm)->field.tqe_prev = (listelm)->field.tqe_prev;// 使elm的tqe_prev指向listelm的前一个节点
(elm)->field.tqe_next = (listelm);// 使elm的tqe_next指向listelm节点
*(listelm)->field.tqe_prev = (elm);// 使listelm的前一个节点的tqe_next指向elm节点
(listelm)->field.tqe_prev = &(elm)->field.tqe_next;// 使listelm的tqe_next指向elm的tqe_next的地址
} while (0)
TAILQ_INSERT_HEAD
头插
// 将新节点(elm)插入为头节点
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
(head)->tqh_first->field.tqe_prev = \
&(elm)->field.tqe_next; \
else \
(head)->tqh_last = &(elm)->field.tqe_next; \
(head)->tqh_first = (elm); \
(elm)->field.tqe_prev = &(head)->tqh_first; \
} while (0)
do {
if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)// 链表不为空,新节点elm的tqe_next指向链表的当前头节点
(head)->tqh_first->field.tqe_prev = &(elm)->field.tqe_next;// 将链表当前头节点的tqe_prev指向新节点的tqe_next
else
(head)->tqh_last = &(elm)->field.tqe_next;// 链表为空,"头指针节点"的tqh_last指向新节点elm的tqe_next的地址
(head)->tqh_first = (elm);// 头指针节点的tqh_first指向新节点
(elm)->field.tqe_prev = &(head)->tqh_first;// 新节点的tqe_prev指向"头指针节点"的tqh_first的地址
} while (0)
TAILQ_REPLACE
// elm2替换elm
#define TAILQ_REPLACE(head, elm, elm2, field) do { \
if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \
(elm2)->field.tqe_next->field.tqe_prev = \
&(elm2)->field.tqe_next; \
else \
(head)->tqh_last = &(elm2)->field.tqe_next; \
(elm2)->field.tqe_prev = (elm)->field.tqe_prev; \
*(elm2)->field.tqe_prev = (elm2); \
} while (0)
do {
if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)// elm不是尾节点,使elm2的tqe_next指向elm的后一个节点
(elm2)->field.tqe_next->field.tqe_prev = &(elm2)->field.tqe_next;// 使elm的后一个节点的tqe_prev指向elm2的tqe_next的地址
else
(head)->tqh_last = &(elm2)->field.tqe_next;// elm是尾节点,使thq_last指向elm2的tqe_next的地址
(elm2)->field.tqe_prev = (elm)->field.tqe_prev;// elm2的tqe_prev指向elm的tqe_prev,即elm前一个节点的tqe_next的地址
*(elm2)->field.tqe_prev = (elm2);// elm的前一个节点的tqe_next指向elm2
} while (0)
TAILQ_FIRST
// 获取头节点的地址
#define TAILQ_FIRST(head) ((head)->tqh_first)
TAILQ_END
#define TAILQ_END(head) NULL
TAILQ_NEXT
// 获取当前节点(elm)的下一个节点的地址
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
TAILQ_LAST
//获取尾节点地址
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
struct {
struct type *tqh_first;
struct type **tqh_last;
}
// ((head)->tqh_last)是一个二级指针,它的值是尾节点tqe_next的地址
// 无论是一级指针还是二级指针,指针变量的值都是一个地址,只不过二级指针变量的值只能是一级指针变量的地址,而一级指针变量的值可以是普通变量的地址
// (struct headname *)((head)->tqh_last) 将tqh_last强转成struct headname的一级指针,但tqh_last的值不变,假定p = (struct headname *)((head)->tqh_last)(为了方便后面表述),此时p指向tqe_next所在内存的地址。
已知entry的结构体为
struct {
struct type *tqe_next;
struct type **tqe_prev;
}
// 已知tqe_next后一个成员是tqe_prev,又因为p的值等于尾节点tqe_next的地址,所以p->tqh_last的值等于尾节点tqe_prev的值,即尾节点的前一个节点的tqe_next的地址;
// 那么*(p->tqh_last) 就是 尾节点的前一个节点的tqe_next的值,也就得到了尾节点的首地址。
我认为这个宏用的非常巧妙,只有充分理解了结构体的存储和指针才能有此妙想和使用。
本质上就是对内存地址进行强制类型转换,然后利用指针和结构体的存储特性来获取特定的值。下面的示例,对这个原理做了一个简单的抽象,方便理解:
示例
TAILQ_PREV
// 获取当前节点的前一个节点
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
// 理解了前面TAILQ_LAST,那么TAILQ_PREV也是同样的原理
// ((elm)->field.tqe_prev) ---> 前一个节点的tqe_next的地址
// ((struct headname *)((elm)->field.tqe_prev))->tqh_last ---> 前一个节点的tqe_prev,而前一个节点的tqe_prev的值是前两个节点的tqe_next的地址
// (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) ---> 前两个节点的tqe_next的值即为前一个节点的地址
TAILQ_EMPTY
#define TAILQ_EMPTY(head) \
(TAILQ_FIRST(head) == TAILQ_END(head))
TAILQ_FOREACH
// 正向遍历
#define TAILQ_FOREACH(var, head, field) \
for((var) = TAILQ_FIRST(head); \
(var) != TAILQ_END(head); \
(var) = TAILQ_NEXT(var, field))
TAILQ_FOREACH_REVERSE
// 逆向遍历
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for((var) = TAILQ_LAST(head, headname); \
(var) != TAILQ_END(head); \
(var) = TAILQ_PREV(var, headname, field))
QQ讨论群:679603305