libevent源码学习(5):TAILQ_QUEUE解析

本文深入解析libevent中使用的TAILQ数据结构,详细介绍了结点定义、链表初始化、查询、遍历、插入、删除、替换等操作。通过示例程序展示了TAILQ的数据结构和指针关系,探讨了头结点在链表操作中的重要性,并解释了为何将prev和last定义为二级指针。
摘要由CSDN通过智能技术生成

目录

前言

结点定义

链表初始化

链表查询及遍历

链表查询

链表遍历

插入结点

头插法

尾插法

前插法

后插法

删除结点

替换结点

总结


前言

        在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变量,其中保存的即是双向链表中的首尾结点信息了。接着就是以头插法形式插入三个结点,然后遍历输出各个结点中关键成员的值与地址,结果如下:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值