LVGL双向链表学习笔记
LVGL双向链表学习笔记
1、LVGL链表数据类型分析
对于LVGL双向链表的使用,我们需要关注lv_ll.h和lv_ll.c两个文件,其中lv_ll.h里面包含了链表结构类型定义,以及相关API的声明,首先介绍链表的结构类,如下图所示:
一开始看到这个类型声明我是懵的,怎么链表的一个结点的类型是uint8_t,那是不是LVGL这个双向链表只能用于uint8_t类型的数据?可是转念一想LVGL内部的定时器、任务都是基于这个双向链表实现的,肯定有我没有理解到的地方,uint8_t是啥,不就是一个字节吗?在计算机内存中的基本单位,也是硬件所能访问的最小单位,这里我们可以联想到任何数据类型都可以字节进行访问,那么怎么访问呢?答案:指针。可以看出链表类型中头尾指针都是lv_ll_node_t *也就是uint8_t *,这样就可以通过head和tail对任意类型的结点进行访问。
2、LVGL链表实现原理
上一节已经对LVGL双向链表的数据类型进行分析,接下来开始分析其实现原理。
2.1、双向链表初始化
双向链表初始化_lv_ll_init()函数,其定义如下:
该函数主要用于初始化一个双向链表,并通过传入参数lv_ll_ *ll_p返回已经初始化的双向链表句柄,这里要重点关注第二个参数node_size,顾名思义该参数表示的是结点所占字节的大小,但要特别说明一下这个node_size表示的只是结点的数据域大小,并没有包含next、prv指针域,这一点在后面分析结点插入时会详细说明。在函数内部对node_size进行了8字节或4字节的内存对齐,具体是8字节对齐还是4字节对齐跟具体的系统位数相关了(比如WIN32就是4字节,WIN64就是8字节)。
2.2、插入结点
通过分析LVGL插入一个结点我们才能真正理解其双向链表的实现原理以及巧妙之处,这里以尾插法的实现进行分析,即_lv_ll_ins_tail()函数,其定义如下图所示:
① 创建一个新的结点
使用lv_mem_alloc()进行动态申请,注意这里申请的内存大小是ll_p->n
_size + LL_NODE_META_SIZE,其中ll_p->n_size就是之前在初始化时传入的node_size,那么我们来看看LL_NODE_META_SIZE是多大,转到定义可以看到:
哈哈,果然是两个指针的大小,如果熟悉双向链表马上就可以推测出这两个指针对应的就是next、prev指针,那么我们可以得到下面的结点内存模型:
还有个问题就是为什么可以确定prev在前,next在后呢?答案可以在下面这两个宏定义中找到:
其中LL_PREV_P_OFFSET表示prev指针相对域结点首地址的偏移,同理LL_NEXT_P_OFFSET表示的是next指针相对于结点首地址的偏移。通过这个偏移地址应该可以很清楚的看出prev在next前面的位置吧。
然后我们可以得到如下的双向链表模型:
② 设置新结点的next
使用node_set_next()函数设置结点的next,因为尾插法,所以新结点的next为空,这里重点分析node_set_next()函数的实现,其定义如下所示:
可以看出该函数内部都是指针的操作,对于指针操作来说,使用内存变化来理解是最好不过了,该函数的内存变化过程如下:
最终内存0x00000024的值为NULL,这也符合我们的预期:尾插法新结点的next为NULL。这里也值得思考一下:为什么设置next指针为什么需要如此复杂?因为LVGL双向链表的结点数据域是由外部决定的,我们只能通过地址这个信息来访问,同样的结点类型为uint8_t,我们也是只能通过地址信息来进行访问,不通过结构体成员的方式来访问数据域、prev指针以及next指针。同时函数内部中出现了两个二级指针,他们的作用就是用来访问地址,如果我们直接对传入act、next这两个一级指针进行操作,只能改这两个指针变量保存的地址值,并不能对传入地址进行访问,所以需要借助二级指针来对传入地址进行访问。
③ 设置新结点的prev
使用node_set_prev()函数设置结点的next,同样这里重点分析node_set_prev()函数的实现,其定义如下所示:
可以看出node_set_prev()和node_set_next()内部实现几乎是一模一样的只是act8获取的prev指针的偏移,就这个差异。其内存变化如下:
最终内存0x00000040(n_new的prev指针)的值为0x00000008(n_prev)。
④ 设置链表尾结点的next
⑤ 更新链尾为新结点
实际上LVGL实现原理的核心就是对node_set_next()和node_set_prev两个函数的理解,掌握了这两个函数的实现剩下的就是对双向链表的理解了,相信学习过数据结构理解双向链表应该是小菜一碟了吧。所以剩下的头插法、删除结点这些就不再赘述。
3、LVGL链表应用实例
点击查看代码
#define STD_NAME_LEN_MAX 15
//学生信息类型
typedef struct StudentInfo StudentInfo_t;
struct StudentInfo
{
char name[STD_NAME_LEN_MAX];
int age;
int sex;
};
//学生信息表
StudentInfo_t std_table[] = {
{"ZhangSan", 23, 1},
{"LiSi", 25, 0},
{"WangWu", 26, 1},
};
void lv_ll_test(void)
{
StudentInfo_t* std;
lv_ll_t std_ll;
_lv_ll_init(&std_ll, sizeof(StudentInfo_t)); // 初始化std_ll链表
/* 遍历学生信息表,将学生信息添加到std_ll中 */
for (int i = 0; i < (sizeof(std_table) / sizeof(std_table[0])); i++)
{
std = (StudentInfo_t*)_lv_ll_ins_tail(&std_ll);
lv_snprintf(std->name, sizeof(std->name), std_table[i].name);
std->age = std_table[i].age;
std->sex = std_table[i].sex;
}
/* 遍历std_ll,验证学生信息是否正确添加到std_ll中 */
std = (StudentInfo_t*)_lv_ll_get_head(&std_ll);
while (std)
{
printf("name:%s age:%d sex:%d \n", std->name, std->age, std->sex);
std = (StudentInfo_t*)_lv_ll_get_next(&std_ll, std);
}
}
LVGL双向链表学习笔记的更多相关文章
- KPROCESS IDT PEB Ldr 《寒江独钓》内核学习笔记(3)
继续上一篇(2)未完成的研究,我们接下来学习 KPROCESS这个数据结构. 1. 相关阅读材料 <深入理解计算机系统(原书第2版)> 二. KPROCESS KPROCESS,也叫内核进 ...
- 《C++ Primer Plus》学习笔记10
<C++ Primer Plus>学习笔记10 <<<<<<<<<<<<<<<<<&l ...
- Java学习笔记——浅谈数据结构与Java集合框架(第一篇、List)
横看成岭侧成峰,远近高低各不同.不识庐山真面目,只缘身在此山中. --苏轼 这一块儿学的是云里雾里,咱们先从简单的入手.逐渐的拨开迷雾见太阳.本次先做List集合的三个实现类的学习笔记 List特点: ...
- Redis学习笔记(三)Redis支持的5种数据类型的总结
继续Redis学习笔记(二)来说说剩余的三种数据类型. 三.列表类型(List) 1.介绍 列表类型可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的一段片段.列表类型内部是 ...
- MOOS学习笔记1——HelloWorld
MOOS学习笔记1--HelloWorld 例程 /* * @功能:通讯客户端的最简单程序,向MOOSDB发送名为"Greeting" * 数据"Hello", ...
- 学习笔记 07 --- JUC集合
学习笔记 07 --- JUC集合 在讲JUC集合之前我们先总结一下Java的集合框架,主要包含Collection集合和Map类.Collection集合又能够划分为LIst和Set. 1. Lis ...
- (转)live555学习笔记-UsageEnvironment和TaskScheduler
2011-12-6阅读1264 评论1 一直想学习流媒体服务器的设计,这几天有点时间,看了一下live555的源代码.live555是一个开源的跨平台流媒体服务器,使用编程语言是C++.将现阶段学习笔 ...
- [Golang学习笔记] 08 链表
链表(Linked list)是一种常见数据结构,但并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针. 由于不必须按顺序存储,链表在插入的时候可以达到O(1),比顺序表快得多,但是查 ...
- <<C++标准程序库>>中的STL简单学习笔记
0. 内容为个人学习笔记, 仅供参考, 如有错漏, 欢迎指正! 1. STL中的所有组件都是由模板构成的, 所以其元素可以是任意型别的. 组件有: - 容器: 管理某类对象的集合. 不同的容器有各自的 ...
- java集合类学习笔记之LinkedHashMap
1.简述 LinkedHashMap是HashMap的子类,他们最大的不同是,HashMap内部维护的是一个单向的链表数组,而LinkedHashMap内部维护的是一个双向的链表数组.HashMap是 ...
随机推荐
- 1. Mybatis 简介
1. Mybatis历史 MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code.随着 ...
- Kafka中的消费者Offset
消费者位移 每个 consumer 实例都会为它消费的分区维护属于自己的位置信息来记录当前消费了多少条消息.这在 Kafka 中有一个特有的术语:位移(offset). 相比较将offset保存在服务 ...
- Unity的Undo:详解解析与实用案例
Unity Undo详解 在Unity中,Undo是一个非常重要的功能,它可以让开发者在编辑器中进行操作时,随时撤销之前的操作,从而避免不必要的错误.本文将详细介绍Unity Undo实现原理和使用方 ...
- Elasticsearch日常开发
2020-08-12 14:51:37 每次遇到ES开发,一般都是查询es里面的数据,今天我教大家一个简单的es的查询.废话不多说,直接上代码. 在pom文件中引入 <dependency> ...
- Hexo博客yilia主题文章添加目录
参考文章 添加目录的文章有一些是自己添加css文件和修主题配置 作者也更新了文章大体目录的功能 打开配置文件themes/yilia/_config.yml 你可以选择toc设置为1 或者2 toc: ...
- Asp-Net-Core学习笔记:单元测试和集成测试
前言 我在使用 AspNetCore 的这段时间内,看了很多开源项目和博客,发现各种 .Net 体系的新技术很多人都有关注和使用,但却很少有人关注测试. 测试是软件生命周期中的一个非常重要的阶段,对于 ...
- Python数据分析易错知识点归纳(六):机器学习
六.机器学习 分类和聚类的区别 分类是有监督学习,聚类是无监督学习 分类算法用于预测新样本,聚类用于理解已知数据 标准化/归一化 type_se_num = type_se[type_se!= 'ob ...
- 飞桨paddlespeech语音唤醒推理C定点实现
前面的文章(飞桨paddlespeech语音唤醒推理C浮点实现)讲了飞桨paddlespeech语音唤醒推理的C浮点实现.但是嵌入式设备通常CPU频率低和memory小,在嵌入式设备上要想流畅的运行语 ...
- linux 查看进程使用的内存大小
你可以使用 ps 命令结合 grep 命令来查看进程使用的内存大小.以下是示例代码: ps aux | grep <进程名> 这个命令会列出所有匹配 <进程名> 的进程,并显示 ...
- Django: django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "game-detail".
错误原因:在序列化时,使用了参数`url`,但在`urls.py`文件中,没有使用命名 # urls.py from django.urls import path from RESTSerializ ...