循环缓冲区

# 循环缓冲区

在这里插入图片描述

说明

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

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

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

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

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

定义

// 定义缓冲区大小(可根据需要修改)
#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.mzph.cn/diannao/81538.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式硬件篇---STM32 系列单片机型号命名规则

文章目录 前言一、STM32 型号命名规则二、具体型号解析1. STM32F103C8T6F103:C:8:T6:典型应用2. STM32F103RCT6F103:R:C:T6:典型应用三、命名规则扩展1. 引脚数与封装代码2. Flash 容量代码3. 温度范围代码四、快速识别技巧性能定位:F1/F4后缀差异硬件设计参考:引脚数…

MySQL 中日期相减的完整指南

MySQL 中日期相减的完整指南 在 MySQL 中&#xff0c;日期相减有几种不同的方法&#xff0c;具体取决于你想要得到的结果类型&#xff08;天数差、时间差等&#xff09;。 1. 使用 DATEDIFF() 函数&#xff08;返回天数差&#xff09; SELECT DATEDIFF(2023-05-15, 2023-05-…

传奇各版本迭代时间及内容变化,屠龙/嗜魂法杖/逍遥扇第一次出现的时间和版本

​【早期经典版本】 1.10 三英雄传说&#xff1a;2001 年 9 月 28 日热血传奇正式开启公测&#xff0c;这是传奇的第一个版本。游戏中白天与黑夜和现实同步&#xff0c;升级慢&#xff0c;怪物爆率低&#xff0c;玩家需要靠捡垃圾卖金币维持游戏开销&#xff0c;遇到高级别法师…

重塑数学边界:人工智能如何引领数学研究的新纪元

目录 一、人工智能如何重新定义数学研究的边界 &#xff08;一&#xff09;数学与AI的关系&#xff1a;从基础理论到创新思维的回馈 &#xff08;二&#xff09;AI的创造力&#xff1a;突破传统推理的局限 &#xff08;三&#xff09;AI对数学研究的潜在贡献&#xff1a;创…

IP伪装、代理池与分布式爬虫

一、动态代理IP应用&#xff1a;代理池的获取、选择与使用 代理池技术的核心是通过动态切换IP地址&#xff0c;让爬虫看起来像不同用户在访问网站&#xff0c;从而规避封禁。 &#xff08;一&#xff09;代理池的获取途径 1. 免费代理&#xff1a;低成本但高风险 免费代理可…

自然语言处理实战:用CRF打造高精度命名实体识别系统

## 一、从标签游戏到智能系统:命名实体识别的前世今生 在信息爆炸的互联网时代,我们每天面对的海量文本中隐藏着无数有价值的信息。想象一下,当你在浏览新闻时,系统能自动标红所有人名、地点和机构名称——这就是命名实体识别(NER)技术的魔力。从早期的规则匹配到如今的…

Space Engineers 太空工程师 [DLC 解锁] [Steam] [Windows]

Space Engineers 太空工程师 [DLC 解锁] [Steam] [Windows] 需要有游戏正版基础本体&#xff0c;安装路径不能带有中文&#xff0c;或其它非常规拉丁字符&#xff1b; DLC 版本 至最新全部 DLC 后续可能无法及时更新文章&#xff0c;具体最新版本见下载文件说明 DLC 解锁列表&…

JVM——JVM 是如何执行方法调用的?

JVM 是如何执行方法调用的&#xff1f; 在 Java 世界的底层运作中&#xff0c;方法调用机制是理解 Java 虚拟机&#xff08;JVM&#xff09;行为的关键之一。JVM 作为 Java 程序运行的核心&#xff0c;承担着执行字节码、管理内存、调度线程等多项职责。而方法调用作为程序逻辑…

MySQL 数据类型详解:字符串、数字、日期

MySQL 数据类型详解&#xff1a;字符串、数字、日期 在 MySQL 中&#xff0c;选择合适的数据类型对于数据库的存储效率和查询性能至关重要。MySQL 提供了**字符串&#xff08;String&#xff09;、数字&#xff08;Numeric&#xff09;和日期&#xff08;Date & Time&…

题解:P2485 [SDOI2011] 计算器

### 思路 本题是一个比较模板化的题目。 #### 一操作 考虑使用快速幂。 快速幂&#xff0c;只需要把 $k$ 变成二进制即可实现 $\Theta(\log k)$ 的时间复杂度。 实现方法&#xff1a; cpp long long qmi(long long a,long long k,long long p){ long long res 1; …

重新构想E-E-A-T:提升销售与搜索可见性的SEO策略

在2025年的数字营销环境中&#xff0c;谷歌的E-E-A-T&#xff08;经验、专业性、权威性、可信度&#xff09;已成为SEO和内容营销的核心支柱。传统的E-E-A-T优化方法通常聚焦于展示作者资质或获取反向链接&#xff0c;但这些策略可能不足以应对AI驱动的搜索和日益挑剔的用户需求…

JVM 一文详解

目录 JVM 简介 JVM 中的内存区域划分 1. 堆&#xff08;一个进程只有一份 ------ 线程共享&#xff09; 2. 栈&#xff08;一个进程可以有 N 份 ------ 线程私有&#xff09; Java 虚拟机栈&#xff1a; 本机方法栈&#xff1a; 3. 程序计数器&#xff08;一个线程可以…

小程序与快应用:中国移动互联网的渐进式革命——卓伊凡的技术演进观

小程序与快应用&#xff1a;中国移动互联网的渐进式革命——卓伊凡的技术演进观 在知乎看到很多&#xff1a;“懂王”发布的要把内行笑疯了的评论&#xff0c;卓伊凡必须怼一下&#xff0c;真印证那句话&#xff0c;无知者无畏 一、Web与小程序的技术本质差异 1.1 浏览器渲染…

[SC]SystemC在GPU/CPU SoC验证中的应用案例

SystemC在GPU/CPU SoC验证中的应用案例 摘要:SystemC 是一种基于 C++ 的系统级建模语言,广泛用于 SoC (System on Chip) 设计的建模和验证,尤其在 GPU SoC 验证中,SystemC 可用于模拟硬件模块、系统行为和性能评估。SystemC 的主要优势在于支持系统级抽象建模、时序…

Java 网络安全新技术:构建面向未来的防御体系

一、Java 安全架构的演进与挑战 1.1 传统安全模型的局限性 Java 平台自 1995 年诞生以来&#xff0c;安全机制经历了从安全管理器&#xff08;Security Manager&#xff09;到 Java 平台模块系统&#xff08;JPMS&#xff09;的演进。早期的安全管理器通过沙箱模型限制不可信…

sonar-scanner在扫描JAVA项目时为什么需要感知.class文件

1 概述 SonarQube是一个静态代码分析工具&#xff0c;主要用于检查源代码的质量&#xff0c;包括代码重复、潜在漏洞、代码风格问题等。而SonarScanner是SonarQube的客户端工具&#xff0c;负责将代码进行形态分析&#xff0c;并将结果发送到SonarQube服务器。所以&#xff0c…

媒资管理之视频管理

一:业务概述: 媒资管理这个模块是我负责开发的,主要的管理对象是视频,图片,文档等 包括文件的上传,视频的处理,文件的删除 (在媒资管理界面,有个上传视频的按钮,视频是在媒资这上传的,课程图片是在内容管理) 上传的图片和视频,会单独存储到搭建的分布式文件系…

Maven 实现多模块项目依赖管理

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

nuxt项目中引入并配置 iview

安装iview npm install iview --save注&#xff1a;想要加入其它的配置&#xff0c;可以在 nuxt.config.js 的 plugins 配置项中加入&#xff0c;同时在 plugins 文件夹下加入引入逻辑。 在nuxt.config.js文件中写&#xff1a; {src: ~plugins/iview, ssr: true}同时新建 plugi…

BG开发者日志505:项目总体情况

1、从2024年12月中旬启动&#xff0c;到4月底gameplay部分开发完毕&#xff0c;已经四个半月过去了。 其中大部分内容是3、4两个月中完成的&#xff0c;量产阶段。 预计6月初参加新品节&#xff0c;6月中旬发售&#xff08;比原计划7月中旬提前一个月&#xff09;。 --------…