目录

1、为何引入链表

2、链表实现

3、测试函数


1、为何引入链表

在程序中经常面临一个问题,我们需要保存一定数量的对象,但是对象数目是不确定的,或者说是随时增加或减少的。这时候最简单的方法是创建一个足够大的数组,用来存储这些对象。我最近开发一个项目就遇到类似的问题,下面我把问题简化一下。

需求:通过PC下发一些矩形的坐标和宽高信息,每个区域有个ID编号,并在这些矩形内填充一定的数据。

通常情况下,最简单易懂的做法是,限制最多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;

然后定义结构体的实体。

#define Area_Num 5
#define Area_cache 1024Area_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

这样的程序存在的问题:

1、在内存资源很紧缺的单片机程序中,当区域数据很少时,这种程序的处理方法浪费了大量的内存空间。

2、数值固定,需要存储更多区域,即使还有内存,还是需要修改宏定义,重新编译固件,不灵活。

这时需要引入链表来解决这个问题。

2、链表实现

链表实际上是线性表的链式存储结构,与数组不同的是,它是用一组任意的存储单元来存储线性表中的数据,存储单元不一定是连续的,且链表的长度不是固定的,链表数据的这一特点使其可以非常的方便地实现节点的插入和删除操作。链表的每个元素称为一个节点,每个节点都可以存储在内存中的不同的位置,为了表示每个元素与后继元素的逻辑关系,以便构成“一个节点链着一个节点”的链式存储结构,除了存储元素本身的信息外,还要存储其直接后继信息,因此,每个节点都包含两个部分,第一部分称为链表的数据区域,用于存储元素本身的数据信息。

对于上面的问题,我们使用链表解决,需要配合内存管理才能实现。内存管理这一块,大家可以自己编写内存管理驱动,也可以使用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");
}

3、测试函数

下面编写一个测试函数,可以测试,链表的初始化,增加一个区域,删除指定区域,根据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工程代码开源地址:

GitHub - strongercjd/STM32_Linklist: STM32 Linklist

如果大家手中有板子可以调试,可以看《一文了解串口打印》文章,使用串口打印。如果临时没有板子可以debug,可以模拟测试,IAR设置如下:

选择Simulator调试

打开View->TerminalI/O,就可以看到打印信息

点击查看本文所在的专辑,STM32F207教程

关注公众号,第一时间收到文章更新。评论区不能及时看到,需要交流可以到公众号沟通

链表在STM32中的应用相关推荐

  1. 再次理解STM32中的堆栈机制

    再次理解STM32中的堆栈机制 刚拿到STM32时,你只编写一个死循环 void main() { while(1); }BUILD://Program Size: Code=340 RO-data= ...

  2. STM32中堆栈的理解

    STM32中堆栈的理解 关于程序的内存分配 栈区(stack):由编译器自动分配和释放,存放函数的参数与返回值.局部变量等. 堆区(heap):由程序员分配管理,一般未使用(malloc函数). 全局 ...

  3. STM32中GPIO的8种工作模式

    一.推挽输出:可以输出高.低电平,连接数字器件:推挽结构一般是指两个三极管分别受两个互补信号的控制,总是在一个三极管导通的时候另一个截止.高低电平由IC的电源决定.形象点解释:推挽,就是有推有拉,任何 ...

  4. Arduino处理STM32中的多个串口通讯问题

    简 介: 对于在Arduino下开发STM32的程序,对于STM32所具有的三个硬件USART进行测测试.结果显示可以使用这些串口完成相应的数据的输入与输出.但是涉及到以下两个问题,还没有得到解决:问 ...

  5. STM32中GPIO的8种工作模式!

    一.推挽输出:可以输出高.低电平,连接数字器件:推挽结构一般是指两个三极管分别受两个互补信号的控制,总是在一个三极管导通的时候另一个截止.高低电平由IC的电源决定.         推挽电路是两个参数 ...

  6. STM32中IO口的8中工作模式

    该文摘自:http://blog.csdn.net/kevinhg/article/details/17490273 一.推挽输出:可以输出高.低电平,连接数字器件:推挽结构一般是指两个三极管分别受两 ...

  7. STM32中GPIO的8种模式

    STM32中GPIO的8种模式

  8. STM32中NVIC_SystemReset()函数的作用?什么时候用?

    STM32中NVIC_SystemReset()函数的作用?什么时候用? STM32软件复位有两种方式 (1)方式一:NVIC_SystemReset()函数用来复位STM32. 注意1:从SYSRE ...

  9. STM32中的timers中断处理函数

    STM32中的timers中断处理函数 1.在固件库函数里面,用来读取中断状态寄存器的值判断中断类型的函数是: ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, ...

最新文章

  1. 我的MYSQL学习心得(十六) 优化
  2. LeetCode Number of Boomerangs
  3. 手把手玩转win8开发系列课程(18)
  4. 逆向python生成的可执行文件
  5. MySQL Replication--复制异常1
  6. Windows Error Report
  7. C++std命名空间和头文件详解
  8. android model 设计,Android model层设计
  9. 安卓线程同步面试_Android面试题
  10. Intellij idea利用Statistic插件统计项目代码行数
  11. 在gcp终端中使用ssh连接到本地机器
  12. 详解收发不畅原因及U-Mail邮件中继解决之道
  13. output在delete中的应用
  14. android布局--Android fill_parent、wrap_content和match_parent的区别
  15. Linux Samba服务器配置
  16. smartSVN用法
  17. Electron 屏幕锁定 快捷键锁定 屏蔽快捷键
  18. 80后的童鞋们,还记得大明湖畔的克林顿时代吗?
  19. CCNP基础知识-交换技术
  20. Python通讯录案例

热门文章

  1. 奶块显示正常登录服务器但进不去,奶块服务器流畅怎么进不进去quest; | 手游网游页游攻略大全...
  2. OpenHarmony3.0 编译烧录
  3. centos编译安装php7_centos7源码安装php7.2
  4. 缺失值填充6——拉格朗日插值法填充序列缺失值
  5. python使用正则表达式删除字符串中的数字
  6. java bitmap取出数据库_bitmap一般如何取出其所表示的数据(以java为例)
  7. gpedit msc组策略面板 win10在哪里_Win10家庭版找不到组策略gpedit.msc的解决方法
  8. ffmpeg结构体SpecifierOpt说明文档
  9. ASP.NET jQuery 食谱15 (通过控件CustomValidator验证CheckBoxList)
  10. 首页终于让百度放出来了