rt-thread的链表
借用网上的一些文章来描述一下什么是链表:
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。
rt-thread采用的数据链表是双向链表。
在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点地址,一般称之为右链域;一个存储直接前驱结点地址,一般称之为左链域。
双向列表图示:
struct rt_list_node
{
struct rt_list_node *next; // 指向下一个节点的指针
struct rt_list_node *prev; // 指向上一个节点的指针
};
typedef struct rt_list_node rt_list_t;
链表的指针其实就是链表的首节点的地址。
接口功能:每新建一个链表都需要对链表进行初始化。
接口说明:初始化的作用:将新建的链表初始化位一个空的链表;
将链表设为空链表的方法就是将链表的首节点的next元素和prev元素指向首节点的地址
rt_inline void rt_list_init(rt_list_t *l)
{
l->next = l->prev = l;
}
接口功能:在链表的首节点后插入节点
接口实现说明:
第一步:先改变节点l的下个一个节点的上一个节点指向节点n
第二步:再将节点n的下个节点指向节点l的下个节点。
第三步:将l的下个节点指向节点n
第四部:将节点n的上一个节点指向节点l
图示:
节点n插入之前:
节点n插入之后:
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;
}
接口功能:在节点l前插入节点n
接口实现说明:
第一步:先改变节点l的上一个节点的下一个节点指向节点n
第二步:再将节点n的上一个节点指向l节点的上一个节点。
第三步:将节点l的上一个节点指向节点n
第四部:将n节点的下一个节点指向节点l
即可完成在节点后插入节点
图示:
节点n插入之前:
节点n插入之后:
rt_inline voidrt_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;
}
接口功能:在链表上移出节点n
接口实现说明:
第一步:节点n的上一个节点的下一个节点指向节点n的上一个节点
第二步:将节点n的上一个节点的下一个节点指向n的下一个节点
第三步:将节点n的下一个节点指向和n的上一个节点都指向n
移除节点前:
移除节点后:
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;
}
接口功能:判断链表是否为空
接口实现说明:
判断节点l的下一个节点是否指向本节点l
rt_inline int rt_list_isempty(const rt_list_t*l)
{
return l->next == l;
}
这是一个非常有技巧和聪明的做法,不懂的必须要搞明白,非常聪明的。
接口功能:通过这个节点以及这个节点所在结构体对象的类型来计算出结构体对象的地址
参数说明:
node 链表的节点地址
type 结构体对象的类型
member 链表在结构体的元素名称
细节说明:
(char*)(node):将节点指针类型强转为char*
(type*)0):将0强制转换为一个type类型的指针
&((type *)0)->member:获取type结构体的member元素的地址,即获取节点元素的指针
(unsigned long)(&((type *)0)->member):将member元素的地址强转换为一个unsigned long类型,即为member元素偏移结构体地址的位置偏移量
((type *)((char *)(node) - (unsignedlong)(&((type *)0)->member))):计算节点node所在的结构体的地址,通过node即可获取结构体的地址。
#define rt_list_entry(node, type, member) \
((type *)((char *)(node) - (unsigned long)(&((type*)0)->member)))
如:
struct student{
rt_list_tnode;
intage;
};
struct student a_student;
a_student的地址可以通过调用
rt_list_entry(&a_student.node, structstudent, node)即可获得a_student的地址。
使用这个接口的前提是只知道链表的节点n的地址,不知道结构体对象的的地址时,可通过这个节点以及这个节点所在结构体对象的类型来计算出结构体对象的地址。
第一次写技术贴,有什么写的不对的地方请提出,欢迎吐槽。