数据结构 | 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中的单向链表的实现及使用的主要内容,如果未能解决你的问题,请参考以下文章

基于STM32Cube MX开发的TencentOS-Tiny软件包

git进阶 | 02 - git设置追踪所有远程分支

git进阶 | 02 - git设置追踪所有远程分支

python数据结构链表之单向链表

用Java语言实现单向链表

Python3中定义一个单向链表