1. 单链表与双链表

单链表: 由一个个节点(node)组成,每个节点都有指向下一个节点的指针。节点的连接方向是单向的,节点之间用指针连起来,所有结构体类型可以不一样,链表的大小也可以动态变化。但是不能随机访问数据,只能遍历。

双链表:由一个个节点(node)组成,每个节点都有指向下一个节点的指针,每个节点都有一个指向上一个节点的指针。所以节点的连接方向是双向的,节点之间用指针连起来,所有结构体类型可以不一样,链表的大小也可以动态变化。但是不能随机访问数据,只能遍历。

可以得出结论:单链表和双链表主要的区别就是双链表多了一个指向上一节点的指针。

循环链表:普通的节点(node) 的 头节点的prev指向 null,尾节点的next 指向 null。循环链表的头节点的 prev 指向 尾节点,尾节点的 next 的 指向头节点。简单理解就是头尾相连。

重点 : RT-Thread 的双链表实现的就是循环链表。

2. 怎么使用双链表

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. */

结构体有两个指针分别指向下一个节点和上一个节点。

  1. 初始化链表
rt_list_t list;
rt_list_init(&list);
  1. 在节点(list)后面插入一个新的节点
rt_list_t nlist1;
rt_list_insert_after(&list,&nlist1);
  1. 在节点(list)前面插入一个新的节点
rt_list_t nlist2;
rt_list_insert_before(&list,&nlist2);
  1. 移除一个节点
rt_list_t nlist3;
rt_list_remove(&nlist3);
  1. 判断链表是否为空
rt_list_isempty(&list);
  1. 求单链表的长度
rt_list_len(&list);
  1. 获取链表所在结构体的类型
rt_list_entry(node(节点), type(结构体类型), 链表所在结构体成员的名字)
  1. 遍历链表
rt_list_for_each(pos(节点), head(头节点))
  1. 安全遍历链表
rt_list_for_each_safe(pos, n, head)
  1. 遍历链表中的结构体成员
    ···
    rt_list_for_each_entry(node(节点), struct (结构体), list(链表所在结构体成员中的名字))
    ···
  2. 安全遍历链表中的结构体成员
 rt_list_for_each_entry_safe(pos, n, head, member)
  1. 第一个节点的结构体
rt_list_first_entry(ptr, type, member)

3. 双链表的实现

  1. 初始化链表
rt_inline void rt_list_init(rt_list_t *l)
{l->next = l->prev = l;
}

初始化的时候 nextprev 都指向了 L, 即实现了首尾相连的循环链表

  1. 在节点后面插入一个节点
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;
}
  1. 在节点前面插入一个节点
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;
}
  1. 移除一个节点
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;
}
  1. 判断节点是否为空
rt_inline int rt_list_isempty(const rt_list_t *l)
{return l->next == l;
}

如果 next 指向了本身即判断链表为空。

  1. 求链表的长度
rt_inline unsigned int rt_list_len(const rt_list_t *l)
{unsigned int len = 0;const rt_list_t *p = l;while (p->next != l){p = p->next;len ++;}return len;
}

通过 next 是否指向链表的头节点,来确定是否遍历完链表。

  1. 获取链表所在的结构体
#define rt_list_entry(node, type, member) \rt_container_of(node, type, member)

具体的分析可以查看单链表的分析

  1. 遍历链表
#define rt_list_for_each(pos, head) \for (pos = (head)->next; pos != (head); pos = pos->next)
  1. 遍历边表并获取结构体
#define rt_list_for_each_entry(pos, head, member) \for (pos = rt_list_entry((head)->next, typeof(*pos), member); \&pos->member != (head); \pos = rt_list_entry(pos->member.next, typeof(*pos), member))
  1. 安全遍历链表
#define rt_list_for_each_safe(pos, n, head) \for (pos = (head)->next, n = pos->next; pos != (head); \pos = n, n = pos->next)
  1. 安全遍历链表并获取结构体
#define rt_list_for_each_entry_safe(pos, n, head, member) \for (pos = rt_list_entry((head)->next, typeof(*pos), member), \n = rt_list_entry(pos->member.next, typeof(*pos), member); \&pos->member != (head); \pos = n, n = rt_list_entry(n->member.next, typeof(*n), member))

重点
rt_list_for_eachrt_list_for_each_safe

    for (pos = (head)->next; pos != (head); pos = pos->next)

head 开始遍历,如果这个时候改变了 pos->next,指向了 NULL, 那么这里将会导致死循环

#define rt_list_for_each_safe(pos, n, head) \for (pos = (head)->next, n = pos->next; pos != (head); \pos = n, n = pos->next)

rt_list_for_each_safe 多了一个参数 n , n 用来缓存 pos->next ,那么就可以避免死循环的情况

所以只做遍历操作用 rt_list_for_each,涉及到对节点的移除操作是就需要使用 rt_list_for_each_safe

4. 最后

作为一名合格的程序员一定要熟练的掌握链表,RT-Thread 的内核中提供了很方便的 API。RT-Thread 的内核源码中也是通过链表来实现了所有 object 连在了一起,掌握链表后,对分析,学习 RT-Thread 的思想一定会事半功倍。

RT-Thread 隐藏的宝藏之双链表相关推荐

  1. rt thread studio使用QBOOT和片外flash实现OTA升级

    我们这里要使用单片机外部flash作为OTA的下载分区,外部flash硬件连接关系 PB3-->SPI3_CLK PB4-->SPI3_MISO PB5-->SPI3_MOSI PE ...

  2. 基于rt thread smart构建EtherCAT主站

    我把源码开源到到了gitee,https://gitee.com/rathon/rt-thread-smart-soem 有兴趣的去可以下载下来跑一下 软件工程推荐用vscode 打开.rt thre ...

  3. Go 学习笔记(80)— Go 标准库 container/list(单链表、双链表)

    列表是一种非连续存储的容器,由多个节点组成,节点通过一些变量记录彼此之间的关系.列表有多种实现方法,如单链表.双链表等. ​ 在 Go 语言中,将列表使用 container/list 包来实现,内部 ...

  4. 利用“哨兵”“实现双链表

    利用"哨兵""实现双链表 下面的代码用一个"哨兵"实现双链表,感觉很简洁,中间也有点绕,暂时实现,供学习之用 static Node list_han ...

  5. 一级指针和二级指以及(void**)在双链表中的应用

    因为函数参数是按值传递的,所以要想改变变量,必须传递地址. 二级指针实际上就是指针变量的地址,如果传递二级指针,函数声明必须写**. (void**)&必须是本质上就是指针变量的地址才可以做这 ...

  6. 结构体的两种声明方式:堆上和栈上以及在双链表的应用

    在看<算法精解:C语言描述>的双链表chtbl和redis的双链表adlist.c发现代码思路基本是一致的. 但是,对于链表的初始化却不一样 1.<算法精解:C语言描述>风格 ...

  7. 【数据结构】双链表的应用

    1.设计一个算法,在双链表中值为y的结点前面插入一个值为x的新结点,即使得值为x的新结点成为值为y的结点的前驱结点. 2.设计一个算法,将一个双链表改建成一个循环双链表. #include <s ...

  8. 【数据结构】双链表的实现(C语言)

    双链表中的结点包括3个域,一个是存放数据信息的info域,另两个是指阵域,这里用llink和rlink表示,llink指向它的前驱结点,rlink指向它的后继结点. 双链表要掌握以下基本操作: 1.创 ...

  9. 链表问题2——在双链表中删除倒数第K个节点

    题目 实现一个函数,可以删除双链表中倒数第K个节点. 要求 如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1). 思路 双链表的思路与前一篇文章单链表的思路基本一致,注意last指针 ...

最新文章

  1. Lync 手机客户端登录过程
  2. PHP memcache实现消息队列实例
  3. 简单记录js中的this关键字
  4. 【收藏】HUE配置HDFS
  5. java 邮件模板_Spring Boot 优雅地发送邮件
  6. a标签去下划线或文字添加下修饰_HTML标签:字体标签和超链接
  7. [TJOI2011] 卡片(网络流 + 质因子优化建图)
  8. Windows下使用net user命令管理账户
  9. apache apr介绍
  10. 如何根据原理图画封装_如何在短时间内,吃透三极管工作原理,开关导通和封装外形知识点?...
  11. 阅读笔记11-孤独后厂村:30万互联网人跳不出的中国硅谷
  12. ubuntu安装python百度经验_如何在Ubuntu 20.04上安装Python 3.9(含python编译安装和使用Apt命令安装)...
  13. 如何把数据库从sql变成mysql_(转)如何将数据库从SQL Server迁移到MySQL
  14. 由浅入深,汇编语言详解与二进制漏洞初阶
  15. 如何导出久其报表所有数据_久其报表不能传输怎么办?
  16. 几个简单的小功能,能提高微信群活跃度?
  17. T-test检验中的P,α理解
  18. 雷观(十八):我的世界观
  19. Win10 LTSC版如何安装应用商店Microsoft Store
  20. python调用pandas保存excel

热门文章

  1. fyssqzh7446
  2. Java和Android构建工具Gradle深入了解
  3. ClickHouse宝典
  4. Windows server2003时间同步
  5. 易语言快递单号查询工具
  6. 在Foxmail中添加阿里云企业邮箱账号
  7. 完成千万元A轮融资,小象生活能否成为折扣界的“永辉”?
  8. WEB3.0定义与未来发展趋势
  9. 正则表达式 /\((.*)\)/ 的含义
  10. html float属性6,CSS float 浮动属性