在程序中常常面临一个问题,咱们须要保存必定数量的对象,可是对象数目是不肯定的,或者说是随时增长或减小的。这时候最简单的方法是建立一个足够大的数组,用来存储这些对象。我最近开发一个项目就遇到相似的问题,下面我把问题简化一下。git
需求:经过PC下发一些矩形的坐标和宽高信息,每一个区域有个ID编号,并在这些矩形内填充必定的数据。github
一般状况下,最简单易懂的作法是,限制最多5个区域,每一个区域存储1K数据。所以设置了这样的一个结构体(相似于面向对象语言里说的成员属性)。数组
typedef struct Area_Inf { uint8_t ID; uint8_t X; uint8_t Y; uint8_t Width; uint8_t Height; uint8_t data_len; }Area_Inf_Typedef;
而后定义结构体的实体。app
#define Area_Num 5 #define Area_cache 1024 Area_Inf_Typedef Area_Info[Area_Num]; uint8_t Area_Data[Area_Num*Area_cache];//存储区域的数据 /*找到ID为5的区域,并将数据拷贝出去*/ void main() { uint8_t i; uint8_t data[1024]; for(i = 0;i < Area_Num;i++) { if(Area_Info[i].ID == 5) { memcpy(data,&Area_Data[i*Area_cache ],Area_Info[i].data_len); } } }
上面这种作法是最简单易懂的,但不灵活,好比有客户要求10个区域,可是每一个区域存储的数据不多,根本用不到1K。虽然上面的程序已经使用了宏定义,只须要修改宏定义就能实现要求。但这意味着不一样的客户,须要编译不一样的固件。函数
#define Area_Num 10 #define Area_cache 512
这样的程序存在的问题:测试
一、在内存资源很紧缺的单片机程序中,当区域数据不多时,这种程序的处理方法浪费了大量的内存空间。ui
二、数值固定,须要存储更多区域,即便还有内存,仍是须要修改宏定义,从新编译固件,不灵活。spa
这时须要引入链表来解决这个问题。debug
链表其实是线性表的链式存储结构,与数组不一样的是,它是用一组任意的存储单元来存储线性表中的数据,存储单元不必定是连续的,且链表的长度不是固定的,链表数据的这一特色使其能够很是的方便地实现节点的插入和删除操做。链表的每一个元素称为一个节点,每一个节点均可以存储在内存中的不一样的位置,为了表示每一个元素与后继元素的逻辑关系,以便构成“一个节点链着一个节点”的链式存储结构,除了存储元素自己的信息外,还要存储其直接后继信息,所以,每一个节点都包含两个部分,第一部分称为链表的数据区域,用于存储元素自己的数据信息。3d
对于上面的问题,咱们使用链表解决,须要配合内存管理才能实现。内存管理这一块,你们能够本身编写内存管理驱动,也可使用C库的malloc和free函数。如何字节编写内存管理驱动不是本文的重点,下文将使用C库的malloc和free函数进行内存管理。
使用链表的方式,在原有的成员属性结构体的前提上,还要再封装多一层链表管理。以单向链表为例:
typedef struct Area_Inf { uint8_t ID; uint8_t X; uint8_t Y; uint8_t Width; uint8_t Height; uint8_t data_len; uint8_t* Area_Data; }Area_Inf_Typedef; typedef struct Area_List_Inf { Area_Inf_Typedef *Area_Inf; struct Area_List_Inf *next_Area_Inf; //用于指向下一个 }Area_List_Inf_Typedef; Area_List_Inf_Typedef *Head_Area_List; //链表的头指针
因为在定义的时候,只定义了一个头指针,那么它也只是个指向了Area_List_Inf_Typedef也就是链表结构体的指针,一样没有内存空间,在没有建立新增链表以前,它是一个野指针。
因此,在具体应用以前,须要先执行一个初始化操做,也就是申请空间给链表管理结构体,而后头指针指向这个空间。
/** * @brief 动态区链表初始化 * @return int */ int Area_List_Init(void) { //申请链表类型大小的空间,并让头指针指向它 Head_Area_List = (Area_List_Inf_Typedef*)malloc(sizeof(Area_List_Inf_Typedef)); if(Head_Area_List == NULL) return false; //同时要标记下一个信息为空 Head_Area_List->next_Area_Inf = NULL; return true; }
经过PC下发一个新的区域信息后,增长新区域到链表末尾。
/** * @brief 在链表末尾增长一个区域参数 * @param Area_Inf 增长的区域区参数指针 * @return int */ int Add_Area_ToList(Area_Inf_Typedef *Area_Inf) { Area_List_Inf_Typedef *p = Head_Area_List; while(p->next_Area_Inf!=NULL) { p = p->next_Area_Inf; } //先申请链表结构体的空间,由于后续还要继续增长 p->next_Area_Inf = (Area_List_Inf_Typedef*)malloc(sizeof(Area_List_Inf_Typedef)); if(p->next_Area_Inf == NULL) return false;//申请不到内存,返回失败 //指向刚刚申请的空间,并为须要存放的动态区信息申请对应的内存 p = p->next_Area_Inf; p->Area_Inf = (Area_Inf_Typedef*)malloc(sizeof(Area_Inf_Typedef)); if(p->Area_Inf == NULL) { free(p);//因为申请失败,先前申请的链表空间也要释放 return false; } memcpy(p->Area_Inf,Area_Inf,sizeof(Area_Inf_Typedef)); /*拷贝数据*/ p->Area_Inf->Area_Data = (uint8_t*)malloc(Area_Inf->data_len); if(p->Area_Inf->Area_Data == NULL) { free(p->Area_Inf); free(p); return false; } memcpy(p->Area_Inf->Area_Data,Area_Inf->Area_Data,Area_Inf->data_len); //标记这个链表的尾部 p->next_Area_Inf=NULL; //添加成功 return true; }
经过PC下发一个删除指定ID的区域命令。
/** * @brief 根据区域ID删除动态区 * @param num 区域ID * @return int */ int Delete_Area_Accordingn_ID(int num) { int res = false; Area_List_Inf_Typedef *p = Head_Area_List; while(p->next_Area_Inf!=NULL) { Area_List_Inf_Typedef *temp = p; p = p->next_Area_Inf; if(p->Area_Inf->ID == num)//匹配到对应的值 { temp->next_Area_Inf = p->next_Area_Inf; //释放内存空间 free(p->Area_Inf->Area_Data); free(p->Area_Inf); free(p); p=temp; res = true; } } return res; }
看了上面的驱动函数,相信你们已经明白,你们能够自行编写一些驱动,下面我实现的三个简单函数。
/** * @brief 根据区域ID找到链表 * @param data_p 链表指针 * @param num 区域ID编号 * @return int */ int Find_Area_According_ID(Area_Inf_Typedef **data_p,int num) { Area_List_Inf_Typedef *p = Head_Area_List; while(p->next_Area_Inf!=NULL) { p = p->next_Area_Inf; if(p->Area_Inf->ID == num)//匹配到对应的值 { *data_p = p->Area_Inf; return true; } } return false; } /** * @brief 删除全部区域 * */ int Delete_All_Area(void) { int res = false; Area_List_Inf_Typedef *p = Head_Area_List; while(p->next_Area_Inf!=NULL) { Area_List_Inf_Typedef *temp = p; p = p->next_Area_Inf; temp->next_Area_Inf = p->next_Area_Inf; //释放内存空间 free(p->Area_Inf->Area_Data); free(p->Area_Inf); free(p); p=temp; res = true; } return res; } /** * @brief 打印链表信息 * */ void Printf_Area_Inf(void) { int i=0; Area_List_Inf_Typedef *p = Head_Area_List; printf("list ID X Y Width Height Area_Data\r\n"); while(p->next_Area_Inf!=NULL) { p = p->next_Area_Inf; printf(" %d %d %d %d %d %d %s\r\n",i,p->Area_Inf->ID,p->Area_Inf->X,p->Area_Inf->Y,p->Area_Inf->Width,p->Area_Inf->Height,p->Area_Inf->Area_Data); i++; } printf("----------------------end-----------------------\r\n"); }
下面编写一个测试函数,能够测试,链表的初始化,增长一个区域,删除指定区域,根据ID返回区域信息,删除全部区域接口。
/** * @brief 链表测试函数 * */ void list_main() { int i,j; Area_Inf_Typedef temp; Area_Inf_Typedef **data_p; data_p = NULL; printf("------------------List test---------------------\r\n"); if(!Area_List_Init( )) { printf("Memory fail..\r\n"); } for(i=0;i<5;i++) { temp.ID = i; temp.X = 5+i; temp.Y = i; temp.Width = 10+i; temp.Height = 10+i; temp.data_len = i+1; temp.Area_Data = (uint8_t*)malloc(temp.data_len+1); for(j=0;j<temp.data_len;j++) { temp.Area_Data[j] = j+0x30; } temp.Area_Data[j] = 0; if(!Add_Area_ToList(&temp)) { printf("Add Area %d Area_Info fail\r\n",i); } } Printf_Area_Inf(); printf("\r\n-------------Delete ID of Area is 3-------------\r\n"); Delete_Area_Accordingn_ID(3); Printf_Area_Inf(); temp.ID = 9; temp.data_len = 10; temp.Area_Data = (uint8_t*)malloc(temp.data_len+1); for(j=0;j<temp.data_len;j++) { temp.Area_Data[j] = j+0x30; } temp.Area_Data[j] = 0; if(!Add_Area_ToList(&temp)) { printf("Add Area %d info fail\r\n",temp.ID); } printf("\r\n--------------Add ID of Area is 9---------------\r\n"); Printf_Area_Inf(); Find_Area_According_ID(data_p,2); temp.ID = (*data_p)->ID; Delete_All_Area(); printf("\r\n--------------Delete All Area-------------------\r\n"); Printf_Area_Inf(); while(1); }
测试结果
IAR和keil工程代码开源地址:
https://github.com/strongercjd/STM32_Linklist
若是你们手中有板子能够调试,能够看《一文了解串口打印》文章,使用串口打印。若是临时没有板子能够debug,能够模拟测试,IAR设置以下:
选择Simulator调试
打开View->TerminalI/O,就能够看到打印信息
点击查看本文所在的专辑,STM32F207教程