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

循环缓冲区

# 循环缓冲区

在这里插入图片描述

说明

所谓消费,就是数据读取并删除。

循环缓冲区这个数据结构与生产者-消费者问题高度适配。

生产者-产生数据,消费者-处理数据,二者速度不一致,因此需要循环缓冲区。

显然,产生的数据要追加到循环缓冲区末尾,然后再去消费

可以直接去最后拿代码,看一般使用说明。

定义

// 定义缓冲区大小(可根据需要修改)
#define CIRCULAR_BUFFER_SIZE 12// 结构体定义(对应C++类)
typedef struct {uint8_t buffer[CIRCULAR_BUFFER_SIZE];     // 内部存储数组uint8_t* bufferEnd;                       // 指向 buffer[capacity - 1]uint8_t* writeP;                          // 写指针(当前写入位置)uint8_t* readP;                           // 读指针(当前读取位置)
} CircularBuffer;

其中

变量说明
readP将要消费内容的位置,应当指向数据区的开头。
writeP将要追加内容的位置,应当指向数据区结尾的后一个。或者说“非数据区”的开头

因为循环缓冲区与生产者-消费者问题高度适配,所以这样设计是合理的。

情况1

123456
bufferreadP->writeP->end
------------------------------------------------------------------------------------------------

情况2

789123456
bufferwriteP->readP->end
------------------------------------------------------------------------------------------------

后面写不下,回到开头去写。

注意消费指针、追加指针都只能“向后走”,走到最后就回到开头。

追加、消费必须在对应位置去做。

注意

不允许将整个数组全部使用,否则无法区分当前是没有数据,还是数据已满。

当然也可以定义一个变量去记录当前有多少数据,我这里不使用这种方法。

没有数据

writeP->
readP->
------------------------------------------------------------------------------------------------

数据充满整个数组

111212345678910
writeP->
readP
------------------------------------------------------------------------------------------------

初始化

/*** @brief 初始化* @param cb 循环缓冲区指针*/
void CircularBuffer_Init(CircularBuffer* cb);void CircularBuffer_Init(CircularBuffer* cb) {cb->bufferEnd = cb->buffer + CIRCULAR_BUFFER_SIZE - 1;cb->writeP = cb->buffer;cb->readP = cb->buffer;
}

是否为空

/*** @brief 是否为空* @param cb 循环缓冲区指针* @return true 是* @return false 否*/
bool CircularBuffer_Empty(const CircularBuffer* cb);bool CircularBuffer_Empty(const CircularBuffer* cb) {return cb->writeP == cb->readP;
}

数据长度

当前存储的数据长度,或者说允许消费的数据的长度

/*** @brief 数据长度* @param cb 循环缓冲区指针* @return 已存储的数据长度*/
size_t CircularBuffer_Size(const CircularBuffer* cb);size_t CircularBuffer_Size(const CircularBuffer* cb) {return (cb->writeP - cb->readP + CIRCULAR_BUFFER_SIZE) % CIRCULAR_BUFFER_SIZE)
}

总容量

/*** @brief 最大容量* @param cb 循环缓冲区指针* @return 最大容量*/
size_t CircularBuffer_Capacity(const CircularBuffer* cb);size_t CircularBuffer_Capacity(const CircularBuffer* cb) {return CIRCULAR_BUFFER_SIZE - 1;// 最大容量(总空间 - 1,因为不能完全填满)
}

数据开头位置

显然,数据头部head的位置就是readP。

/*** @brief 数据区开头* @param cb 循环缓冲区指针* @return 数据区开头指针*/
uint8_t* CircularBuffer_Begin(const CircularBuffer* cb);uint8_t* CircularBuffer_Begin(const CircularBuffer* cb) {if (CircularBuffer_Empty(cb)) return NULL;return cb->readP;
}

数据结尾位置

但要注意:数据最后一个tail并不是head + dataCount - 1!(见前面的例子)

应当这样得到

数据结尾tail

tail = buffer + ((head + dataCount -1 - buffer + CIRCULAR_BUFFER_SIZE) % CIRCULAR_BUFFER_SIZE)

其中

head + dataCount -1 - buffer 可能超过真实数组范围,是一个“假想”的索引,还要进行取模运算!得到真实的索引。

/*** @brief 数据区结尾* @param cb 循环缓冲区指针* @return 数据区结尾指针*/
uint8_t* CircularBuffer_End(const CircularBuffer* cb);uint8_t* CircularBuffer_End(const CircularBuffer* cb) {if (CircularBuffer_Empty(cb)) return NULL;size_t tailIndex = (cb->readP + CircularBuffer_Size(cb) - 1 - cb->buffer + CIRCULAR_BUFFER_SIZE) % (CIRCULAR_BUFFER_SIZE);return (cb->buffer + tailIndex);
}

指针是否指向数据区

先排除指针根本不指向内部数组的情况。

然后注意,不要比较it和tail的真实位置,应当比较它们的虚拟位置

如果itMap <= tailMap 说明指针指向数据区

if (it < cb->buffer || it > cb->bufferEnd) return false;size_t itMapIndex = (it - cb->readP) % (CIRCULAR_BUFFER_SIZE);//指针以数据头为开头的索引
size_t tailMapIndex = CircularBuffer_Size(cb) - 1;//数据尾部,以数据头为开头的索引
if (itMapIndex > tailMapIndex) return false;//目标不在数据区内,错误

it - readP 可能为超出范围,应当取模以修正

(it - readP) % (CIRCULAR_BUFFER_SIZE) 得到是,当循环缓冲区以head为开头时,it的索引。

迭代器的“后”一个

迭代器(指针)it,它的后一个itBehind

注意itBehind并不一定是it++。

遍历时应当

while(...)
{it++;if(it > bufferEnd) it=buffer;//超过范围,回到开头
}

当然,也可以这样得到后一个的位置

itBehind = buffer + (it - buffer +1) % CIRCULAR_BUFFER_SIZE

同理,后n个

itNBehind = buffer + (it - buffer + n) % CIRCULAR_BUFFER_SIZE

追加一批数据

循环缓冲区使用时,来一批数据,就追加一批。

注意:为了提高拷贝效率,需要调用memcpy。memcpy有底层的优化。

并不是迭代器一直++,因此需要区分两种情况。在真实的数组找到关键位置,然后调用memcpy

一般循环缓冲区用于生产者消费者模型,所以要使用这个和后面的消费一批数据。

说明

不需要返回开头的情况

以追加4个为例

原本

012
bufferreadPwritePend
------------------------------------------------------------------------------------------------

之后

0123456
bufferreadPwritePend
------------------------------------------------------------------------------------------------
需要返回开头的情况

写不下需要返回开头!

以追加6个为例

原本

012
bufferreadPwritePend
------------------------------------------------------------------------------------------------

之后

567801234
bufferwritePreadPend
------------------------------------------------------------------------------------------------

实现代码

/*** @brief 向缓冲区追加数据* @param cb 循环缓冲区指针* @param data 数据* @param length 数据长度* @return true 成功* @return false 失败*/
bool CircularBuffer_Append(CircularBuffer* cb, const uint8_t* data, size_t length);bool CircularBuffer_Append(CircularBuffer* cb, const uint8_t* data, size_t length) {if (length == 0) return true;if (length > (CircularBuffer_Capacity(cb) - CircularBuffer_Size(cb))) return false;if (cb->writeP + length <= cb->bufferEnd) {memcpy(cb->writeP, data, length);cb->writeP += length;}else {size_t endLength = cb->bufferEnd - cb->writeP + 1;size_t beginLength = length - endLength;memcpy(cb->writeP, data, endLength);memcpy(cb->buffer, data + endLength, beginLength);cb->writeP = cb->buffer + beginLength;}return true;
}

消费一批数据

必须从头消费。

说明

以消费5个数据为例

不需要返回开头的情况

读取前

1234567
bufferreadPwritePend
------------------------------------------------------------------------------------------------

读取后

67
bufferreadPwritePend
------------------------------------------------------------------------------------------------
需要返回开头的情况

读取前

3456712
bufferwritePreadPend
------------------------------------------------------------------------------------------------

读取后

67
bufferreadPwritePend
------------------------------------------------------------------------------------------------

实现代码

/*** @brief 从缓冲区消费数据* @param cb 循环缓冲区指针* @param data 数据* @param length 数据长度* @return true 成功* @return false 失败 */
bool CircularBuffer_Consume(CircularBuffer* cb, uint8_t* rBuffer, size_t length);bool CircularBuffer_Consume(CircularBuffer* cb, uint8_t* rBuffer, size_t length) {if (length == 0) return true;if (length > CircularBuffer_Size(cb)) return false;if (cb->readP + length <= cb->bufferEnd) {memcpy(rBuffer, cb->readP, length);cb->readP += length;}else {size_t endLength = cb->bufferEnd - cb->readP + 1;size_t beginLength = length - endLength;memcpy(rBuffer, cb->readP, endLength);memcpy(rBuffer + endLength, cb->buffer, beginLength);cb->readP = cb->buffer + beginLength;}return true;
}

删除一批数据

只给出迭代器,删除它(不含)前面的所有数据的方法。

任意位置的删除,会让数据不再连续,要移动数据,这很复杂、而且没什么用,不做讨论。

说明

显然

删除一个迭代器前面的数据,只需要把头指针移动到迭代器的位置。

删除前

5678901234
bufferitwritePreadPend
------------------------------------------------------------------------------------------------

删除后

789
bufferreadPwritePend
------------------------------------------------------------------------------------------------

实现代码

/*** @brief 删除指针前的所有数据* @param cb 循环缓冲区指针* @param it 一个指向数据区结尾指针* @return true 成功* @return false 失败*/
bool CircularBuffer_EraseFront(CircularBuffer* cb, uint8_t* it);bool CircularBuffer_EraseFront(CircularBuffer* cb, uint8_t* it) {if (CircularBuffer_Empty(cb)) return false;if (it < cb->buffer || it > cb->bufferEnd) return false;size_t itMapIndex = (it - cb->readP) % (CIRCULAR_BUFFER_SIZE);//指针以数据头为开头的索引size_t tailMapIndex = CircularBuffer_Size(cb) - 1;//数据尾部,以数据头为开头的索引if (itMapIndex > tailMapIndex) return false;//目标不在数据区内,错误cb->readP = it;return true;
}

查找数据

通常是要查找通信协议的数据头

说明

以查找1 2 3 4为例

为了说明最后一次查找的位置last,我们弄个虚拟数组,他以数据区开头位置为开头。

为了之后比较方便,我们找last后面一个的位置lastBehind

在虚拟数组中

??????????
readPtailwriteP
1234
lastlastBehind
------------------------------------------------------------------------------------------------

在虚拟数组中最后一次查找的后面一个的位置是

lastBehindMap = tailMap - length + 1 + 1;
迭代器不需要返回开头的情况

找到前

??1234?
bufferreadPwritePend
1234
it->
------------------------------------------------------------------------------------------------

找到后

??1234?
bufferreadPwritePend
1234
it
------------------------------------------------------------------------------------------------
迭代器需要返回开头的情况

找到前

34????12
bufferwritePreadPend
1234
it->
------------------------------------------------------------------------------------------------

临近尾部要把目标数据拆分成两部分分别比较

34???12
bufferwritePreadPend
4123
it->
------------------------------------------------------------------------------------------------

找到后

34???12
bufferwritePreadPend
3412
it
------------------------------------------------------------------------------------------------

实现代码

/*** @brief 查找特定数据* @param cb 循环缓冲区指针* @param target 目标* @param length 目标长度* @return NULL 没有找到* @return 找到的目标指针
*/
uint8_t* CircularBuffer_Find(CircularBuffer* cb, const uint8_t* target, size_t length);uint8_t* CircularBuffer_Find(CircularBuffer* cb, const uint8_t* target, size_t length) {if (target == NULL) return NULL;if (length == 0) return CircularBuffer_Begin(cb);if (length > CircularBuffer_Size(cb)) return NULL;//虚拟数组中“最后一次比较的后一个”的索引size_t lastBehindMapIndex = CircularBuffer_Size(cb) - length + 1 + 1;//真实数组中“最后一次比较的后一个”的索引size_t lastBehindIndex = (cb->readP - cb->buffer + lastBehindMapIndex) % CIRCULAR_BUFFER_SIZE;//真实数组中“最后一次比较的后一个”的位置。uint8_t* lastBehind = cb->buffer + lastBehindIndex;uint8_t* it = cb->readP;while (it != lastBehind) {if (it + length <= cb->bufferEnd) {if (memcmp(it, target, length) == 0) {return it;}}else {size_t endLength = cb->bufferEnd - it + 1;size_t beginLength = length - endLength;if (memcmp(it, target, endLength) == 0 && memcmp(cb->buffer, target + endLength, beginLength) == 0) {return it;}}it++;if (it > cb->bufferEnd) it = cb->buffer;//超过范围,回到开头}return NULL;
}

完整代码

使用说明

生产者产生数据,追加进循环缓冲区->消费者查找协议头->删除前面的无效数据->消费数据包

append -> find -> eraseFront -> consume

C版本

Circularbuffer.h

#ifndef CIRCULAR_BUFFER_H
#define CIRCULAR_BUFFER_H#include <stdint.h>
#include <string.h>
#include <stdbool.h>
// 定义缓冲区大小(可根据需要修改)
#define CIRCULAR_BUFFER_SIZE 12// 结构体定义
typedef struct {uint8_t buffer[CIRCULAR_BUFFER_SIZE];     // 内部存储数组uint8_t* bufferEnd;                       // 指向 buffer[capacity - 1]uint8_t* writeP;                          // 写指针(当前写入位置)uint8_t* readP;                           // 读指针(当前读取位置)
} CircularBuffer;// 函数声明/*** @brief 初始化* @param cb 循环缓冲区指针*/
void CircularBuffer_Init(CircularBuffer* cb);
void CircularBuffer_Clear(CircularBuffer* cb);/*** @brief 数据长度* @param cb 循环缓冲区指针* @return 已存储的数据长度*/
size_t CircularBuffer_Size(const CircularBuffer* cb);/*** @brief 最大容量* @param cb 循环缓冲区指针* @return 最大容量*/
size_t CircularBuffer_Capacity(const CircularBuffer* cb);/*** @brief 是否为空* @param cb 循环缓冲区指针* @return true 是* @return false 否*/
bool CircularBuffer_Empty(const CircularBuffer* cb);/*** @brief 数据区开头* @param cb 循环缓冲区指针* @return 数据区开头指针*/
uint8_t* CircularBuffer_Begin(const CircularBuffer* cb);/*** @brief 数据区结尾* @param cb 循环缓冲区指针* @return 数据区结尾指针*/
uint8_t* CircularBuffer_End(const CircularBuffer* cb);/*** @brief 向缓冲区追加数据* @param cb 循环缓冲区指针* @param data 数据* @param length 数据长度* @return true 成功* @return false 失败*/
bool CircularBuffer_Append(CircularBuffer* cb, const uint8_t* data, size_t length);/*** @brief 从缓冲区消费数据* @param cb 循环缓冲区指针* @param data 数据* @param length 数据长度* @return true 成功* @return false 失败 */
bool CircularBuffer_Consume(CircularBuffer* cb, uint8_t* rBuffer, size_t length);/*** @brief 删除指针前的所有数据* @param cb 循环缓冲区指针* @param it 一个指向数据区结尾指针* @return true 成功* @return false 失败*/
bool CircularBuffer_EraseFront(CircularBuffer* cb, uint8_t* it);/*** @brief 查找特定数据* @param cb 循环缓冲区指针* @param target 目标* @param length 目标长度* @return NULL 没有找到* @return 找到的目标指针
*/
uint8_t* CircularBuffer_Find(CircularBuffer* cb, const uint8_t* target, size_t length);#endif // CIRCULAR_BUFFER_H

Circularbuffe.c

#include "CircularBuffer.h"// 初始化缓冲区(对应C++构造函数)
void CircularBuffer_Init(CircularBuffer* cb) {cb->bufferEnd = cb->buffer + CIRCULAR_BUFFER_SIZE - 1;cb->writeP = cb->buffer;cb->readP = cb->buffer;
}// 清空缓冲区
void CircularBuffer_Clear(CircularBuffer* cb) {cb->writeP = cb->buffer;cb->readP = cb->buffer;
}size_t CircularBuffer_Size(const CircularBuffer* cb) {return (cb->writeP - cb->readP + CIRCULAR_BUFFER_SIZE) % CIRCULAR_BUFFER_SIZE)
}size_t CircularBuffer_Capacity(const CircularBuffer* cb) {return CIRCULAR_BUFFER_SIZE - 1;// 最大容量(总空间 - 1,因为不能完全填满)
}bool CircularBuffer_Empty(const CircularBuffer* cb) {return cb->writeP == cb->readP;
}uint8_t* CircularBuffer_Begin(const CircularBuffer* cb) {if (CircularBuffer_Empty(cb)) return NULL;return cb->readP;
}uint8_t* CircularBuffer_End(const CircularBuffer* cb) {if (CircularBuffer_Empty(cb)) return NULL;size_t tailIndex = (cb->readP + CircularBuffer_Size(cb) - 1 - cb->buffer + CIRCULAR_BUFFER_SIZE) % (CIRCULAR_BUFFER_SIZE);return (cb->buffer + tailIndex);
}bool CircularBuffer_Append(CircularBuffer* cb, const uint8_t* data, size_t length) {if (length == 0) return true;if (length > (CircularBuffer_Capacity(cb) - CircularBuffer_Size(cb))) return false;if (cb->writeP + length <= cb->bufferEnd) {memcpy(cb->writeP, data, length);cb->writeP += length;}else {size_t endLength = cb->bufferEnd - cb->writeP + 1;size_t beginLength = length - endLength;memcpy(cb->writeP, data, endLength);memcpy(cb->buffer, data + endLength, beginLength);cb->writeP = cb->buffer + beginLength;}return true;
}bool CircularBuffer_Consume(CircularBuffer* cb, uint8_t* rBuffer, size_t length) {if (length == 0) return true;if (length > CircularBuffer_Size(cb)) return false;if (cb->readP + length <= cb->bufferEnd) {memcpy(rBuffer, cb->readP, length);cb->readP += length;}else {size_t endLength = cb->bufferEnd - cb->readP + 1;size_t beginLength = length - endLength;memcpy(rBuffer, cb->readP, endLength);memcpy(rBuffer + endLength, cb->buffer, beginLength);cb->readP = cb->buffer + beginLength;}return true;
}bool CircularBuffer_EraseFront(CircularBuffer* cb, uint8_t* it) {if (CircularBuffer_Empty(cb)) return false;if (it < cb->buffer || it > cb->bufferEnd) return false;size_t itMapIndex = (it - cb->readP) % (CIRCULAR_BUFFER_SIZE);//指针以数据头为开头的索引size_t tailMapIndex = CircularBuffer_Size(cb) - 1;//数据尾部,以数据头为开头的索引if (itMapIndex > tailMapIndex) return false;//目标不在数据区内,错误cb->readP = it;return true;
}uint8_t* CircularBuffer_Find(CircularBuffer* cb, const uint8_t* target, size_t length) {if (target == NULL) return NULL;if (length == 0) return CircularBuffer_Begin(cb);if (length > CircularBuffer_Size(cb)) return NULL;//虚拟数组中“最后一次比较的后一个”的索引size_t lastBehindMapIndex = CircularBuffer_Size(cb) - length + 1 + 1;//真实数组中“最后一次比较的后一个”的索引size_t lastBehindIndex = (cb->readP - cb->buffer + lastBehindMapIndex) % CIRCULAR_BUFFER_SIZE;//真实数组中“最后一次比较的后一个”的位置。uint8_t* lastBehind = cb->buffer + lastBehindIndex;uint8_t* it = cb->readP;while (it != lastBehind) {if (it + length <= cb->bufferEnd) {if (memcmp(it, target, length) == 0) {return it;}}else {size_t endLength = cb->bufferEnd - it + 1;size_t beginLength = length - endLength;if (memcmp(it, target, endLength) == 0 && memcmp(cb->buffer, target + endLength, beginLength) == 0) {return it;}}it++;if (it > cb->bufferEnd) it = cb->buffer;//超过范围,回到开头}return NULL;
}

C++版本

Circularbuffer.h

#pragma once#include <stddef.h>
#include <stdint.h>#define CIRCULAR_BUFFER_SIZE 12 //缓冲区大小,根据需要修改,必要的话动态吧,去构造函数中newclass CircularBuffer
{
private:uint8_t buffer[CIRCULAR_BUFFER_SIZE] = { 0x00 };//内部真正的数组uint8_t* const bufferEnd = buffer + CIRCULAR_BUFFER_SIZE - 1;//缓冲区真正的结尾uint8_t* writeP = this->buffer;//写指针,追加的位置uint8_t* readP = this->buffer;//读指针,消费数据的位置public:/*** @brief 清空此循环缓冲区* @note 可以使用此函数清空循环缓冲区*/void clear();public:/*** @brief 储存的数据量* @return 储存的数据量* @note 也就是未处理的数据长度*/size_t size();public:/*** @brief 最大容量* @return 最大容量*/size_t capacity();public:/*** @brief 是否为空* @return true 是* @return false 否*/bool empty();public:/*** @brief 数据区开头* @return 有数据 数据区开头的指针* @return 没有数据 nullptr*/uint8_t* begin();public:/*** @brief 数据区结尾* @return 有数据 数据区最后一个的指针* @return 没有数据 nullptr*/uint8_t* end();public:/*** @brief 向此缓冲区追加数据* @param data 要写入的数据指针* @param length 要写入的数据的长度* @return true 成功* @return false 失败*/bool append(const uint8_t* data, size_t length);public:/*** @brief 从此循环缓冲区中读出数据* @param rBuffer 读取到的缓冲区* @param length 要求读取的长度* @return true 成功* @return false 失败*/bool consume(uint8_t* rBuffer, size_t length);public:/*** @brief 删除迭代器前的所有数据* @param it 迭代器* @return true 成功* @return false 失败*/bool eraseFront(uint8_t* it);public:/*** @brief 查找特定数据* @param target 目标数据指针* @param length 目标数据长度* @return 找到 目标位置* @return 没有找到 nullptr*/uint8_t* find(const uint8_t* target, size_t length);public:/*** @brief 打印,测试用*/void printfCircularBuffer();
};

Circularbuffer.cpp


#include "CircularBuffer.h"
#include <string.h>void CircularBuffer::clear()
{//memset(cb, 0x00, CIRCULAR_BUFFER_SIZE);//其实不赋值也无所谓this->writeP = this->buffer;this->readP = this->buffer;
}size_t CircularBuffer::size()
{return  ((this->writeP - this->readP + (this->capacity() + 1)) % (this->capacity() + 1));	 
}size_t CircularBuffer::capacity()
{return CIRCULAR_BUFFER_SIZE - 1;//内部数组不允许存满
}bool CircularBuffer::append(const uint8_t* data, size_t length)
{if (length == 0)return true;if (length > (this->capacity() - this->size())) return false;//超过最大长度,错误if (this->writeP + length <= this->bufferEnd)//不需要返回开头的情况{memcpy(this->writeP, data, length);this->writeP += length;}else//需要返回开头的情况{size_t endLength = this->bufferEnd - this->writeP + 1;//结尾部分需要拷贝的数量size_t beginLength = length - endLength;//开头部分需要拷贝的数量memcpy(this->writeP, data, endLength);//拷贝到后面		memcpy(this->buffer, data + endLength, beginLength);//拷贝到前面this->writeP = this->buffer + beginLength;}return true;
}bool CircularBuffer::empty()
{return this->writeP == this->readP;
}uint8_t* CircularBuffer::begin()
{if (this->empty()) return nullptr;//没有数据return this->readP;
}uint8_t* CircularBuffer::end()
{if (this->empty()) return nullptr;//没有数据size_t tailIndex = (this->readP + this->size() - 1 - this->buffer + this->capacity() + 1) % (this->capacity() + 1);return this->buffer + tailIndex;
}bool CircularBuffer::consume(uint8_t* rBuffer, size_t length)
{if (length == 0) return true;if (length > size()) return false;//超过当前存储的长度,错误if (this->readP + length <= this->bufferEnd)//不需要返回开头的情况{memcpy(rBuffer, this->readP, length);this->readP += length;}else//需要返回开头的情况{size_t endLength = this->bufferEnd - this->readP + 1;//结尾部分需要拷贝的数量size_t beginLength = length - endLength;//开头部分需要拷贝的数量memcpy(rBuffer, this->readP, endLength);//拷贝后面的memcpy(rBuffer + endLength, this->buffer, beginLength);//拷贝前面的this->readP = this->buffer + beginLength;}return true;
}bool CircularBuffer::eraseFront(uint8_t* it)
{if (this->empty()) return false;if (it<this->buffer || it>this->bufferEnd) return false;//不在真实数组范围内,错误size_t itMapIndex = (it - this->begin()) % (this->capacity() + 1);//指针以数据头为开头的索引size_t tailMapIndex = this->size() - 1;//数据尾部,以数据头为开头的索引if (itMapIndex > tailMapIndex) return false;//目标不在数据区内,错误this->readP = it;return true;
}uint8_t* CircularBuffer::find(const uint8_t* target, size_t length)
{if (target == NULL) return nullptr;if (length == 0) return this->begin();if (length > this->size()) return nullptr;//目标长度超过当前数据长度,错误size_t lastBehindMapIndex = this->size() - length + 1 + 1;//虚拟数组中“最后一次比较的后一个”的索引size_t lastBehindIndex = (this->readP + lastBehindMapIndex - this->buffer) % (this->capacity() + 1);//真实数组中“最后一次比较的后一个”的索引uint8_t* lastBehind = this->buffer + lastBehindIndex;//真实数组中“最后一次比较的后一个”的位置。uint8_t* it = this->readP;//遍历时的迭代器while (it != lastBehind){//目前不涉及迭代器返回开头的情况if (it + length <= this->bufferEnd){if (memcmp(it, target, length) == 0){				return it;}}else{size_t endLength = this->bufferEnd - it + 1;//结尾部分需要比较的数量size_t beginLength = length - endLength;//开头部分需要比较的数量if (memcmp(it, target, endLength) == 0 && memcmp(this->buffer, target + endLength, beginLength) == 0){return it;}}it++;if (it > this->bufferEnd) it = this->buffer;//超过结尾,回到开头}return nullptr;
}#include <iostream>
#include <iomanip> 
void CircularBuffer::printfCircularBuffer()
{std::cout << "Size: " << this->size() << ", Capacity: " << this->capacity()<< ", Empty: " << (this->empty() ? "true" : "false") << "\n";std::cout << "buffer" << std::endl;for (size_t i = 0; i < this->capacity(); ++i) {// 将 uint8_t 转换为 int,并以两位十六进制输出std::cout << std::hex << std::uppercase<< std::setw(2) << std::setfill('0')<< static_cast<int>(this->buffer[i]) << " ";}std::cout << std::dec << std::endl; // 恢复十进制输出格式std::cout << "data" << std::endl;uint8_t* it = this->begin();for (size_t i = 0; i < this->size(); i++){// 将 uint8_t 转换为 int,并以两位十六进制输出std::cout << std::hex << std::uppercase<< std::setw(2) << std::setfill('0')<< static_cast<int>(*it) << " ";it = it + ((it - this->buffer + 1) % (this->capacity() + 1));//下一个位置}std::cout << std::dec << std::endl; // 恢复十进制输出格式std::cout << "readP index " << (this->readP - this->buffer) << std::endl;std::cout << "writeP index " << (this->writeP - this->buffer) << std::endl;std::cout << "\n\n";
}

后记

循环缓冲区一般配合生产者、消费者问题使用。

生产者产生数据,追加进循环缓冲区->消费者查找协议头->删除前面的无效数据->消费数据包

append -> find -> eraseFront -> consume

嵌入式似乎很忌讳动态内存分配(本人目前不清楚为什么)。

如果嵌入式项目中涉及大量数据结构的问题,请使用ETL库

ETL(Embedded Template Library) 是一个专为嵌入式系统和资源受限环境设计的轻量级 C++ 模板库,提供了类似 STL(标准模板库)的容器和算法,但更注重 确定性内存管理零动态内存分配低开销

etl::circular_buffer实现了循环缓冲区

但我没看懂怎么用

参考

循环缓冲区其它讲解:

https://docs.keysking.com/docs/stm32/example/UART_COMMAND

【【STM32】串口数据拆包?黏包?循环缓冲区帮你搞定!】 https://www.bilibili.com/video/BV1p75yzSEt9/?share_source=copy_web&vd_source=734d9fe130776e8ae82b2b5371a5f5b8

ETL说明

https://blog.csdn.net/gitblog_00682/article/details/142607246?fromshare=blogdetail&sharetype=blogdetail&sharerId=142607246&sharerefer=PC&sharesource=cctv1324&sharefrom=from_link

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

相关文章:

  • QNAP Duplicati 备份 123云盘
  • Java接口全面教程:从入门到精通
  • ai之paddleOCR 识别PDF python312和paddle版本冲突 GLIBCXX_3.4.30
  • C与指针4——指针
  • 每天一道面试题@第五天
  • 第九课认识倍数
  • 【C++】模板进阶
  • C++/SDL 进阶游戏开发 —— 双人塔防(代号:村庄保卫战 20)
  • 多协议 Tracker 系统架构与传感融合实战 第四章 IMU 与 UWB 传感融合框架
  • 基于Springboot旅游网站系统【附源码】
  • 步进电机中断函数解释
  • rhce第二次作业
  • 工作记录 2015-06-01
  • fastapi+vue中的用户权限管理设计
  • Seata RM的事务提交与回滚源码解析
  • 六大机器学习算法全解析:企业级开发实战与深度理解
  • AWS云服务深度技术解析:架构设计与最佳实践
  • Android Compose 物联网(IoT)UI 组件库封装指南
  • Dev-C++下载安装使用教程
  • 单细胞测序数据分析流程的最佳实践
  • Java学习手册:关系型数据库基础
  • 爬虫准备前工作
  • 【AI面试准备】NLP解析API文档生成测试脚本
  • 二叉树 - JS - 2
  • 49-dify案例分享-私有化 MCP 广场搭建与网页小游戏智能体工作流实战
  • 学习Cesium自定义材质
  • 硬件工程师面试常见问题(12)
  • 【LeetCode Hot100】贪心篇
  • 在pycharm profession 2020.3将.py程序使用pyinstaller打包成exe
  • Windows 中使用dockers创建指定java web 为镜像和运行容器