TailQueue详解
本文详细解释Libevent中TailQueue数据结构及使用方法。
采用的基础数据结构如下所示:该数据结构包含2个业务字段,设备编号devId和设备名称name。
struct AlarmInfo
{
//业务数据
int devId;
const char name[32];
}
如果要采用TailQueue进行改结构体保存,必须在该数据结构中增加字段用于建立链表之用,如下所示:
struct AlarmInfo
{
//业务数据
int devId;
const char name[32];
TAILQ_ENTRY(AlarmInfo) TQueueField;
}
TAILQ_ENTRY(AlarmInfo)TQueueField;是一个宏定义,展开后该数据结构如下:
struct AlarmInfo
{
//业务数据
int devId;
const char name[32];
Struct
{
struct AlarmInfo *tqe_next;
struct AlarmInfo **tqe_prev;
} TQueueField;
}
tqe_next指向下一个要素的地址;
tqe_prev指向前一个要素的next指针的地址。
这样,基本的数据结构就定义完毕。
但是,还必须有其他的辅助手段,才能实现数据的存取。
首先,定义一个列表头,其宏定义如下:
#define TAILQ_HEAD(name, type)
struct name
{
/* first element */
Struct type *tqh_first;
/* addr of last next element */
Struct type **tqh_last;
}
对于本文中的AlarmInfo数据需要通过如下语句定义一个新的结构体: TAILQ_HEAD(AlarmInfoTaileQueue,AlarmInfo);
该宏定义展开后如下图所示:
struct AlarmInfoTaileQueue
{
Struct AlarmInfo *tqh_first;
Struct AlarmInfo **tqh_last;
};
然后就可以用该结构体定义一个实际的结构体实例,用于保存改队列的头节点。
struct AlarmInfoTaileQueue alarmTQHead;
在使用该头节点前,需要对其进行初始化:
TAILQ_INIT(&alarmTQHead);
其宏定义如下:
#define TAILQ_INIT(head) do {\
(head)->tqh_first = NULL; \
(Head)->tqh_last = &(head)->tqh_first;\
} while (0)
主要作用就是将头指针(tqh_first)初始化为NULL,将尾指针(指向指针的指针)指向头指针(tqh_first)的地址。
接下来,就可以通过该头节点对该队列进行操作了。
//增加要素进入队列
AlarmInfoalr;
for(i=0;i<10;i++)
{
//创建该结构体实例。
alr =NewAlarmInfo();
if(NULL== alr){return;}
alr -> devId =i;
sprintf(tem,"jiayp%d",i+1);
strcpy(alr->name,tem);
TAILQ_INSERT_TAIL(&alarmTQHead,alr,TQueueField);
}
TAILQ_INSERT_TAIL宏定义如下:
#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
该宏定义实现功能如下图:
调用TAILQ_INIT(&alarmTQHead)后如下图所示:
第一次调用TAILQ_INSERT_TAIL(&alarmTQHead,alr,TQueueField);后如下图所示:
首先,将AlarmInfo节点下TequeueField的tqe_next的指针设置为NULL;
然后,将AlarmInfo节点下TequeueField的tqe_prev(指向指针的指针)设置到alarmTQHead的Tqh_first(因为原来尾指针(指向指针的指针)就是指向头指针的地址);
然后,将尾指针(此时改指针仍然指向头指针的地址),所以是将头指针指向实际的数据对象(AlarmInfo)地址;
最后,将尾指针,指向新对象的teq_next指针的地址。
第二次调用TAILQ_INSERT_TAIL(&alarmTQHead,alr,TQueueField);后如下图所示:
这样就形成了一个链表,该链表的alarmTQHead节点,分别指向链表的第一和最后一个节点。
下面看一下链表的遍历过程:
static void OutputAll(){
struct AlarmInfo*alr;
TAILQ_FOREACH (alr,& alarmTQHead,TQueueField)
{
printf(alr->name);
}
}
TAILQ_FOREACH也是一个宏定义:
#define TAILQ_FOREACH(var, head, field)\
for((var) = AILQ_FIRST(head);\
(var) != TAILQ_END(head);\
(var) = TAILQ_NEXT(var, field))
代码展开后如下所示:
static void OutputAll()
{
struct AlarmInfo *alr;
for((var) = alarmTQHead ->tqh_first;(var) != NULL; (var)= var->TQueueField. tqe_next)
{
printf(alr->name);
}
}
该代码简单,不做详细说明。