一、双向链表概念
双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向其前一个节点的指针。其头指针head是唯一确定的。
从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这种数据结构形式使得双向链表在查找时更加方便,特别是大量数据的遍历。由于双向链表具有对称性,能方便地完成各种插入、删除等操作,但需要注意前后方向的操作。
如下链表图所示:
下面是华为官方提供的开发指南手册的截取部分,如图所示:
这里的开发流程其实和我们之前学习的C语言或者C++语言中的数据结构与算法的双链表是差不多的。以下我根据野火开发手册讲解一下。
1、初始化双链表
华为LiteOS的链表结构体如下图所示:
有些朋友没玩过RTOS等操作系统的话,可能会好奇,我学的链表定义的结构体一步还带有别的元素的,为什么这里的没有,其实这是为了方便开发者开发及统一,定义自己想要带的元素结构体即可,如下图所示:
这里不多说怎么构造自己的链表结构体,我先带大家认识华为LiteOS是如何对链表进行一些链表的常规操作的。
双向链表一般都需要定义一个头节点,这是为了方便链表的定位和查表等操作。定义一个头节点的操作只需要将首尾指针指向头节点本身即可。
2、链表添加节点
双向链表添加节点操作如下图所示:
上图可以很清晰知道,先将新节点的首尾指针分别接到前后两个节点上,然后再断开前后两个节点的指针并将这两个指针接到新的节点上。
3、链表删除节点
双向链表删除节点操作如下图所示:
根据上图可以清晰知道,分别将前后的节点指针与其节点断开并前后对接上,然后将其节点的指针断开置空即可。
其他什么尾部插表什么都基本都和上面的操作差不多,就不将其展开说了。
二、华为LiteOS双向链表之宿舍信息录入小实验
这里需要大家了解几个比较关键的事情:
1、信息系统会比较占用内存,因此我们需要使用动态申请和释放内存的方式操作链表。(这里申请内存实现就不多说了,后面我会出内存管理的博客讲解)
2、★重点★之前不是我们建立有元素的表我们需要在我们建立的有元素的结构体内嵌套华为LiteOS自带的LOS_DL_LIST链表结构体,构建的大体结构图如下图所示:
很多人看不出上面有什么问题,我举个例子吧!例如:printf("A1姓名 = %s\r\n",A1.姓名);
这样输出肯定没有问题。我们在链表中按理论来说printf("A1姓名 = %s\r\n",((A *)Head->next).姓名);
也可以正常输出姓名才对,但是这只是我们看起来,这里我分析一下为什么第二种无法正常输出,这里仍以上图来做解释,如下图我标记的。
![在这里插入图片描述](https://img-blog.csdnimg.cn/891506732ecc4314b6333668ae782bf1.png)
那么我们就需要将指向下一个节点的指针地址偏移到其首地址位置上。使用华为LiteOS的API函数LOS_DL_LIST_ENTRY(item, type, member)
,此API用于获取指向包含双向链表的结构体的指针,如下图所示。
而((long)&((type *)0)->member)
这串代码是计数出除LOS_DL_LIST链表结构体外的偏移地址字节长度。
有了上面计算出来的元素长度,那么就可以通过((type *)((char *)item - LOS_OFF_SET_OF(type, member)))
计算出节点的首地址了,大家可能有会疑惑了,这串代码中(char *)作用是什么,大家想想不同指针类型它们++或–是根据其类型进行地址偏移的,例如int类型的,每次偏移4个字节的,但是我们这里偏移需要让其一个字节一个字节的移动。
至此,比较重要的理论已经讲完了。
代码实现:
#include "stm32f10x.h"
#include "los_typedef.h"
#include "los_sys.h"
#include "los_task.ph"
#include "los_swtmr.h"
#include "los_membox.h"
#include "los_queue.h"
#include "los_sem.h"
#include "los_memory.h"
#include "string.h"
#include "led.h"
#include "usart1.h"
#include "key.h"
#include "exti.h"
/* 函数声明 */
extern LITE_OS_SEC_BSS UINT8* m_aucSysMem0;
/* 宿舍结构体 */
typedef struct {
UINT32 StudentNumber; //学号
char Name[20]; //姓名
UINT32 Age; //年龄
LOS_DL_LIST List; //首尾指针
}DormitoryData;
DormitoryData *p; //用于存放节点地址
/* 定义任务ID变量 */
UINT32 Test_Task_Handle;
/* 函数声明 */
static UINT32 AppTaskCreate(void); //用于创建任务管理
static UINT32 Create_Test_Task(void);
static void Test_Task(void);
/* LED控制函数 */
void LED_RED(UINT32 state) {
if(state) {
GPIO_ResetBits(GPIOB, GPIO_Pin_5); //点亮
} else if(state == 0) {
GPIO_SetBits(GPIOB, GPIO_Pin_5); //熄灭
}
}
void LED_YELLOW(UINT32 state) {
if(state) {
GPIO_ResetBits(GPIOE, GPIO_Pin_5); //点亮
} else if(state == 0) {
GPIO_SetBits(GPIOE, GPIO_Pin_5); //熄灭
}
}
int main(void)
{
UINT32 uwRet = LOS_OK; //定义一个任务创建的返回值,默认为创建成功
/* 中断优先级分组为 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/* 板载相关初始化 */
Usart1_Config(); //串口初始化
LED_GPIO_Config(); //LED初始化
Key_Config_parameter(); //按键初始化
printf("正点原子战舰开发板-LiteOS-SRAM 动态创建多任务 之 双链表实现!\r\n");
/* LiteOS 内核初始化 */
uwRet = LOS_KernelInit();
if (uwRet != LOS_OK) {
printf("LiteOS核心初始化失败!任务代码0X%X\r\n",uwRet);
return LOS_NOK;
}
uwRet = AppTaskCreate();
if (uwRet != LOS_OK) {
printf("AppTaskCreate任务创建失败!任务代码0X%X\r\n",uwRet);
return LOS_NOK;
}
/* 开启 LiteOS 任务调度 */
LOS_Start();
while(1){ //上面任务失败会进入此
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOE,GPIO_Pin_5);
}
}
/*********************************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 任务创建,为了方便管理,所有的任务创建函数都可以放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
********************************************************************************/
static UINT32 AppTaskCreate(void)
{
/* 定义一个返回类型变量,初始化为LOS_OK */
UINT32 uwRet = LOS_OK;
/* LED任务 */
uwRet = Create_Test_Task();
if(uwRet != LOS_OK) {
printf("Test_Task任务创建失败!失败代码0X%X\r\n",uwRet);
return uwRet;
}
return LOS_OK;
}
/*********************************************************************************
* @ 函数名 : Create_Test_Task
* @ 功能说明: 创建 Test_Task 任务
* @ 参数 : 无
* @ 返回值 : 无
********************************************************************************/
static UINT32 Create_Test_Task(void) {
/* 定义一个返回类型变量 */
UINT32 uwRet = LOS_OK;
/* 定义一个用于创建任务的参数结构体 */
TSK_INIT_PARAM_S task_init_param;
task_init_param.usTaskPrio = 4; /* 设计任务优先级为5 */
task_init_param.pcName = "Test_Task"; /* 任务名 */
task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Test_Task; /* 任务入口函数 */
task_init_param.uwStackSize = 0x1000; /* 栈大小 */
uwRet = LOS_TaskCreate(&Test_Task_Handle, &task_init_param);
return uwRet;
}
/*********************************************************************************
* @ 函数名 : Test_Task
* @ 功能说明: Test_Task 任务实现
* @ 参数 : 无
* @ 返回值 : 无
********************************************************************************/
static void Test_Task(void) {
UINT32 uwRet = LOS_OK;
printf("\r\n双向链表初始化中......\r\n");
/* 申请创建一个双向链表的头节点的内存 */
LOS_DL_LIST *head = LOS_MemAlloc(m_aucSysMem0,sizeof(LOS_DL_LIST));
/* 申请创建双向链表的后面的宿舍成员信息节点都内存 */
DormitoryData *person1 = LOS_MemAlloc(m_aucSysMem0,sizeof(DormitoryData));
DormitoryData *person2 = LOS_MemAlloc(m_aucSysMem0,sizeof(DormitoryData));
DormitoryData *person3 = LOS_MemAlloc(m_aucSysMem0,sizeof(DormitoryData));
DormitoryData *person4 = LOS_MemAlloc(m_aucSysMem0,sizeof(DormitoryData));
LOS_ListInit(head); /* 初始化双向链表 */
if(!LOS_ListEmpty(head)) {
printf("双向链表初始化失败!\r\n");
} else {
printf("双向链表初始化成功!\r\n");
}
/* 插入节点:顺序插入与从尾节点插入 */
printf("添加节点和尾节点添加......\r\n");
/*** 定义双向链表的宿舍4人信息 ***/
/* 将每个双向链表节点添加对应的宿舍成员信息 */
person1->StudentNumber = 1840918147; strcpy(person1->Name,"常江"); person1->Age = 20;
person2->StudentNumber = 1840918148; strcpy(person2->Name,"莫志华"); person2->Age = 20;
person3->StudentNumber = 1840918149; strcpy(person3->Name,"钟子能"); person3->Age = 20;
person4->StudentNumber = 1840918150; strcpy(person4->Name,"邓家文"); person4->Age = 20;
printf("宿舍4人信息动态申请第一个节点内存\r\n");
printf("添加第一个节点与第二个节点......\r\n");
LOS_ListAdd(head,&person1->List); /* 添加第一个节点,连接在头节点上 */
if(person1->List.pstPrev == head) {
printf("添加节点成功!\r\n");
} else {
printf("添加节点失败!\r\n");
}
p = LOS_DL_LIST_ENTRY(head->pstNext,DormitoryData,List);
printf("StudentNumber1 = %d\r\n",p->StudentNumber);
printf("将尾节点插入双向链表的末尾......\r\n");
LOS_ListTailInsert(head,&person2->List); /* 将尾节点插入双向链表的末尾 */
if(person2->List.pstNext == head) {
printf("链表尾节点添加成功!\r\n");
} else {
printf("链表尾节点添加失败!\r\n");
}
p = LOS_DL_LIST_ENTRY(head->pstPrev,DormitoryData,List);
printf("StudentNumber2 = %d\r\n",p->StudentNumber);
LOS_ListTailInsert(head,&person3->List); /* 将尾节点插入双向链表的末尾 */
if(person3->List.pstNext == head) {
printf("链表尾节点添加成功!\r\n");
} else {
printf("链表尾节点添加失败!\r\n");
}
p = LOS_DL_LIST_ENTRY(head->pstPrev,DormitoryData,List);
printf("StudentNumber3 = %d\r\n",p->StudentNumber);
LOS_ListTailInsert(head,&person4->List); /* 将尾节点插入双向链表的末尾 */
if(person4->List.pstNext == head) {
printf("链表尾节点添加成功!\r\n");
} else {
printf("链表尾节点添加失败!\r\n");
}
p = LOS_DL_LIST_ENTRY(head->pstPrev,DormitoryData,List);
printf("StudentNumber4 = %d\r\n\r\n",p->StudentNumber);
LOS_DL_LIST_FOR_EACH_ENTRY(p,head,DormitoryData,List) {
printf("StudentNumber = %d\r\n",p->StudentNumber);
printf("Name = %s\r\n",p->Name);
}
/* 删除已有的节点 */
printf("删除节点......\r\n");
LOS_ListDelete(&person3->List); /* 删除第一个节点 */
LOS_DL_LIST_FOR_EACH_ENTRY(p,head,DormitoryData,List) {
printf("StudentNumber = %d\r\n",p->StudentNumber);
}
int status = 0;
while(1) {
status = ~status;
LED_YELLOW(-status);
//printf("任务运行中%d......\r\n",-status);
LOS_TaskDelay(500);
}
}