libuv系列--queue详解

news/2024/11/29 5:28:35/

一、目的

        在以前的博文《RT-Thread系列--双链表分析》我们介绍了如何实现双链表,其实这种双链表结构在linux内核中用得比较多,但是考虑到版权问题,libuv通过宏实现了同样功能的双链表结构。本篇的目的就是帮助大家理解其实现原理,涉及到的知识点包括:指针、指针数组、左值

二、介绍

        涉及的源文件为libuv/src/queue.h

typedef void *QUEUE[2];

        上面的代码片段通过typedef定义了一个新类型QUEUE指针数组类型,数组大小为2个void类型的指针。

        我们可以声明一个QUEUE类型的变量

void *p[2];
QUEUE q = p;

         也可以声明一个QUEUE类型的指针

void *p[2];
QUEUE *q = &p;

        QUEUE类型里面的两个void *指针的作用是用于存储链表中的前一个元素和后一个元素的地址

        在进一步介绍了QUEUE的相关接口细节之前我们先使用QUEUE构建一个双链表。

双链表

 

struct foo {QUEUE q;int val;
};int main() {QUEUE header;QUEUE_INIT(&header);for (int i = 0; i < 10; i++) {struct foo *obj = calloc(1, sizeof(struct foo));obj->val = i;//QUEUE_INSERT_HEAD(&header, &obj->q);QUEUE_INSERT_TAIL(&header, &obj->q);}struct foo *obj;QUEUE *q;QUEUE_FOREACH(q, &header) {obj = QUEUE_DATA(q, struct foo, q);printf("obj value: %d\n", obj->val);}return 0;
}

        在构建一个新的双链表时,我们需要将QUEUE作为一个变量放在自定义的结构体中,比如上面的foo结构体中的变量q,通过q就可以将struct foo A/B/C连接起来。

        同时为了管理素双链表,我们还需要一个链表头,例如上面代码中的QUEUE header。

       

#define QUEUE_NEXT(q)       (*(QUEUE **) &((*(q))[0]))
#define QUEUE_PREV(q)       (*(QUEUE **) &((*(q))[1]))

         上面的两个宏一个是为了获取双链表q元素下一个元素的地址,一个是获取上一个元素的地址。注意QUEUE_NEXT中的q是QUEUE *类型的。

        之所以这样复杂是为了类型一致和保持左值,我们下面好好解释一下

        正常情况下我们如果要获取下一个元素的地址的话,可以直接q[0]就可以了,为什么此处如此复杂。

        (*(q))是对QUEUE *类型的q解引用

        ((*(q))[0])是获取第一个指针

        因为在类型Queue中[0][1]存放的是下一个元素的Queue类型的地址,所以我们需要对其进行类型强转(原本类型为void *)

        QUEUE * ((*(q))[0])

        但是这样类型强转之后是一个右值,无法对其赋值;如果只是进行取值操作是可以这样定义的。

        举例说明

//①
int a = 10;
//②
(char)a = 2;  //错误
//③
*((char *)&a) = 2; //正确

        在步骤②中我们是无法修改a的值等于2,但是③的方法却可以实现。 

        基于这样的原因,我们对

((*(q))[0])

        取地址,取地址后类型变成void **类型;然后我们再进行QUEUE **类型强制转换;然后再对其解引用(*);

        最终 QUEUE_NEXT(q)宏定义我们既可以用于取值操作,也可以用于赋值操作。

        

#define QUEUE_PREV_NEXT(q)  (QUEUE_NEXT(QUEUE_PREV(q)))
#define QUEUE_NEXT_PREV(q)  (QUEUE_PREV(QUEUE_NEXT(q)))

         链表初始化

#define QUEUE_INIT(q)                                                         \do {                                                                        \QUEUE_NEXT(q) = (q);                                                      \QUEUE_PREV(q) = (q);                                                      \}                                                                           \while (0)

        将q的next/prev都指向自身,此时q是一个空链表,例如QUEUE_INIT(header)将header进行初始化。

         

         判断链表是否为空

#define QUEUE_EMPTY(q)                                                        \((const QUEUE *) (q) == (const QUEUE *) QUEUE_NEXT(q))

        获取链表的第一个元素(必须是非空链表)

#define QUEUE_HEAD(q)                                                         \(QUEUE_NEXT(q))

        通过节点内QUEUE变量的地址获取的元素的首地址

/* Public macros. */
#define QUEUE_DATA(ptr, type, field)                                          \((type *) ((char *) (ptr) - offsetof(type, field)))

        在头部插入新元素

#define QUEUE_INSERT_HEAD(h, q)                                               \do {                                                                        \QUEUE_NEXT(q) = QUEUE_NEXT(h);                                            \QUEUE_PREV(q) = (h);                                                      \QUEUE_NEXT_PREV(q) = (q);                                                 \QUEUE_NEXT(h) = (q);                                                      \}                                                                           \while (0)

        在 尾部插入新元素

#define QUEUE_INSERT_TAIL(h, q)                                               \do {                                                                        \QUEUE_NEXT(q) = (h);                                                      \QUEUE_PREV(q) = QUEUE_PREV(h);                                            \QUEUE_PREV_NEXT(q) = (q);                                                 \QUEUE_PREV(h) = (q);                                                      \}                                                                           \while (0)

        移除节点

#define QUEUE_REMOVE(q)                                                       \do {                                                                        \QUEUE_PREV_NEXT(q) = QUEUE_NEXT(q);                                       \QUEUE_NEXT_PREV(q) = QUEUE_PREV(q);                                       \}                                                                           \while (0)

  

        遍历链表

/* Important note: mutating the list while QUEUE_FOREACH is* iterating over its elements results in undefined behavior.*/
#define QUEUE_FOREACH(q, h)                                                   \for ((q) = QUEUE_NEXT(h); (q) != (h); (q) = QUEUE_NEXT(q))

        上面的遍历过程中不能新增或者删除节点

        链接拼接


#define QUEUE_ADD(h, n)                                                       \do {                                                                        \QUEUE_PREV_NEXT(h) = QUEUE_NEXT(n);                                       \QUEUE_NEXT_PREV(n) = QUEUE_PREV(h);                                       \QUEUE_PREV(h) = QUEUE_PREV(n);                                            \QUEUE_PREV_NEXT(h) = (h);                                                 \}                                                                           \while (0)

        将n链表中的元素拼接到h链表的尾部

        分割链表

#define QUEUE_SPLIT(h, q, n)                                                  \do {                                                                        \QUEUE_PREV(n) = QUEUE_PREV(h);                                            \QUEUE_PREV_NEXT(n) = (n);                                                 \QUEUE_NEXT(n) = (q);                                                      \QUEUE_PREV(h) = QUEUE_PREV(q);                                            \QUEUE_PREV_NEXT(h) = (h);                                                 \QUEUE_PREV(q) = (n);                                                      \}                                                                           \while (0)

        将h链表从q节点开始分割到链表n中 

        移动链表

#define QUEUE_MOVE(h, n)                                                      \do {                                                                        \if (QUEUE_EMPTY(h))                                                       \QUEUE_INIT(n);                                                          \else {                                                                    \QUEUE* q = QUEUE_HEAD(h);                                               \QUEUE_SPLIT(h, q, n);                                                   \}                                                                         \}                                                                           \while (0)

        将链表中的节点移动到链表n中 

三、实战

#include "queue.h"#include <stdio.h>
#include <stdlib.h>struct foo {QUEUE q;int val;
};int main() {QUEUE header;QUEUE_INIT(&header);for (int i = 0; i < 10; i++) {struct foo *obj = calloc(1, sizeof(struct foo));obj->val = i;//QUEUE_INSERT_HEAD(&header, &obj->q);QUEUE_INSERT_TAIL(&header, &obj->q);}struct foo *obj;QUEUE *q;QUEUE_FOREACH(q, &header) {obj = QUEUE_DATA(q, struct foo, q);printf("obj value: %d\n", obj->val);}QUEUE header2;QUEUE_MOVE(&header, &header2);while (!QUEUE_EMPTY(header2)) {q = QUEUE_HEAD(&header2);QUEUE_REMOVE(q);obj = QUEUE_DATA(q, struct foo, q);printf("remove: %d, add tail\n", obj->val);QUEUE_INSERT_TAIL(&header, q);}QUEUE_MOVE(&header, &header2);while (!QUEUE_EMPTY(header2)) {q = QUEUE_HEAD(&header2);QUEUE_REMOVE(q);//安全遍历obj = QUEUE_DATA(q, struct foo, q);printf("free: %d\n", obj->val);free(q);}return 0;
}

        以上,就是libuv queue的全部内容。


http://www.ppmy.cn/news/41717.html

相关文章

Asp net core写法

变量和字符串赋值 $"{变量}字符串" 列如 $"{a}你好" 全球唯一标识符 Guid.NewGuid() 线程 Task Map Dictionary<string,object> using 定义对象的使用范围&#xff0c;即使释放对象 using(Student student new Student() ) { } 异步编程&#x…

springboot项目配置动态注入与docker设置环境变量

1.springboot怎么配置信息动态注入&#xff1f;2.docker怎么在镜像启动的时候注入env环境变量&#xff1f;带着这些问题我开始探索实验并把结果以及常用的命令记录下。 springboot动态注入配置信息。 这是一个很简单的问题&#xff0c;但是我不擅长记命令&#xff0c;只是知道…

Qt Quick - ProgressBar

ProgressBar使用总结一、概述二、使用三、定制化一、概述 ProgressBar 就是进度条。该值应该随着进度定期更新。范围由from和to定义&#xff0c;这个定义的就是区间值&#xff0c;它们都可以包含任何值。 ProgressBar {value: 0.5}ProgressBar还支持一种特殊的不确定模式&#…

6.【动手学深度学习v2】 矩阵计算

6. 矩阵计算【动手学深度学习v2】 李沐 B站&#xff1a;https://space.bilibili.com/1567748478/channel/seriesdetail?sid358497 课程主页&#xff1a;https://courses.d2l.ai/zh-v2/ 教材&#xff1a;https://zh-v2.d2l.ai/ 课件&#xff1a;https://courses.d2l.ai/zh-v2/a…

java StringBuffer和StringBuilder

目录一、概述二、StringBuffer和StringBuilder区别三、StringBuffer使用一、概述 String类是字符串常量&#xff0c;是不可更改的常量。而StringBuffer是字符串变量&#xff0c;它的对象是可以扩充和修改的。 StringBuffer是使用缓冲区的&#xff0c;本身也是操作字符串的&…

【中级软件设计师】—数据库系统考点总结篇(三)

【中级软件设计师】—数据库系统考点总结篇&#xff08;三&#xff09; 课程大纲与考点分布 1 数据库系统的体系结构 分布式数据库的透明性 1.1 三级模式—两级映射 1.2 数据库的设计过程 1.3 E-R模型 首先每个实体要单独转成一个关系模式&#xff0c;总共三个实体三个关系模式…

在外web浏览器远程访问jupyter notebook服务器【内网穿透】

文章目录前言视频教程1. Python环境安装2. Jupyter 安装3. 启动Jupyter Notebook4. 远程访问4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口5. 固定公网地址转载自远控源码文章&#xff1a;公网远程访问jupyter notebook【cpolar内网穿透】 前言 Jupyter Notebook&#…

CesiumForUnreal实现鹰眼地图(MiniMap)效果

文章目录 1.实现目标2.实现过程3.参考资料1.实现目标 基于CesiumForUnreal插件加载的在线地形和影像数据,使用Widget实现鹰眼小地图的效果,GIF动图如下: 2.实现过程 在UE开发中,常用的以Widget方法实现小地图的形式有两种。一种是动态的小地图,即地图的纹理图片会发生变化…