RT-Thread系列--双链表分析

news2024/11/18 19:47:29

一、目的

学习过C语言的同学应该都知道几种常用的数据结构,例如数组、单链表、双链表等。

每种数据结构都有其特点和应用场景,本篇就结合RT-Thread源码分析一下其双链表实现细节和特点。

那什么是双链表呢,这边简单解释一下帮助大家理解。

通过双链表中的链表头可以直接访问到A,因为A是其下一个节点;也可以直接访问到C,因为C是其上一个节点(也可以认为是链表的最后一个节点)。

链表上的每一个节点都能访问到其上一个节点或者下一个节点,例如B就可以直接定位到其前置节点A或者后置节点C。

这样的数据结构就是双链表,每个节点都有前置和后置节点指针。

二、介绍

刚刚学习数据结构的同学如果自己实现双链表一般会这样设计:

struct my_list {
    struct my_list *prev;
    struct my_list *next;
    void *my_data;
};

其中prev/next指针指向前后节点,my_data字段存放特定的数据结构。

但是这种设计有一个最令人诟病的问题,以后如果要复用此双链表就必须重新再写一遍;所以这样的双链表设计只能说能用但不是最好用的,那有没有更好的设计呢?

如果你是一个资深开发人员,你肯定知道linux里面有个最优的设计,大家可以去下面的链接去深入学习。

list.h - include/linux/list.h - Linux source code (v6.1.5) - Bootlin


其实RT-Thread的作者估计也是一个深度linux开发者(很多代码设计亮点都是从linux借鉴过来的)。

下面我们正式介绍一下RT-Thread中的双链表结构。

首先我们先来看一下链表头的设计

/**
 * Double List structure
 */
struct rt_list_node
{
    struct rt_list_node *next;                          /**< point to next node. */
    struct rt_list_node *prev;                          /**< point to prev node. */
};
typedef struct rt_list_node rt_list_t;                  /**< Type for lists. */

链表头其实很简单,里面就只有两个字段prev/next指针,分别指向链表中的第一个和最后一个节点。

链表头需要初始化,prev/next指针都指向自身。

/**
 * @brief initialize a list
 *
 * @param l list to be initialized
 */
rt_inline void rt_list_init(rt_list_t *l)
{
    l->next = l->prev = l;
}

如果prev或者next指向自身则代表链表为空

rt_inline int rt_list_isempty(const rt_list_t *l)
{
    //return l->next == l;
    return l->prev = l;
}

空链表

从上面的代码片段中细心的小伙伴可能会发现链表头和链表节点使用的是同样的数据结构,大家是不是很疑惑(链表节点中一般都要包含特定的业务数据信息)。

其实大家可以这么理解,链表头就是一个没有数据块的数据节点。

上图是一个拥有3个数据节点A/B/C的双链表,A是第一个节点,C是最后一个节点。

如果某个节点从链表中去除,那么这个节点就变成孤立节点

孤立节点的prev/next都指向自身。

如果我们将节点B从链表中取下,那么此时的链表变成

从链表中删除一个节点的代码如下

/**
 * @brief remove node from list.
 * @param n the node to remove from the list.
 */
rt_inline void rt_list_remove(rt_list_t *n)
{
    n->next->prev = n->prev;
    n->prev->next = n->next;

    n->next = n->prev = n;
}

只需要将被删除节点的前一个节点的next指针指向被删除节点下一个节点;下一个节点的prev指针指向被删除节点的上一个节点。


在某个节点处可以在其前后插入一个新的节点

rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{
    l->next->prev = n;
    n->next = l->next;

    l->next = n;
    n->prev = l;
}

/**
 * @brief insert a node before a list
 *
 * @param n new node to be inserted
 * @param l list to insert it
 */
rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{
    l->prev->next = n;
    n->prev = l->prev;

    l->prev = n;
    n->next = l;
}

链表的遍历,也就是从链表头依次编译每个节点,直到某个节点的next指针指向的是链表头节点则结束

/**
 * rt_list_for_each - iterate over a list
 * @pos:    the rt_list_t * to use as a loop cursor.
 * @head:   the head for your list.
 */
#define rt_list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

链表的遍历的安全版本,在遍历过程中可以新增或者删除一个节点

/**
 * rt_list_for_each_safe - iterate over a list safe against removal of list entry
 * @pos:    the rt_list_t * to use as a loop cursor.
 * @n:      another rt_list_t * to use as temporary storage
 * @head:   the head for your list.
 */
#define rt_list_for_each_safe(pos, n, head) \
    for (pos = (head)->next, n = pos->next; pos != (head); \
        pos = n, n = pos->next)

实现细节就是将当前被遍历的节点的下一个节点先保存起来


如何使用该链表结构呢?

假如我们有这样一个数据结构

struct foo {
    int a;
    int b;
};

那么我们可以重新定义我们的foo结构,将rt_list_t作为一个双链表钩子放置在我们定义的结构体中。

struct foo {
    rt_list_t list;
    int a;
    int b;
};

重要知识点:通过结构体内的字段地址获取结构体首地址

/**
 * @brief get the struct for this entry
 * @param node the entry point
 * @param type the type of structure
 * @param member the name of list in structure
 */
#define rt_list_entry(node, type, member) \
    rt_container_of(node, type, member)

关于container_of的讲解请看《container_of(ptr, type, member)详解》。

至此,我们基本讲解完RT-Thread的双链表结构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/163695.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

KubeSphere 开源社区 2022 年度回顾与致谢

2022 年&#xff0c;国内的云原生技术生态日趋完善&#xff0c;细分技术项目也不断涌现&#xff0c;形成了完整的支撑应用云原生化的全生命周期技术体系。基础设施即代码、微服务、Serverless 等技术&#xff0c;促使基础设施资源向更加灵活弹性、自动化方向发展。而开源生态也…

重装系统如何设置u盘启动为第一启动项

如何设置u盘启动为第一启动项呢?将U盘设为第一启动项&#xff0c;是使用U盘装机工具重装系统的重要步骤之一&#xff0c;很多网友不清楚怎么操作&#xff0c;下面小编就分享下设置u盘启动为第一启动项的方法。 工具/原料&#xff1a; 系统版本&#xff1a;win7家庭版 品牌型…

【openGauss实战4】数据类型解读

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&#x1f61…

一文教会你巧用设计模式重构项目

文章目录一、设计模式总览二、模板方法模式案例三、策略模式案例四、支付改造4.1 思路分析4.2 实现图解&#xff1a;4.3 代码实现&#xff1a;4.4 效果演示4.5 如何扩展本文参考自12.29日尚硅谷雷神的 巧妙使用设计模式重构项目 一、设计模式总览 总体分类 不同时期选择不同的…

Python批量检索论文被引用数量源码,利用百度学术网页版来批量检索论文的被引用数量源码

论文被引用数搜索 利用百度学术网页版来检索一个文件夹中的所有论文的被引用数量。 完整代码下载地址&#xff1a;Python批量检索论文被引用数量源码 依赖有beautifulsoup库、regex正则表达式库。 使用方法 主程序为fileWalk.py。 修改程序中workPath值为文件夹绝对路径&am…

基于碰撞传感器的自动导航车系统设计

1、内容简介略635-可以交流、咨询、答疑2、内容说明略随着世界各国对科学技术的重视&#xff0c;各类高科技技术突飞猛进&#xff0c;人类逐步进入人工智能时代。而在这些高科技技术的背后&#xff0c;自动导航小车作为无人驾驶小车的一种类型备受关注。它的主要优点是不需要人…

冰冰学习笔记:智能指针

欢迎各位大佬光临本文章&#xff01;&#xff01;&#xff01; 还请各位大佬提出宝贵的意见&#xff0c;如发现文章错误请联系冰冰&#xff0c;冰冰一定会虚心接受&#xff0c;及时改正。 本系列文章为冰冰学习编程的学习笔记&#xff0c;如果对您也有帮助&#xff0c;还请各位…

vite 在proxy代理中更改headers

vite 在proxy代理中更改headers 平时我们在对接接口时&#xff0c;我们都是配置代理解决跨域问题 proxy: {^/api: {target: envConfig.VITE_APP_BASE_URL,changeOrigin: true,rewrite: (path) > path.replace(/^/api/, ),}} 某天你明明配置好了代理&#xff0c;浏览器还是会…

嵌入式Linux-守护进程

1.守护进程 1.1 何为守护进程 守护进程&#xff08;Daemon&#xff09;也称为精灵进程&#xff0c;是运行在后台的一种特殊进程&#xff0c;它独立于控制终端并且周期性地执行某种任务或等待处理某些事情的发生&#xff0c;主要表现为以下两个特点&#xff1a; 长期运行。 守…

Java中的多线程安全问题

目录 一、什么是线程安全&#xff1f; 二、线程不安全的原因 2.1 从底层剖析count的操作 2.2 线程不安全的原因总结 2.3 JVM内存模型&#xff08;JMM&#xff09; 三、synchronized 关键字-监视器锁monitor lock 3.1 如何加锁&#xff08;Synchronized用法和特性&#xff…

【sklearn】模型融合_堆叠法

Stacking参数含义1. 工具库 & 数据2. 定义交叉验证函数2.1 对融合模型2.2 对单个评估器3. 定义个体学习器和元学习器3.1 个体学习器3.2 元学习器4. 评估调整模型5. 元学习器的特征矩阵5.1 特征矩阵两个问题 & Stacking5.2 StackingClassfier\Regressor参数cv - 解决样本…

C语言 动态通讯录实现(附源码)

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; 前言&#xff1a; 上期博客写了静态通讯录并且答应给大家写一个动态版&#xff0c;这不&#xff0c;它来了&#xff1a; 1.动态版与静态版的区别 静态版的内存空间开辟大小是固定的&#xff0c;放了预期的最…

Leetcode 剑指 Offer II 010. 和为 k 的子数组

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个整数数组和一个整数 k &#xff0c;请找到该数组中和为…

MTBF是什么意思?交换机做MTBF有什么要求?MTTF、MTBF和MTTR的区别是什么?

MTBF&#xff0c;即平均故障间隔时间&#xff0c;英文全称是“Mean Time Between Failure”。是衡量一个交换机的可靠性指标。单位为“小时”。它反映了交换机的时间质量&#xff0c;是体现交换机在规定时间内保持功能的一种能力。具体来说&#xff0c;是指相邻两次故障之间的平…

【考研】2020-Part A 作文(英一)

可搭配以下链接一起学习&#xff1a; 【考研】2018-Part B 作文&#xff08;英一&#xff09;_住在阳光的心里的博客-CSDN博客 目录 一、2020 Part A &#xff08;一&#xff09;题目及解析 &#xff08;二&#xff09;优秀范文 &#xff08;三&#xff09;参考译文 &a…

Ansible playbook 讲解与实战操作

文章目录一、概述二、playbook 核心元素三、playbook 语法&#xff08;yaml&#xff09;1&#xff09;YAML 介绍1、YAML 格式如下2、playbooks yaml配置文件解释3、示例2&#xff09;variables 变量1、facts:可以直接调用2、用户自定义变量3、通过roles传递变量4、 Host Invent…

LINUX---文件

目录第一部分&#xff1a;文件编程一.打开/创建文件二.文件的写入操作三.文件的读取四.文件的光标应用&#xff1a;计算文件的大小第二部分&#xff1a;文件操作原理&#xff1a;一.文件描述符静态文件和动态文件第三部分&#xff1a;文件编程小应用1.实现CP命令2.修改文件3.写…

安卓玩机搞机技巧综合资源-----修改rom 制作rom 解包rom的一些问题解析【二十一】

接上篇 安卓玩机搞机技巧综合资源------如何提取手机分区 小米机型代码分享等等 【一】 安卓玩机搞机技巧综合资源------开机英文提示解决dm-verity corruption your device is corrupt. 设备内部报错 AB分区等等【二】 安卓玩机搞机技巧综合资源------EROFS分区格式 小米红…

【Vue笔记】Vue组件的创建、使用以及父子组件数据通信常见的几种方式

这篇文章&#xff0c;主要介绍Vue组件的创建、使用以及父子组件数据通信常见的几种方式。 目录 一、Vue组件的使用 1.1、局部组件 1.2、全局组件 1.3、动态组件&#xff08;组件动态切换&#xff09; 1.4、缓存组件 &#xff08;1&#xff09;如何缓存组件 &#xff08;…

微服务技术--Nacos与Eureka

eureka注册中心 远程调用的问题 消费者该如何获取服务提供者具体信息&#xff1f; 服务提供者启动时向eureka注册自己的信息eureka保存这些信息消费者根据服务名称向eureka拉取提供者信息 如果有多个服务提供者&#xff0c;消费者该如何选择&#xff1f; 服务消费者利用负载均…