当前位置: 首页 > backend >正文

循环队列分析及应用

逐一解读队列模块

一、队列数据结构

queue.h#ifndef _QUEUE_H_
#define _QUEUE_H_#include <stdint.h>这是队列里面的关键参数,队列里面有一个数组,是用来接收数据的,那么就需要定义一个数组的下标,队头和队尾,接着就是队列的长度、最后是队列的数组。typedef struct
{uint32_t head;        //数组下标,指向队头uint32_t tail;        //数组下标,指向队尾uint32_t size;        //队列缓存长度(初始化时赋值)uint8_t *buffer;      //队列缓存数组(初始化时赋值)
} QueueType_t;用来表示队列状态,一共有四种状态,
1、正常状态
2、错误状态
3、队列已填满
4、队列已空
typedef enum
{QUEUE_OK = 0,       //队列正常QUEUE_ERROR,        //队列错误QUEUE_OVERLOAD,     //队列已满QUEUE_EMPTY         //队列已空
} QueueStatus_t;void QueueInit(QueueType_t *queue, uint8_t *buffer, uint32_t size);QueueStatus_t QueuePush(QueueType_t *queue, uint8_t data);QueueStatus_t QueuePop(QueueType_t *queue, uint8_t *pdata);uint32_t QueuePushArray(QueueType_t *queue, uint8_t *pArray, uint32_t len);uint32_t QueuePopArray(QueueType_t *queue, uint8_t *pArray, uint32_t len);uint32_t QueueCount(QueueType_t *queue);#endif

二、队列模块函数

1、初始化队列结构体

一般在调用队列之前需要进行初始化函数

#define MAX_BUF_SIZE       (77)
static uint8_t g_rcvDataBuf[MAX_BUF_SIZE]实例化一个队列数据   主要包含队头、队尾、队列长度、队列的buff数组
static QueueType_t g_rcvQueue首先需要初始化这写个内容
因为QueueType_t里面的buff里面是指针,因此需要将我们自己定义的数组的首地址传进去注意数据类型QueueInit(&g_rcvQueue, g_rcvDataBuf, MAX_BUF_SIZE);void QueueInit(QueueType_t *queue, uint8_t *buffer, uint32_t size)
{queue->buffer = buffer;queue->size = size;queue->head = 0;queue->tail = 0;
}

QueueType_t * queue 这样就相当于将我们自己定义的结构体的首地址传进去,然后利用形参queue进行访问,而前面的QueueType_t 表示的是这个结构体的类型,也就是我们传进去地址类型,或者也可以理解为* queue指针变量的类型,再或者可以理解为我们使用编译器开辟了一个* queue把这个地址空间,而这个地址空间的结构和QueueType_t 是一模一样的,用来保存我们传进去的自己定义的结构体g_rcvQueue。理解可能有些绕,
可以参考我前面的文章。指针深入理解(一)-CSDN博客 指针深入理解(二)-CSDN博客等专栏文章关于这一部分的解读。

而我们的queue->buffer = buffer; 接收的也是我们自己定义数组的首地址,g_rcvDataBuf 这个表示数组名/数组标签 也就是数组的首地址。

2、压入数据到队列中

QueueStatus_t QueuePush(QueueType_t *queue, uint8_t data)
{uint32_t index = (queue->tail + 1) % queue->size;if (index == queue->head){return QUEUE_OVERLOAD;}queue->buffer[queue->tail] = data;queue->tail = index;return QUEUE_OK;
}

传进去的一个是环形队列的指针变量,一个是即将压入队列的data

一个data是一个字节,只能放进去一个字节,并且本身在硬件层面,一次也只能传输一个字节。一个字节发送完毕,产生一个中断。需要多少个字节,则会产生多少个中断。

首先在传进去之前,需要判断队列是否已经满了,如果满了,那么就退出函数。

需要说明的是tail这个变量指的是队尾,可是我们怎么知道这个队尾?
队尾是负责输入数据的。

根据我们编写的代码可以看出,tail只有在初始化的时候会进行赋值为0。后续没有将其赋值为0。
那么第一次压入的时候根据这个QueuePush函数的执行逻辑可以看出,tail会进行加1操作,并且会更新这个数据( queue->tail = index;)。因此正常情况下会一直进行这个操作。
在这里插入图片描述

循环队列会出现两种情况,一种是读的太快,写的太慢,head和tail会相等
另外一种情况就是tail走了一圈,和head重合了,这个是写的比读的快, 这一种相当于是队列满了,那么就不能再写入数据了,但是和第一种就会造成无法判断,因此为了避免这种情况,设计者就重新设计了一个思路,在数组中空出来一个位置,不写入数据。
也就是tail指向head前面的一个位置时,认为队列满了,这样就轻松区别了这两种情况。
而在代码实现上面就是下面这一行代码

uint32_t index = (queue->tail + 1) % queue->size;

并且这个取模运算就是实现了循环操作,由于tail和head之间隔了一个位置,那么就算这个时候tail已经走了一圈,由于head前面必须要保持一个空位置,因此,只有在以下这种情况才会是满

    uint32_t index = (queue->tail + 1) % queue->size;if (index == queue->head){return QUEUE_OVERLOAD;}queue->buffer[queue->tail] = data;queue->tail = index;return QUEUE_OK;

如果我数组的长度是5,那么数值就是0-4.
假设我现在tail现在是在0号位置写入数据,但是在刚进来这个函数的时候已经tail+1了,也就是index是1,在写入0位置数据以后,就会将index赋值为tail,此时tail为1,但是是没有数据的。这样就实现了在head前面永远有一个位置是空的,或者可以理解为写入当前tail数据位置的下一次位置已经被空出来了,不可能被写入数据。因此下一次进来就会立马+1进行判断,判断是否紧挨着head的位置,如果是就不会写了,直接退出函数。如果不是就会写,是在不+1的位置写。这个地方需要认真理解,帮助后续的分析代码有很大帮助,或者说进行优化的时候,可以有思路,知其所以然。

这是写入一组数据到队列中,
道理是一样的,只不过使用了for循环遍历。
将这一组数据全部写到队列里面。

uint32_t QueuePushArray(QueueType_t *queue, uint8_t *pArray, uint32_t len)
{uint32_t i;for (i = 0; i < len; i++){if(QueuePush(queue, pArray[i]) == QUEUE_OVERLOAD){break;}}return i;
}

3、读队列的数据

首先将接收数组进行初始化操作。

然后是调用函数,


uint8_t readBuf[PACKET_DATA_LEN_MAX] = {0};QueuePop(&g_rcvQueue, &readBuf[0])

注意写入和读取两个函数的区别


QueueStatus_t QueuePop(QueueType_t *queue, uint8_t *pdata)
{if(queue->head == queue->tail){return QUEUE_EMPTY;}*pdata = queue->buffer[queue->head];queue->head = (queue->head + 1) % queue->size;return QUEUE_OK;
}

在读取之前先要判断队列是否为空,
就跟在写之前先判断是否为满一样,在工作用,一定要注意边界条件的处理,只有这样才能将产品维护的很好,业务干的漂亮,不然就是写出来Bug很多。
言归正传
当队列为空的时候,也就只有一种情况,我写的太慢了,读的速度很快,导致队列中一会数据就用完了。那就是head追上了tail,也只有这一种情况会这样。需要注意的是队列满的时候,是另外的判断思路。因此,满和空就完全是两个判断方式,直接让他们两种情况永远不会有交集。

此外,如果位置不是空,那就取出当前位置的数据,然后进行+1,因为我读取了这次的数据以后,我一定要直接+1处理,这样不会影响我下一次的判断。这就是编程的思想。

跟写入的思路是异曲同工之妙。

这是直接读出来一组数据,直接连续读数据。
一口气读出来。感觉这样容易导致空?
目前想想不到应用场景。

uint32_t QueuePopArray(QueueType_t *queue, uint8_t *pArray, uint32_t len)
{uint32_t i;for(i = 0; i < len; i++){if (QueuePop(queue, &pArray[i]) == QUEUE_EMPTY){break;}}return i;
}

四、获取队列中的数据个数

一般是两种情况,
在这里插入图片描述


uint32_t QueueCount(QueueType_t *queue)
{if (queue->head <= queue->tail){return queue->tail - queue->head;}return queue->size + queue->tail - queue->head;
}

以上就是循环队列的基本使用方法。

文章源码获取方式:
如果您对本文的源码感兴趣,欢迎在评论区留下您的邮箱地址。我会在空闲时间整理相关代码,并通过邮件发送给您。由于个人时间有限,发送可能会有一定延迟,请您耐心等待。同时,建议您在评论时注明具体的需求或问题,以便我更好地为您提供针对性的帮助。

【版权声明】
本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议。这意味着您可以自由地共享(复制、分发)和改编(修改、转换)本文内容,但必须遵守以下条件:
署名:您必须注明原作者(即本文博主)的姓名,并提供指向原文的链接。
相同方式共享:如果您基于本文创作了新的内容,必须使用相同的 CC 4.0 BY-SA 协议进行发布。
示例:
如果您在博客或文章中引用了本文的内容,请在显著位置标注类似以下声明:
本文部分内容参考自 [博主名称] 的原创文章,原文链接:[文章链接],遵循 CC 4.0 BY-SA 版权协议。
感谢您的理解与支持!如果您有任何疑问或需要进一步协助,请随时在评论区留言。*

http://www.xdnf.cn/news/7975.html

相关文章:

  • 在 Qt 中实现动态切换主题(明亮和暗黑)
  • Gartner研究报告《Generative AI 赋能Digital Commerce的三种路径》学习心得
  • 笑林广记读书笔记三
  • 下一代电子电气架构(EEA)的关键技术
  • 具有思考模式模型部署:Qwen3、DeepSeek-R1-Distill、Phi-4、QWQ系列
  • 模型量化与保存
  • Python实例题:Python实现简单画板
  • 网络安全之身份验证绕过漏洞
  • 【AI+开发】什么是LLM、MCP和Agent?
  • 容器网络中的 veth pair 技术详解
  • 求无符号字符型数据乘积的高一半
  • 隧道自动化监测解决方案
  • 【攻防实战】MacOS系统上线Cobalt Strike
  • 高并发内存池|六、page cache的设计
  • 13、自动配置【源码分析】-自动包规则原理
  • Springboot2
  • Qt enabled + geometry 属性(2)
  • 微信小游戏流量主广告自动化浏览功能案例5
  • 【IP101】图像质量评价体系:从主观评价到客观度量的完整解析
  • RL电路的响应
  • Spring AI 1.0 GA 于 2025 年 5 月 20 日正式发布,都有哪些特性?
  • 防火墙高可靠性
  • AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年5月21日第84弹
  • nlohmann json:检查类型并取出数据
  • 【八股战神篇】Spring高频面试题汇总
  • 企业数字化转型是否已由信息化+自动化向智能化迈进?
  • YCKC【二分答案专题】题解
  • 关于C++使用位运算交换变量值的分析
  • Vue学习记录
  • docker面试题(5)