数据结构 | TencentOS-tiny中的单向链表的实现及使用
Posted Mculover666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构 | TencentOS-tiny中的单向链表的实现及使用相关的知识,希望对你有一定的参考价值。
1. 什么是单向链表
链表全称叫做链式存储线性表(对应的,顺序存储线性表叫做数组)。
单向链表是最简单的一种链表,每个链表只包含一个指针域,所以称为单链表。单链表分为两种:不带有头结点的单链表,和带有头结点的单链表。
① 不带有头结点的单链表如图所示,头指针直接指向第一个有效结点:
不带头结点的链表有个缺点:删除第一个结点的操作和其它结点不相同。
② 为了解决这个缺陷,在第一个有效结点前放置一个头结点,头结点不存放有效数据,只是为了操作链表方便,就变为了带有头结点的单链表,如图:
接下来是四个链表中重要的概念:
- 头指针:指向第一个结点的指针;
- 头结点:为了链表操作方便设立,数据域不存放有效数据,指针域指向第一个有效结点;
- 有效结点:数据域存放有效数据,指针域指向下一个有效结点;
- 尾结点:数据域存放有效数据,指针域为空,表示链表结束。
2. 双向循环链表的实现
TencentOS-tiny中的单向链表实现在tos_slist.h
中。
2.1. 节点实现
节点数据结构的实现如下:
typedef struct k_slist_node_st {
struct k_slist_node_st *next;
} k_slist_t;
2.2. 单向链表初始化
链表初始化的实现如下:
__API__ __STATIC_INLINE__ void tos_slist_init(k_slist_t *slist)
{
slist->next = K_NULL;
}
2.3. 单链表的插入
单链表由于只有后向指针,所以只能在已有结点之后插入新的结点。
假设有一个新的有效结点p,想要插入到有效节点s之后,如图:
插入方法非常简单,如下(不能反了):
① 将p指向s之后的结点
② 将s指向p
用代码实现如下:
p->next = s->next;
s->next = p;
TencentOS-Tiny中还提供了在某个结点之前插入新结点的函数:
__API__ __STATIC_INLINE__ void tos_slist_insert(k_slist_t *next_node, k_slist_t *new_node, k_slist_t *slist)
{
if (!next_node) {
tos_slist_add_tail(new_node, slist);
return;
}
while (slist->next) {
if (slist->next == next_node) {
slist->next = new_node;
new_node->next = next_node;
}
slist = slist->next;
}
}
除了这个基本的API之外,tencentOS-tiny还提供了两个插入的API,分别是链表头部插入和链表尾部插入:
__API__ __STATIC_INLINE__ void tos_slist_add_head(k_slist_t *node, k_slist_t *slist)
{
node->next = slist->next;
slist->next = node;
}
__API__ __STATIC_INLINE__ void tos_slist_add_tail(k_slist_t *node, k_slist_t *slist)
{
node->next = K_NULL;
while (slist->next) {
slist = slist->next;
}
tos_slist_add_head(node, slist);
}
2.4. 单链表的删除
单链表删除节点的操作更为简单,前继结点将钩子断开,指向后继结点即可:
TencentOS-Tiny中的代码实现如下:
__API__ __STATIC_INLINE__ void tos_slist_del(k_slist_t *node, k_slist_t *slist)
{
while (slist->next) {
if (slist->next == node) {
slist->next = node->next;
break;
}
slist = slist->next;
}
}
除此之外,TencentOS-Tiny还提供了删除链表第一个有效结点和尾结点的API:
__API__ __STATIC_INLINE__ k_slist_t *tos_slist_del_head(k_slist_t *slist)
{
k_slist_t *head;
if (!slist->next) {
return K_NULL;
}
head = slist->next;
slist->next = head->next;
head->next = K_NULL;
return head;
}
__API__ __STATIC_INLINE__ k_slist_t *tos_slist_del_tail(k_slist_t *slist)
{
while (slist->next) {
if (!slist->next->next) {
return tos_slist_del_head(slist);
}
slist = slist->next;
}
return K_NULL;
}
2.5. 单链表的读取
要在单链表中读取某个结点数据,只需要遍历即可,遇到要找的结点停止或者遇到尾结点停止即可。
TencentOS-Tiny中提供了遍历单链表的宏:
#define TOS_SLIST_FOR_EACH(curr, slist) \\
for (curr = (slist)->next; curr; curr = curr->next)
① 基于该遍历宏,实现获取单链表长度:
__API__ __STATIC_INLINE__ int tos_slist_length(k_slist_t *slist)
{
int len = 0;
k_slist_t *iter;
TOS_SLIST_FOR_EACH(iter, slist) {
++len;
}
return len;
}
② 读取链表头结点或者尾结点:
__API__ __STATIC_INLINE__ k_slist_t *tos_slist_head(k_slist_t *slist)
{
return slist->next;
}
__API__ __STATIC_INLINE__ k_slist_t *tos_slist_tail(k_slist_t *slist)
{
if (!slist->next) {
return K_NULL;
}
while (slist->next) {
slist = slist->next;
}
return slist;
}
③ 判断链表是否为空:
__API__ __STATIC_INLINE__ int tos_slist_empty(k_slist_t *slist)
{
return !slist->next;
}
3. 双向链表使用示例
3.1. 实验内容
本实验会创建一个带有10个静态结点的双向链表,每个新的自定义节点中有一个数据域,存放一个uint8_t类型的值,有一个双向链表节点,用于构成双向链表。
3.2. 实验代码
首先包含内核头文件:
/**
* @brief TencentOS-tiny单向链表测试
* @author Mculover666
* @date 2021/9/20
*/
#include <tos_k.h>
创建一个自己的新节点:
typedef struct node
{
uint8_t data;
k_slist_t list;
}node_t;
新建一个函数用来测试:
#define LIST_LEN 10
void single_list_test()
{
int i;
/* 头结点 */
k_slist_t slist;
/* 创建10个链表节点 */
node_t node_pool[LIST_LEN];
/* 初始化链表头结点 */
tos_slist_init(&slist);
/* 构建一条具有LIST_LEN个节点的双向链表 */
for(i = 0; i < LIST_LEN;i++)
{
node_pool[i].data = i;
// 头插法
tos_slist_add_head(&node_pool[i].list, &slist);
}
/* 遍历打印所有节点 */
k_slist_t *cur;
node_t *n;
//for(cur = slist.next;cur != NULL;cur = cur->next)
TOS_SLIST_FOR_EACH(cur, &slist)
{
n = TOS_SLIST_ENTRY(cur, node_t, list);
printf("n = %d\\n", n->data);
}
return;
}
遍历整条链表的时候,使用了tencentOS-tiny中提供的宏定义 TOS_LIST_FOR_EACH。
还有最后一个使用问题,我们都是对整条链表进行操作(比如可以轻松的遍历整条链表),操作的时候得到的地址都是node_t类型节点中k_slist_t类型成员的地址,那么如何访问到data成员呢?
TencentOS-tiny中依然提供了两个宏定义来解决这一问题,在tos_klib.h
中。
① 计算某一个成员在结构体基地址中的偏移地址:
#define TOS_OFFSET_OF_FIELD(type, field) \\
((uint32_t)&(((type *)0)->field))
② 已知某一个成员的地址,计算结构体的基地址:
#define TOS_CONTAINER_OF_FIELD(ptr, type, field) \\
((type *)((uint8_t *)(ptr) - TOS_OFFSET_OF_FIELD(type, field)))
这两个宏定义的实现属实有点骚,其中的巧妙之处可以再写一篇文章讲解了哈哈,此处我们先了解其使用即可(此处要感谢戴大神的解答)。
有了这两个宏定义,就有了实验中所使用的宏定义,用来获取结构体(node_t类型节点)的基地址:
#define TOS_SLIST_ENTRY(node, type, field) \\
TOS_CONTAINER_OF_FIELD(node, type, field)
获取到结构体的基地址,还愁访问不到其中的任何一个成员吗?
最后的实验结果,你应该能猜到了,上图:
接收更多精彩文章及资源推送,欢迎订阅我的微信公众号:『mculover666』。
以上是关于数据结构 | TencentOS-tiny中的单向链表的实现及使用的主要内容,如果未能解决你的问题,请参考以下文章