【数据结构】环形缓冲区/循环队列 C语言实现

来源:互联网 发布:维密摔倒后果 知乎 编辑:程序博客网 时间:2024/06/05 10:26

忙着毕设,很久没有写文章了,终于答辩完了,得了个校优秀毕业设计。毕设做的是个智能接口模块,用一周时间入门了,MC9S12XEP100的开发,又用一周时间入门了uC/OS-II嵌入式操作系统,在做毕设的过程中学到了很多,现在把一些工具发上来分享。


这里分享一个自己用纯C实现的环形缓冲区。环形缓冲区有很多作用,比如嵌入式中的通信可以用环形缓冲区作为信道,一个线程往里放字节,一个线程取字节进行处理,只要保证取的速度大于读的速度,就可以保证通信顺畅进行,不丢一个字节。
简要介绍:
环形缓冲区其实就是一个队列,里头的元素是先入先出的,但是因为其(逻辑上)是环形的,所以不需要像很多队列的实现那样在内部元素变动的时候需要移动内部剩下的元素。这样就使元素出队入队的时间复杂度只有O(1)。具体实现一般有链表和数组两种方法,当不能确定需要的缓冲区大小时使用链表较好,能确定时使用数组可以节省很多动态分配内存的开销。
在嵌入式开发中,一般不动态分配内存,而是使用静态分配的数组。所以这里我使用数组实现了环形缓冲区,为了能够在不同的程序中复用代码,使用结构体模拟了面向对象编程,这样就可以用一套代码管理不同的缓冲区了。
废话不多说,直接上代码。以下是.h 文件:

/*************************************************************************************************************                                       RingQueueStruct*                                         环形队列结构** File : RingQueue.h* By   : Lin Shijun(http://blog.csdn.net/lin_strong)* Date : 2017/10/16* version: V1.1 * NOTE(s): 这段程序用来对一个给定的缓冲区进行模拟环形队列的管理*                   程序本身不会自动分配缓冲区空间,用户需要自己负责分配空间,并且要保证不直接访问缓存区*                   // 在某处分配内存空间*                   RQTYPE buffer[BUFFER_SIZE];*                   RING_QUEUE que,*ptr_que;*                   unsigned char err;*                   // 初始化*                   ptr_que = RingQueueInit(&que,buffer,BUFFER_SIZE,&err);*                   if(err == RQ_ERR_NONE  ){*                     // 初始化成功,使用其他函数*                   }* History : 2017/04/25   the original version of RingQueueStruct.*           2017/10/16   put functions used frequently,RingQueueIn and RingQueueOut, in non-banked address;*                        modify single line function RingQueueIsEmpty and RingQueueIsFull to marco function;*                        to get better efficiency.  **********************************************************************************************************/#ifndef   RING_QUEUE_H#define   RING_QUEUE_H/***********************************************************************************************************                                       INCLUDES**********************************************************************************************************//***********************************************************************************************************                                        CONSTANTS     常量**********************************************************************************************************/#define   RQ_ERR_NONE                        0u#define   RQ_ERR_POINTER_NULL                1u#define   RQ_ERR_SIZE_ZERO                   2u#define   RQ_ERR_BUFFER_FULL                 3u#define   RQ_ERR_BUFFER_EMPTY                4u#define   RQ_OPTION_WHEN_FULL_DISCARD_FIRST  0u       // discard the first element when ring buffer is full#define   RQ_OPTION_WHEN_FULL_DONT_IN        1u       // discard new element when ring buffer is full/***********************************************************************************************************                                    DATA TYPE    数据类型**********************************************************************************************************/// define the data type that stores in the RingQueue.       定义存在环形缓冲区内的数据的类型typedef unsigned char RQTYPE;typedef RQTYPE* pRQTYPE;typedef struct {    unsigned short  RingBufCtr;              /* Number of characters in the ring buffer */    unsigned short  RingBufSize;             /* Ring buffer Size */        pRQTYPE RingBufInPtr;                    /* Pointer to where next character will be inserted        */      pRQTYPE RingBufOutPtr;                   /* Pointer from where next character will be extracted     */      pRQTYPE RingBuf;                         /* Ring buffer array */      pRQTYPE RingBufEnd;                      /* Point to the end of the buffer */} RING_QUEUE;/***********************************************************************************************************                                  FUNCTION PROTOTYPES 函数原型**********************************************************************************************************/RING_QUEUE *RingQueueInit(RING_QUEUE *pQueue,pRQTYPE pbuf,unsigned short bufSize,unsigned char *perr);#pragma CODE_SEG __NEAR_SEG NON_BANKEDunsigned short RingQueueIn(RING_QUEUE *pQueue,RQTYPE data,unsigned char option,unsigned char *perr);RQTYPE RingQueueOut(RING_QUEUE *pQueue,unsigned char *perr);#pragma CODE_SEG DEFAULTshort RingQueueMatch(RING_QUEUE *pQueue,pRQTYPE pbuf,unsigned short len);void RingQueueClear(RING_QUEUE *pQueue);/***********************************************************************************************************                                        RingQueueIsEmpty()** Description :  whether the RingQueue is empty.   环形队列是否为空** Arguments   :  pQueue  pointer to the ring queue control block;     指向环形队列控制块的指针** Return      :  TRUE    the RingQueue is empty.*                FALSE   the RingQueue is not empty.* Note(s)     :**********************************************************************************************************/#define RingQueueIsEmpty(pQueue) ((pQueue)->RingBufCtr == 0)/***********************************************************************************************************                                        RingQueueIsFull()** Description : whether the RingQueue is full.    环形队列是否为空** Arguments   : pQueue  pointer to the ring queue control block;   指向环形队列控制块的指针** Return      : TRUE    the RingQueue is full.*               FALSE   the RingQueue is not full.* Note(s)     :**********************************************************************************************************/#define RingQueueIsFull(pQueue)  ((pQueue)->RingBufCtr >= (pQueue)->RingBufSize)#endif

然后下面是.c文件。

/*************************************************************************************************************                                      RingQueueStruct*                                        环形队列结构** File : RingQueue.c* By   : Lin Shijun(http://blog.csdn.net/lin_strong)* Date : 2017/10/16* version: V1.1 * NOTE(s): ** History : 2017/04/25   the original version of RingQueueStruct.*           2017/10/16   put functions used frequently,RingQueueIn and RingQueueOut, in non-banked address;*                        modify single line function RingQueueIsEmpty and RingQueueIsFull to marco function;*                        to get better efficiency.  **********************************************************************************************************//***********************************************************************************************************                                     INCLUDES**********************************************************************************************************/#include "RingQueue.h"/***********************************************************************************************************                                   CONSTANTS  常量**********************************************************************************************************//***********************************************************************************************************                                LOCAL FUNCTION DECLARE**********************************************************************************************************/#pragma CODE_SEG __NEAR_SEG NON_BANKED// 内部使用,给定将给定指针在环形缓冲区内向前移动一步(到尾了会移回头)static void forwardPointer(RING_QUEUE *pQueue,pRQTYPE* pPointer);#pragma CODE_SEG DEFAULT/***********************************************************************************************************                                        RingQueueInit()** Description : To initialize the ring queue.    初始化环形队列** Arguments   : pQueue   pointer to the ring queue control block;     指向环形队列控制块的指针*               pbuf     pointer to the buffer(an array);             指向自定义的缓冲区(实际就是个数组)*               bufSize  the Size of the buffer;                      缓冲区的大小;*               perr     a pointer to a variable containing an error message which will be set by this*                          function to either:**                           RQ_ERR_NONE                                       *                           RQ_ERR_SIZE_ZERO*                           RQ_ERR_POINTER_NULL** Return      : the pointer to the ring queue control block;        返回指向环形队列控制块的指针*               0x00 if any error;                                  如果出错了则返回NULL**Note(s):**********************************************************************************************************/RING_QUEUE* RingQueueInit(RING_QUEUE *pQueue,pRQTYPE pbuf,unsigned short bufSize,unsigned char *perr){     if(pQueue == 0x00 || pbuf == 0x00){          *perr = RQ_ERR_POINTER_NULL;           return 0x00;           }     if(bufSize == 0){          *perr = RQ_ERR_SIZE_ZERO;           return 0x00;           }     pQueue->RingBufCtr = 0;     pQueue->RingBuf = pbuf;     pQueue->RingBufInPtr = pbuf;     pQueue->RingBufOutPtr = pbuf;     pQueue->RingBufSize = bufSize;     pQueue->RingBufEnd = pbuf + bufSize;     *perr = RQ_ERR_NONE;          return pQueue;}/***********************************************************************************************************                                        RingQueueIn()** Description : Enqueue an element.    入队一个元素** Arguments   : pQueue   pointer to the ring queue control block;    指向环形队列控制块的指针*               data     the data to enqueue;                        要入队的数据*               option   option when queue is full ,you can choose:  当队列满的时候的选项,你可以选择:*                            RQ_OPTION_WHEN_FULL_DISCARD_FIRST          抛弃队头的元素来填进去新的元素*                            RQ_OPTION_WHEN_FULL_DONT_IN                不入队新给的元素*               perr     a pointer to a variable containing an error message which will be set by this*                          function to either:**                             RQ_ERR_NONE                            if no err happen*                             RQ_ERR_POINTER_NULL                    if pointer is 0x00*                             RQ_ERR_BUFFER_FULL                     if buffer is full** Return       : the Elements Count after enqueue the element*                    调用函数后队列中的元素个数*Note(s)       :**********************************************************************************************************/#pragma CODE_SEG __NEAR_SEG NON_BANKEDunsigned short RingQueueIn(RING_QUEUE *pQueue,RQTYPE data,unsigned char option,unsigned char *perr){     if(pQueue == 0x00){          *perr = RQ_ERR_POINTER_NULL;                   return 0;      }     if(pQueue->RingBufCtr >= pQueue->RingBufSize){           *perr = RQ_ERR_BUFFER_FULL;                if(option == RQ_OPTION_WHEN_FULL_DISCARD_FIRST){               forwardPointer(pQueue,&pQueue->RingBufOutPtr); /* Wrap OUT pointer                          */             }else{                                                  // option == RQ_OPTION_WHEN_FULL_DONT_IN               return pQueue->RingBufCtr;           }     }else{           pQueue->RingBufCtr++;                                           /* No, increment character count            */                 *perr = RQ_ERR_NONE;     }      *pQueue->RingBufInPtr = data;                                /* Put character into buffer                */        forwardPointer(pQueue,&pQueue->RingBufInPtr);   /* Wrap IN pointer                          */       return pQueue->RingBufCtr;}/***********************************************************************************************************                                        RingQueueOut()** Description : Dequeue an element.       出队一个元素** Arguments   : pQueue   pointer to the ring queue control block;     指向环形队列控制块的指针*               perr     a pointer to a variable containing an error message which will be set by this*                          function to either:**                              RQ_ERR_NONE                            if no err happen*                              RQ_ERR_POINTER_NULL                    if pointer is 0x00*                              RQ_ERR_BUFFER_EMPTY                    if buffer is empty** Return      : the data *               0                 if any error;*Note(s):**********************************************************************************************************/RQTYPE RingQueueOut(RING_QUEUE *pQueue,unsigned char *perr){     RQTYPE data;     if(pQueue == 0x00) {          *perr = RQ_ERR_POINTER_NULL;                    return 0;     }     if(pQueue->RingBufCtr == 0){          *perr = RQ_ERR_BUFFER_EMPTY;                  return 0;       }     pQueue->RingBufCtr--;                                      /*  decrement character count           */       data = *pQueue->RingBufOutPtr;                      /* Get character from buffer                */       forwardPointer(pQueue,&pQueue->RingBufOutPtr);        /* Wrap OUT pointer                          */       *perr = RQ_ERR_NONE;     return data;}#pragma CODE_SEG DEFAULT/***********************************************************************************************************                                        RingQueueMatch()** Description : Match the given buffer in RingQueue          在环形队列中匹配给定缓冲区** Arguments   : pQueue   pointer to the ring queue control block;     指向环形队列控制块的指针*               pbuf     pointer to the chars need to match;*               len      the length of the chars* Return      :  -1       Don't match            -1    则没有匹配到*                >= 0     match                  >= 0  则匹配到了**Note(s):**********************************************************************************************************/short RingQueueMatch(RING_QUEUE *pQueue,pRQTYPE pbuf,unsigned short len){     pRQTYPE pPosQ,pCurQ,pCurB,pEndB;     unsigned short rLen,Cnt;     if(len > pQueue->RingBufCtr)        return -1;     pPosQ = pQueue->RingBufOutPtr;     pEndB = pbuf + len;     Cnt = 0;     rLen = pQueue ->RingBufCtr;     while(rLen-- >= len){                                    // if remian length of queue bigger than buffer. continue          pCurQ = pPosQ;          pCurB = pbuf;          while(pCurB != pEndB && *pCurQ == *pCurB) {    // compare one by one,until match all(pCurB==pEndB) or some one don't match               forwardPointer(pQueue,&pCurQ);               pCurB++;          }           if(pCurB == pEndB)                                                 // if match all                return Cnt;           Cnt++;           forwardPointer(pQueue,&pPosQ);     }     return -1;}/***********************************************************************************************************                                        RingQueueClear()** Description:  Clear the RingQueue.        清空环形队列** Arguments  :  pQueue    pointer to the ring queue control block;     指向环形队列控制块的指针** Return:             ** Note(s):**********************************************************************************************************/void RingQueueClear(RING_QUEUE *pQueue){     pQueue->RingBufCtr = 0;     pQueue->RingBufInPtr = pQueue -> RingBufOutPtr;}/***********************************************************************************************************                                       LOCAL FUNCTION **********************************************************************************************************/#pragma CODE_SEG __NEAR_SEG NON_BANKEDstatic void forwardPointer(RING_QUEUE *pQueue,pRQTYPE* pPointer){    if (++*pPointer == pQueue->RingBufEnd)           *pPointer = pQueue->RingBuf;        /* Wrap OUT pointer                          */  }#pragma CODE_SEG DEFAULT

简单解释下。在.h文件中定义了一个环形缓冲区的控制块,当然也可以当其为一个环形缓冲区对象,用户需要为每个环形缓冲区分配一个控制块和其缓冲区(也就是一个数组)。理想情况下,虽然用户知道控制块的结构,但也不应该直接访问内部字段,而应该通过提供的函数来访问。
队列中默认的元素是无符号字符,如果要改成缓存其他类型的话改下.h文件中的
typedef unsigned char RQTYPE;
这行就行了。
使用示例:

#include "RingQueue.h"#define RX_BUF_MAX_SIZE     200        // 定义缓冲区的最大大小为200static unsigned char RxBuffer[RX_BUF_MAX_SIZE];   // 定义缓冲区static RING_QUEUE RxRingQ;             // 定义环形缓冲区的控制块void main(){   unsigned char err;   // 初始化缓冲区   RingQueueInit(&RxRingQ,RxBuffer,RX_BUF_MAX_SIZE,&err);   if(err != RQ_ERR_NONE){       //初始化缓冲区失败的处理   }   ……}

然后调用所有方法都需要传递环形缓冲区控制块的指针。如入队就像:

// 往RxRingQ缓冲区内入队一个元素c,如果满的话丢弃第一个元素RingQueueIn(&RxRingQ,c,RQ_OPTION_WHEN_FULL_DISCARD_FIRST,&err); 

出队就像:

// 从RxRingQ缓冲区内提取一个字符c = RingQueueOut(&RxRingQ,&err);

其他就不一 一举例了。要特别说明下的是RingQueueMatch()这个方法并不是队列应该有的方法,这是为了比如我需要在缓冲区中匹配到某一串字符后做某些事情而特别加上的,不需要的话删掉即可。比如我需要一旦出现“abc”就做某些事情,那我代码可以类似这么写:

static const unsigned char* StringsWait = "abc";……while(true){    //比如从某处获得了下一个字符c    ……    // 将字符c入队    RingQueueIn(&RxRingQ,c,RQ_OPTION_WHEN_FULL_DISCARD_FIRST,&err);     if(RingQueueMatch(&RxRingQ,StringsWait,3) >= 0){  // 如果在缓冲区内找到"abc"         // RingQueueClear(&RxRingQ);     // 可能需要清空缓冲区         // 做想要做的事         ……    }}

有什么建议或意见请留言,谢谢!


更改历史:

2017/10/27 更新到 V1.1

原创粉丝点击