【数据结构】——队列


前言

  在单片机数据处理的时候,如果在中断里添加太多函数,可能会影响整个程序的运行。这时利用数据结构队列,先将缓存放入队列,等主程序空闲时再处理,可以变相提高代码稳定性。本文介绍队列的原理以及单片机C语言实现。


一、队列的概念

  队列(Queue)是限定只能在表的两端分别进行插入或删除操作的线性表。在队列结构中,数据元素只能从一端(表尾)插入,从另一端(表头)删除。允许插入的一端称为队尾(Rear),允许删除的一端称为队头(Front)。当队列中没有包含数据元素时,称为空队。向一个队列插入新的元素称为人队(Enqueue),此时,插入的元素成为新的队尾元素;从队列中删除一个元素时,只能删除当前的队头元素,称为出队(Dequeue)。基于列的这种“先进先出”的结构特点,因而也称为先进先出(First In First Out,FIFO)线性表。
请添加图片描述

二、队列结构

  队列的表示与实现也可以采用顺序存储结构和链式存储结构描述

2.1 顺序队列

  采用顺序存储结构表示队列时,可以用C语言中的一维数组进行描述。由于对于队列的操作只在队头和队尾进行,因此,需要设置两个指针 front和rear分别指示队头和队尾的位置,并可约定队尾指针rear指向当前队尾元素后的下一个位置,队头指针front指向当前的队头元素。顺序队列的表示形式如下,

#define MAXSIZE 100			//队列的最大容量
typedef struct SqQueue
{
	ElmType data[MAXSIZE];	//队列的存储空间
	int rear,front;			//对头队尾指针
}SqQueue;

(1)初始化队列。队尾指针和队头指针均指向下标为0的单元,令front=rear=0。
(2)入队操作。新元素将存放到当前队尾指针所指的单元,并使队尾指针rear增1指示下一次插入的位置,则rear=rear+1。
(3)出队操作。将当前队头指针所指单元的值返回,并将队头指针front增1即可,则 front=front+1。
请添加图片描述

  入队和出队操作的过程如上图所示。可以看出,队列为空,不能做出队操作,因此判断队列为空的条件是front==rear;经过多次插入和删除操作以后,队列的队尾指针和队头指针将逐步向后移动,最终出现当队尾指针已指向所定义空间中的最后单元时,即rear=MAXSIZE-1时,队列满,无法再入队。虽然此时队头指针所指位置之前可能存在若干单元空闲,却无法进行入队操作的问题。这种现象称为“假溢出”,可以借助两种方法解决顺序队列的“假溢出”问题:

方法一:采用“移动队列”的方法,即每当执行一次出队操作,则依次将队头和队尾指针向数组的起始位置移动,始终保持队头在数组的起始位置。这种方法的代价是产生大量的元素移动,显然不是一种好方法。
方法二:采用循环队列,将一维数组的最后一个单元和第一个单元连接起来构成循环数组,此时称为“循环队列”。当队尾指针已指向数组的最后时,在进行人队操作过程中,可将队尾指针rear 移至数组的起始位置,表示下一次入队操作时的队尾。(居指针移动的方式是rear=(rear+1)%MAXSIZE,队头指针的移动也一样front=(front+1)%MAXSIZE,如图所示:
请添加图片描述

  循环队列初始化空队时,front=rear=0;当队列经过多次出队入队操作后,会出现队头指针front 和队尾指针rear 再次指向同一单元的情况,此时有可能队列空也有可能队列满。为了区分队满和队空的情况,规定当队空时,front== rear;队满时,队尾指针指向队头指针的前一个位置即为满Jrear+1)%MAXSIZE==front,实际循环队列队满时,所容纳的元素个数为 MAXSIZE—T;通过空留一个存储单元的方式区分循环队列队满和队空的情况。下面第三小节详细说明循环队列的存储表示和基本操作的实现方法。

2.2 链队

  采用链式存储结构表示队列,称为链队。显然,利用与线性链表类似的方法可以很容易实现链队结构,并分别设置队尾和队头指针指示链队的位置。在链队结构中增加一个附加的头结点,并使队头指针front指向它。此时,判断空队的标志是,链队的队尾指针rear和队头指针 front均指向头结点。带头结点的链队结构示意图如图所示:
请添加图片描述

三、C语言实现

  在单片机中,由于单片机RAM空间比较小,通常只有几十KB到击败KB大小,此时采用链队的方式并不方便内存管理,所以本节主讲顺序队列的实现

3.1 ringbuffer.h头文件

包含基本数据的结构体,以及函数声明

#ifndef __RINGBUFFER_H
#define __RINGBUFFER_H

#ifdef __cplusplus
 extern "C" {
#endif 

#include "stm32f4xx.h"
#include <stdio.h>

typedef struct {
	uint8_t* pBuff;	//保存缓存数据
	uint8_t* pEnd;  // pBuff + legnth
	uint8_t* wp;    // Write Point
	uint8_t* rp;    // Read Point
	uint16_t length;	
	uint8_t  flagOverflow; // set when buffer overflowed
} RingBuffer;

void rbInitialize(RingBuffer* pRingBuff, uint8_t* buff, uint16_t length);
void rbClear(RingBuffer* pRingBuff);
void rbPush(RingBuffer* pRingBuff, uint8_t value);
uint8_t rbPop(RingBuffer* pRingBuff);
uint16_t rbGetCount(const RingBuffer* pRingBuff);
int8_t rbIsEmpty(const RingBuffer* pRingBuff);
int8_t rbIsFull(const RingBuffer* pRingBuff);

#ifdef __cplusplus
}
#endif

#endif 

3.2 ringbuffer.c源文件

#include "ringbuffer.h"

//初始化队列结构体
void rbInitialize(RingBuffer* pRingBuff, uint8_t* buff, uint16_t length)
{
	pRingBuff->pBuff = buff;
	pRingBuff->pEnd  = buff + length;
	pRingBuff->wp = buff;
	pRingBuff->rp = buff;
	pRingBuff->length = length;
	pRingBuff->flagOverflow = 0;
}

//清除缓存溢出标志位
//正常情况下不会过流,只有在IAP情况下可能会出现虚假过流
void rbClear(RingBuffer* pRingBuff)
{
 	pRingBuff->wp = pRingBuff->pBuff;
	pRingBuff->rp = pRingBuff->pBuff;
	pRingBuff->flagOverflow = 0;
}

//入队
//首先判断队列有没有满,满就退回一段数据,未满就写数据
void rbPush(RingBuffer* pRingBuff, uint8_t value)
{
	uint8_t* wp_next = pRingBuff->wp + 1;
	if( wp_next == pRingBuff->pEnd ) {
		wp_next -= pRingBuff->length; 	// Rewind pointer when exceeds bound
	}
	if( wp_next != pRingBuff->rp ) {
		*pRingBuff->wp = value;
		pRingBuff->wp = wp_next;
	} else {
		pRingBuff->flagOverflow = 1;
	}
}

//出队,空队返回,否则读取数据
uint8_t rbPop(RingBuffer* pRingBuff)
{
	if( pRingBuff->rp == pRingBuff->wp ) return 0; // empty
  
	uint8_t ret = *(pRingBuff->rp++);
	if( pRingBuff->rp == pRingBuff->pEnd ) {
		pRingBuff->rp -= pRingBuff->length; 		// Rewind pointer when exceeds bound
	}
	return ret;
}

//返回未读数据长度
uint16_t rbGetCount(const RingBuffer* pRingBuff)
{
	return (pRingBuff->wp - pRingBuff->rp + pRingBuff->length) % pRingBuff->length;
}

//判断队列数据是否为空
//1:空;0:非空
int8_t rbIsEmpty(const RingBuffer* pRingBuff)
{
	return pRingBuff->wp == pRingBuff->rp; 
}

//判断队列空间是否满,没啥意义,可以不用
int8_t rbIsFull(const RingBuffer* pRingBuff)
{
 	return (pRingBuff->rp - pRingBuff->wp + pRingBuff->length - 1) % pRingBuff->length == 0;
}

3.3 函数调用

  1. 申请缓存
#define RX_BUFF_SIZE	4096
static RingBuffer  RX_RingBuff;

//初始化
void RcvFrom_Init()
{
  static uint8_t RX_Buff[RX_BUFF_SIZE];
  rbInitialize(&RX_RingBuff, RX_Buff, sizeof(RX_Buff));
}

//入队
void Push_RcvData(u8 dat)
{
  rbPush(&RX_RingBuff, dat);
}
  1. 中断压入队列,以串口中断为例
void USART1_IRQHandler(void)
{
    u8 rcvdata;

    if (USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)
    {
        USART_ClearFlag(USART1,USART_FLAG_ORE);
        USART_ReceiveData(USART1);
        //The RXNE flag can also be cleared by a read operation to the USART_DR register(USART_ReceiveData()).
    }
    if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)
    {
        rcvdata = USART_ReceiveData(USART1);
        Push_RcvData(rcvdata);
    }
}

3.主函数里处理

u8 cur;

void main(void)
{
	/*初始化*/
	RcvFrom_Init();
	while(1)
	{
		cur = rbPop(&RX_RingBuff);
		/*具体处理*/
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别问,问就是全会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值