RT.
当我看libevent源代码的时候,看到了queue,并且在evbuffer.c里面大量使用,必须先将queue这个关攻克,才可以继续无障碍的看evbuffer.c,乃至bufferevent.h等具体的包装类实现。刚开始学习,免不了要在网上搜索代码了。
这不,刚刚就在csdn搜索到一篇很好的讲述queue用法的文章(地址:http://blog.csdn.net/jiayp004/article/details/13023027)
我按照他给的代码一步一步走,还是容易懂的。唯一让我纠结的就是指针的指针,很麻烦。不得已,我通过debug,并记录下,每个指针的指针变量的地址。来判断。
首先引用一下那篇博客的内容。
// Begin -- TailQueue详解
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);
}
}
该代码简单,不做详细说明。
// End -- TailQueue详解
看完这篇博客,相信大家都明白了差不多了。
下面我给出我调试,并将宏展开后的代码,睁大眼睛哦。。
// 该数据结构包含2个业务字段,设备编号devId和设备名称name。
struct AlarmInfo
{
//业务数据
int devId;
char name[32];
//TAILQ_ENTRY(AlarmInfo) TQueueField;
struct {
struct AlarmInfo* tqe_next;
struct AlarmInfo** tqe_prev;
} TQueueField;
/*
* 展开为:
* struct {
* struct AlarmInfo *tqe_next; // next element
* struct AlarmInfo **tqe_prev; // address of previous next element
* } TQueueField;
*/
};
//TAILQ_HEAD(AlarmInfoTailQueue, AlarmInfo);
struct AlarmInfoTailQueue {
struct AlarmInfo *tqh_first; // first element
struct AlarmInfo **tqh_last; // addr of last next element
};
/* 定义了一个结构体
* 展开为:
* struct AlarmInfoTailQueue {
* struct AlarmInfo *tqh_first; // first element
* struct AlarmInfo **tqh_last; // addr of last next element
* }
*/
///
inline void Test_Tail_Queue()
{
struct AlarmInfoTailQueue alarmTQHead;
//TAILQ_INIT(&alarmTQHead);
do {
(&alarmTQHead)->tqh_first = NULL;
(&alarmTQHead)->tqh_last = &((&alarmTQHead)->tqh_first);
} while(0);
// debug
LogPrintflnA("alarmTQHead: %x", &alarmTQHead);
LogPrintflnA("tqh_first: %x", &(&alarmTQHead)->tqh_first);
LogPrintflnA("tqh_last: %x", &(&alarmTQHead)->tqh_last);
/*
* 展开为:
* do {
* (&alarmTQHead)->tqh_first = NULL;
* (&alarmTQHead)->tqh_last = &(&alarmTQHead)->tqh_first;
* } while (0);
*/
//增加元素进入队列
char temp[32];
for( int i=0; i<2; i++) {
//创建该结构体实例
AlarmInfo* pInfo = new AlarmInfo;
// debug
LogPrintflnA("pInfo: %x", &pInfo);
LogPrintflnA("&devId: %x", &(pInfo->devId));
LogPrintflnA("tqe_next: %x", &(pInfo->TQueueField.tqe_next));
LogPrintflnA("tqh_last: %x", &(pInfo->TQueueField.tqe_prev));
if( NULL == pInfo ) {
return;
}
sprintf(temp, "jiayp%d", i+1);
pInfo->devId = i;
strcpy(pInfo->name, temp);
do {
pInfo->TQueueField.tqe_next = NULL;
pInfo->TQueueField.tqe_prev = (&alarmTQHead)->tqh_last;
*(&alarmTQHead)->tqh_last = pInfo;
(&alarmTQHead)->tqh_last = &(pInfo->TQueueField.tqe_next);
} while(0);
//TAILQ_INSERT_TAIL(&alarmTQHead, pInfo, TQueueField);
/*
* 展开为:
* do {
* pInfo->TQueueField.tqe_next = NULL;
* pInfo->TQueueField.tqe_prev = (&alarmTQHead)->tqh_last;
* *(&alarmTQHead)->tqh_last = pInfo;
* (&alarmTQHead)->tqh_last = &(pInfo->TQueueField.tqe_next);
* } while(0);
*/
}
// output
struct AlarmInfo*alr;
TAILQ_FOREACH(alr, &alarmTQHead, TQueueField)
{
LogPrintflnA(alr->name);
}
}
当我们定义了
struct AlarmInfoTailQueue alarmTQHead;
然后在循环里面,动态创建了一个AlarmInfo,此时他们在内存的布局为:
详见,初始化部分:
经过我们的
do {
pInfo->TQueueField.tqe_next = NULL;
pInfo->TQueueField.tqe_prev = (&alarmTQHead)->tqh_last;
*(&alarmTQHead)->tqh_last = pInfo;
(&alarmTQHead)->tqh_last = &(pInfo->TQueueField.tqe_next);
} while(0);
部分后,其中内存的布局如我们的“插入第一个AlarmInfo'结构所示。
然后我们继续插入第二个AlarmInfo结构,其中内存布局为:
所以,使用TailQueue可以直接索引到第一个和最后一个AlarmInfo。
每一个AlarmInfo之间都是有链表连接的,最后一个AlarmInfo的prev域存放的是一个 AlaramInfoTailQueue的地址。