首页 > 其他分享 >LVGL双向链表学习笔记

LVGL双向链表学习笔记

时间:2023-10-08 23:12:06浏览次数:30  
标签:std lv 结点 ll next 链表 双向 LVGL

LVGL双向链表学习笔记

1、LVGL链表数据类型分析

对于LVGL双向链表的使用,我们需要关注lv_ll.h和lv_ll.c两个文件,其中lv_ll.h里面包含了链表结构类型定义,以及相关API的声明,首先介绍链表的结构类,如下图所示:
image
一开始看到这个类型声明我是懵的,怎么链表的一个结点的类型是uint8_t,那是不是LVGL这个双向链表只能用于uint8_t类型的数据?可是转念一想LVGL内部的定时器、任务都是基于这个双向链表实现的,肯定有我没有理解到的地方,uint8_t是啥,不就是一个字节吗?在计算机内存中的基本单位,也是硬件所能访问的最小单位,这里我们可以联想到任何数据类型都可以字节进行访问,那么怎么访问呢?答案:指针。可以看出链表类型中头尾指针都是lv_ll_node_t *也就是uint8_t *,这样就可以通过head和tail对任意类型的结点进行访问。

2、LVGL链表实现原理

上一节已经对LVGL双向链表的数据类型进行分析,接下来开始分析其实现原理。

2.1、双向链表初始化

双向链表初始化_lv_ll_init()函数,其定义如下:
image
该函数主要用于初始化一个双向链表,并通过传入参数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()函数,其定义如下图所示:
image
① 创建一个新的结点
使用lv_mem_alloc()进行动态申请,注意这里申请的内存大小是ll_p->n
_size + LL_NODE_META_SIZE,其中ll_p->n_size就是之前在初始化时传入的node_size,那么我们来看看LL_NODE_META_SIZE是多大,转到定义可以看到:
image
哈哈,果然是两个指针的大小,如果熟悉双向链表马上就可以推测出这两个指针对应的就是next、prev指针,那么我们可以得到下面的结点内存模型:
image
还有个问题就是为什么可以确定prev在前,next在后呢?答案可以在下面这两个宏定义中找到:
image
其中LL_PREV_P_OFFSET表示prev指针相对域结点首地址的偏移,同理LL_NEXT_P_OFFSET表示的是next指针相对于结点首地址的偏移。通过这个偏移地址应该可以很清楚的看出prev在next前面的位置吧。
然后我们可以得到如下的双向链表模型:
image
② 设置新结点的next
使用node_set_next()函数设置结点的next,因为尾插法,所以新结点的next为空,这里重点分析node_set_next()函数的实现,其定义如下所示:
image
可以看出该函数内部都是指针的操作,对于指针操作来说,使用内存变化来理解是最好不过了,该函数的内存变化过程如下:
image
最终内存0x00000024的值为NULL,这也符合我们的预期:尾插法新结点的next为NULL。这里也值得思考一下:为什么设置next指针为什么需要如此复杂?因为LVGL双向链表的结点数据域是由外部决定的,我们只能通过地址这个信息来访问,同样的结点类型为uint8_t,我们也是只能通过地址信息来进行访问,不通过结构体成员的方式来访问数据域、prev指针以及next指针。同时函数内部中出现了两个二级指针,他们的作用就是用来访问地址,如果我们直接对传入act、next这两个一级指针进行操作,只能改这两个指针变量保存的地址值,并不能对传入地址进行访问,所以需要借助二级指针来对传入地址进行访问。
③ 设置新结点的prev
使用node_set_prev()函数设置结点的next,同样这里重点分析node_set_prev()函数的实现,其定义如下所示:
image
可以看出node_set_prev()和node_set_next()内部实现几乎是一模一样的只是act8获取的prev指针的偏移,就这个差异。其内存变化如下:
image
最终内存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);
    }

}

标签:std,lv,结点,ll,next,链表,双向,LVGL
From: https://www.cnblogs.com/wangfeng-98/p/17707184.html

相关文章

  • 【UVA 12657】Boxes in a Line 题解(静态双向链表)
    您在编号为1的表格上有n个方框。n从左到右。您的任务是模拟4命令类型:•1XY:将框X向左移动到Y(如果X已经是Y的左侧,则忽略此项)•2XY:将框X向右移动到Y(如果X已经是Y的右侧,则忽略此项)•3XY:交换盒X和Y•4:反转整条线路。命令保证有效,即X不等于Y。例如,如果n=6,在执行114之后,该行......
  • 141. 环形链表
    给你一个链表的头节点 head ,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从0开始)。注意:pos 不作为参数进行传递 。仅仅是为了标......
  • 03-链表常见六个操作
    我的想法:问题:正确思路:适用场景:代码//题目:/**学习到:*写代码过程中:*1.类成员变量使用'_',变量名前后都可*2.要弄清出index(第几个元素,从0开始)与_size(链表中元素个数)的意义*2.*代码逻辑:*1.写代码之前,一定要弄清出目的,以及实现他需要的东西,条件*2.操作前......
  • 01-建立静态链表
    一、实现思路1、声明一个结构体类型,成员有数据类型和指针变量next;2、将第一个结点的起始地址赋给头指针head,将第二个结点的起始地址赋给第一个结点的next成员,将第三个结点的起始地址赋值给第二个结点的next成员。第三个结点的next成员赋值为NULL,这样就形成了链表。二、程序设计......
  • Leetcode刷题83. 删除排序链表中的重复元素
    给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。示例1:输入:head=[1,1,2]输出:[1,2]示例2:输入:head=[1,1,2,3,3]输出:[1,2,3] 提示:链表中节点数目在范围 [0,300] 内-100<=Node.val<=100题目数......
  • 01 链表
    链表的基本实现与应用,差不多可以了学生通讯录管理系统#include<stdio.h>#include"stdlib.h"#include"string.h"#defineMAX10//链表typedefstructNode{intid,telenum;charname[20];intlength;structNode*next;}Node,*LinkList;......
  • 基础数据结构:数组实现的单链表(静态链表)、双链表
    1、单链表(静态链表)以AcWing.826为例,题目要求如下:实现一个单链表,链表初始为空,支持三种操作:向链表头插入一个数;删除第k个插入的数后面的数;在第k个插入的数后插入一个数。现在要对该链表进行M次操作,进行完所有操作后,从头到尾输出整个链表。注意:题目中第k个插入的数并不是指当......
  • 408---十字链表法
    一、十字链表法画法参考: https://www.bilibili.com/video/BV1hV411t7SC/?spm_id_from=333.337.search-card.all.click&vd_source=87f7ad8544d4c3ad070c5c2ff28b7698方法就是先画出邻接表然后从头接点开始连接其相应的弧结点,如图,V1头节点的第二个数据项去连接弧结点的第三个......
  • Vue双向数据绑定原理-下
    Vue双向数据绑定原理-下这一篇文章主要讲解Vue双向数据绑定的原理,主要是通过Object.defineProperty()来实现的,这里我们手写Vue双向数据绑定的原理。首先我提出一个需求,我的需求是,快速监听对象中所有属性的变化。首先得要有一个对象,对象的定义代码如下:<script>letobj={......
  • Vue双向数据绑定原理-中
    defineProperty方法defineProperty除了可以动态修改/新增对象的属性以外,还可以在修改/新增的时候给该属性添加get/set方法,从而实现数据劫持。definePropertyget/set方法特点只要通过defineProperty给某个属性添加了get/set方法,那么以后只要获取这个属性的值就会自动调用g......