目录
前言
在libevent中使用到了TAILQ数据结构,看了一下其他资料,发现TAILQ这一数据结构不仅仅用于libevent中,在很多其他地方像linux内核中也有使用。它的内部实际上就是一个双向链表,可以实现结点的插入(头插、尾插、指定位置插入)、删除、替换和遍历等功能,不过所有功能都是通过宏函数来实现的,有的地方还是比较难以理解的,下面就来分析一下这一数据结构。
结点定义
TAILQ中涉及到了两个很关键的结构体,如下所示:(queue.h)
#define TAILQ_HEAD(name, type) \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* addr of last next element */ \
}
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
}
先来猜测一下这两个宏定义的作用。在宏定义TAILQ_HEAD下的结构体中,包含了两个结构体成员tqh_first和tqh_last,先不管它们是几级指针,从成员名就能推测,tqh_first应当是和链表第一个元素有关,而tqh_last则和链表最后一个元素相关。再来看宏定义TAILQ_ENTRY,其中也包含了两个结构体成员tqe_next和tqe_prev,从变量名就会发现,二者应当与前后元素相关,这实际上和双向链表中的结点定义是非常相似的。因此,就可以推断,宏定义中所需输入的参数type实际上就是结点类型,这个结点类型应该包含但不限于TAILQ_ENTRY所定义的结构,而TAILQ_HEAD则是对于整个双向链表而言的,用于找到首尾结点元素,因此TAILQ_HEAD中的type也应该是与TAILQ_ENTRY中相同的结点类型。
那么这里为什么TAILQ_HEAD还需要一个参数name呢?前面说了,TAILQ_ENTRY应当包含在结点类型的定义中,结点类型一旦定义好了并定义了一个结点,那么自然而然tqe_next和tqe_prev就都包含在该结点中了,此时TAILQ_ENTRY结构体作为一个匿名结构体即可,因此无需指定name来定义TAILQ_ENTRY结构体的名称;而对于TAILQ_HEAD来说,它是独立的数据类型,用来描述了双向链表的首尾结点,需要用TAILQ_HEAD来定义一个具体的结构体来存放首尾结点指针,因此这里必须指明结构体名name。
根据前面的推测,现在来正式分析一下TAILQ_HEAD与TAILQ_ENTRY中各成员的含义。
对于TAILQ_HEAD宏定义,其中的tqh_first为一级指针,tqh_last为二级指针,也就是说,tqh_first指向一个struct type类型的结点,而tqh_last则是指向一个指向一个struct type类型的结点的指针。TAILQ_ENTRY中的tqe_next和tqe_prev也是类似,这里就不多说了。那么到底各自指向什么呢?如果光是通过代码来推测一级、二级指针各自指向什么,我觉得太麻烦,因此我直接写一个程序先来看看结果如何:
#include <iostream>
#include "queue.h"
using namespace std;
struct Entry //结点类型
{
int val;
TAILQ_ENTRY(Entry)entry;
};
TAILQ_HEAD(Head, Entry); //名为Head的结构体,指向首尾Entry类型的结点
int _tmain(int argc, _TCHAR* argv[])
{
Head Head_h;
TAILQ_INIT(&Head_h);
for (int i = 0; i < 3; i++)
{
Entry * new_item = (Entry *)malloc(sizeof(Entry));
new_item->val = i;
TAILQ_INSERT_HEAD(&Head_h, new_item, entry); //头插法插入新结点
}
Entry* p; //用于遍历时保存当前结点
int i = 0;
cout << "first : " << Head_h.tqh_first << " first addr : " << &Head_h.tqh_first << endl << endl; //打印first的值以及first的地址
TAILQ_FOREACH(p, &Head_h, entry) //遍历链表
{
cout << "Node " << i++ << " addr : " << p << endl; //打印结点地址
cout << "prev : " << p->entry.tqe_prev << " prev addr : " << &p->entry.tqe_prev << endl; //打印prev的值以及prev地址
cout << "next : " << p->entry.tqe_next << " next addr : " << &p->entry.tqe_next << endl << endl; //打印next的值以及next的地址
}
cout << "last : " << Head_h.tqh_last << " last addr : " << &Head_h.tqh_last << endl; //打印last的值以及last的地址
system("pause");
return 0;
}
在该程序中,定义了结点类型为Entry类型,其中包含了一个int型的val变量以及TAILQ_ENTRY所定义的结构体。可以看到,调用TAILQ_HEAD宏函数时,传入的name参数Head最终就成为了TAILQ_HEAD下结构体类型名。然后用Head来定义一个Head_h变量,其中保存的即是双向链表中的首尾结点信息了。接着就是以头插法形式插入三个结点,然后遍历输出各个结点中关键成员的值与地址,结果如下: