起标题时在犹豫着要不要沿用“libevent源代码分析”,犹豫的原因是tail_queue其实是率属于linux的源代码。它在/usr/local/include/sys/queue.h中。但是我却是在看libevent的时候才发现它的。
tail_queue这个名字很奇怪,是个queue却冠以tail前缀。貌似是个新的数据结构,但是一路跟下来却更像是一个双向链表。队列允许中间插值吗?但它可是提供了TAILQ_INSERT_AFTER,TAILQ_INSERT_BEFORE。困惑吧。。。
当它是个双向链表好了,问题就简单了。双向链表怎么定义?假设有一个结构体叫Player
struct Player
{
int _id;
char _username[255];
int _isManager;
struct Player * Prev;//前向指针
struct Player * Next;//后项指针
};
Tail_queue提供了TAILQ_ENTRY宏,封装了前后项指针的定义。使用TAILQ_ENTRY,以上结构体就变成了
struct Player
{
int _id;
char _username[255];
int _isManager;
TAILQ_ENTRY(Player) tqEntry;
};
TAILQ_ENTRY的定义如下:
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
}
其实至此双向链表的定义就已经完成了,不过教科书通常都会告诉我们要定义一个链表头....嗯,我是说为了便于操作通常都需要定义一个链表头,对外代表一张链表。
不过这个TAIL_QUEUE既然叫QUEUE,就应该允许往头与尾添加数据,所以有指针指向头尾是免不了的。
TAILQ_HEAD(QueuePlayers, Player) ;
宏展开后就变成了
struct QueuePlayers {
struct Player *pFirst;
struct Player **ppLast;
}
pFirst代表队首元素。当队为空时,它为0。
ppLast指向最后一个元素的地址,默认时指向pFirst。
往队首插入数据时,代码大概会这么写。
void InsertToHead(struct Players* pQueuePlayers, struct Player* pPlayer)
{
pPlayer->tqEntry.tqe_next = pQueuePlayers->tqh_first;//往队首插入,故pPlayer.next = queue.first
if ( pQueuePlayers->tqh_first != NULL)
pQueuePlayers->tqh_first->tqEntry.tqe_prev = &pPlayer->tqEntry.tqe_next;//queue.first.prev = &(pPlayer->next)
else
pQueuePlayers->tqh_last = &pPlayer->tqEntry.tqe_next; //如果队为空,则修改queue.last = &pPlayer.next。只执行一次
pQueuePlayers->tqh_first = pPlayer;//往队首插入,故pPlayer成为队的第一个元素
pPlayer->tqEntry.tqe_prev = &pQueuePlayers->tqh_first;
}
tail_queue封装为:
#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)
第一个参数是队的变量(pQueuePlayers),第二个参数是要插入的元素(pPlayer),第三个参数是TAILQ_ENTRY类型的变量(tqEntry)
这种写法很有意思,类似于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 (0)
对于每个元素,都有tqe_next指针指向后一个元素。和指向前一个元素的*tqe_prev。这种表现形式像极了双向链表。
如果这还不够像,那么以下两个宏几乎都让人以为这个tail_queue其实不是队列了。
#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)
#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)
代码不复杂,就不多做解释。无非就是插入一个元素,更新prev跟next指针。
删除是TAILQ_REMOVE。没什么好说的。
很值得一提的是
TAILQ_FOREACH(p1,pQueuePlayers,tqEntry)
{
//在这里使用p1
}
这种写法很贴心,不用关心怎么遍历queue,不用关心它的next的写法是p1 = p1->next,还是 p1 = p1->tqEntry->next还是其它的。
For each的代码很值得一看。
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_END(head) NULL
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
/* XXX */
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_EMPTY(head) \
(TAILQ_FIRST(head) == TAILQ_END(head))
#define TAILQ_FOREACH(var, head, field) \
for((var) = TAILQ_FIRST(head); \
(var) != TAILQ_END(head); \
(var) = TAILQ_NEXT(var, field))
它还可以倒着遍历,
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for((var) = TAILQ_LAST(head, headname); \
(var) != TAILQ_END(head); \
(var) = TAILQ_PREV(var, headname, field))
至此分析完毕。
感受:链表、队列等是常用的数据结构。但在一遍遍写这种基础的数据结构时常会因为笔误引入一些不易调试的BUG。在C++中经常会使用std::queue, std::list来替代自己写的基础数据结构。在linux中,可以通过包含头文件sys/queue.h来使用这些成熟的代码。感觉挺好!
贴上我的一段小小的测试程序
struct Player
{
int _id;
char _username[255];
int _isManager;
TAILQ_ENTRY(Player) tqEntry;
};
void test()
{
TAILQ_HEAD(Players, Player) queuePlayers;
struct Player* p1 = NULL;
int i = 0;
TAILQ_INIT(&queuePlayers);
for( i = 0; i < 10; i ++)
{
p1 = (struct Player*)malloc(sizeof(struct Player));
p1->_id = i;
sprintf(p1->_username,"Player%d",i);
p1->_isManager = FALSE;
TAILQ_INSERT_HEAD(&queuePlayers, p1, tqEntry);
//TAILQ_INSERT_TAIL(&listPlayers, p1, tqEntry);
}
TAILQ_FOREACH(p1,&queuePlayers,tqEntry)
{
printf("id = %d, username = %s\n",p1->_id,p1->_username);
}
}