手撕代码: C++实现按位序列化和反序列化

news/2025/1/15 10:09:03 标签: c++, 开发语言, 设计模式, 模板

目录

1.需求

2.流程分析

3.实现过程

4.总结


1.需求

        在我们正在开发的项目,有这样一种需求,实现固定格式自由格式比特流无线传输。解释一下,固定格式形如下面表格:

每个字段都有位宽、类型等属性,这种固定格式一般总位宽都是固定的,比如72比特(9个字节)。它的序列化过程就是把比特数据转换成字节流,反序列化过程即是把字节流转换成比特数据。

自由格式形如下面表格:

它的最大位宽是固定的,但是总位宽是不固定的,用户可以自定义每个字段;自定义格式的序列化和反序列化跟固定格式的流程是差不多的。

2.流程分析

以下面自定义格式为例来说明实现的过程:

自定义格式的组包过程如下:

1)字段1为2个比特,值为1,二进制为0b00000001,即把0b01移到第1个字节的低两位。

2)字段2为5个比特,值为5,二进制为0b00000101,即把0b00101移到第1个字节的第2位到第6位。

3)字段3为3个比特,值为2,二进制为0b00000010,即把0b010的最低比特0移到第1个字节的第7位,高两个比特01移到第2字节的低两个比特上。

4)字段4、字段5、字段6依次做类似处理,最后得到一个字节流如上图的底部所示。

解包过程即是上面的流程反过来,就不在这里赘述了。

3.实现过程

单个字段组包实现过程代码如下:

int CJField::getData(char* pData, int len, int& currIndex, int& reserveBit) const {
	constexpr quint8 nShift[] = { 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF };
	int nBitWidth = m_bitWidth;
	qint64 value = m_value;
	if (reserveBit <= 0) {
		currIndex++;
		reserveBit = 8;
	}
	while (nBitWidth > 0) {
		if (nBitWidth > reserveBit) {
			pData[currIndex] |= ((value & nShift[reserveBit - 1]) << (8 - reserveBit));
			value >>= reserveBit;
			currIndex++;
			nBitWidth -= reserveBit;
			reserveBit = 8;
		}
		else {
			pData[currIndex] |= ((value & nShift[nBitWidth - 1]) << (8 - reserveBit));
			reserveBit -= nBitWidth;
			nBitWidth = 0;
		}
	}
	return 1;
}

单个字段解包实现过程代码如下:

int  CJField::setData(const char* pData, int len, int& currIndex, int& reserveBit) {
	constexpr quint8 nShift[] = { 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF };
	int nBitWidth = m_bitWidth;
	qint64 value = 0;
	if (reserveBit <= 0) {
		currIndex++;
		reserveBit = 8;
	}
	while (nBitWidth > 0) {
		if (nBitWidth > reserveBit) {
			value |= ((qint64)((pData[currIndex] >> (8 - reserveBit)) & nShift[reserveBit - 1]) << (m_bitWidth - nBitWidth));
			nBitWidth -= reserveBit;
			reserveBit = 8;
			currIndex++;
		}
		else {
			value |= ((qint64)((pData[currIndex] >> (8 - reserveBit)) & nShift[nBitWidth - 1]) << (m_bitWidth - nBitWidth));
			reserveBit -= nBitWidth;
			nBitWidth = 0;
		}
	}
	m_value = value;
	calcPhyFromValue();
	return 1;
}

自定义格式整体组包过程代码如下:

    /// <summary>
	/// //组包
	/// </summary>
	/// <param name="pData"></param>
	/// <param name="nLen"></param>
	/// <returns></returns>
	int getData(char* pData, int& nLen) {
		int  nCurrIndex = 0;
		int  nReserveBit = 8;
		//[1]
		if (m_vecMsgFields.size() <= 0)
			return 0;

		//[2]
		for (auto& it : m_vecMsgFields) {
			it->getData(pData, nLen, nCurrIndex, nReserveBit);
		}
		//[3]填充数据,长度为9的倍数
		nLen = nCurrIndex + 1;
		while (nLen % 9 != 0) {
			nLen++;
		}
		return 1;
	}

自定义格式整体解包过程代码如下:

    /// <summary>
	/// 解包
	/// </summary>
	/// <param name="pData"></param>
	/// <param name="nLen"></param>
	/// <returns></returns>
	int setData(const char* pData, int nLen) {
		int  nCurrIndex = 0;
		int  nReserveBit = 8;
		//[1]
		if (m_vecMsgFields.size() <= 0)
			return 0;
		//[2] 根据模板,解析内容
		for (auto& it : m_vecMsgFields) {
			it->setData(pData, nLen, nCurrIndex, nReserveBit);
		}
		return 1;
	}

固定格式和自由格式的实现差不多,就不在这里赘述了。

4.总结

        上述实现的按位序列化和反序列化的过程并不是很复杂;看着上面的过程,可以自己动手写写其实现过程,就会真正理解它。

        你要相信一句话:纸上得来终觉浅,绝知此事要躬行。

        如果需要此实现的详细源代码,可以后台@我。


http://www.niftyadmin.cn/n/5823826.html

相关文章

LSM6DSV16XTR STM32 硬件spi驱动

在使用LSM6DSV16XTR时硬件i2c接口被占用,不想要共用其他设备所以就把芯片的接口从i2c改为spi接口。硬件接线如下&#xff1a; spi配置如下&#xff1a; 在驱动代码中做如下更改&#xff1a; int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp, uint16_t…

IDEA编译器集成Maven环境以及项目的创建(2)

选择&#xff1a;“File” ---> "Othoer Setting" --> "Settings for New Projects..." --->搜索“Maven” 新建项目 利用maven命令去编译这个项目 利用maven去打包

贪心算法详细讲解(沉淀中)

文章目录 1. 什么是贪心算法&#xff1f;&#xff08;贪婪鼠目寸光&#xff09;经典例题1.1.1 找零问题1.1.2最小路径和1.1.3 背包问题 2.贪心算法的特点2.1 证明例1 3.学习贪心的方向心得体会 1. 什么是贪心算法&#xff1f;&#xff08;贪婪鼠目寸光&#xff09; 贪心策略&a…

ES6的高阶语法特性

一、模板字符串的高级用法 1.1.模板字符串的嵌套 模板字符串的嵌套允许在一个模板字符串内部再嵌入一个或多个模板字符串。这种嵌套结构在处理复杂数据结构或生成具有层级关系的文本时非常有用。 1. 嵌套示例 假设我们有一个包含多个对象的数组&#xff0c;每个对象都有名称、…

Golang——GPM调度器

本文详细介绍Golang的GPM调度器&#xff0c;包括底层源码及其实现&#xff0c;以及一些相关的补充知识。 文章目录 前情提要并发与并行并行 (Parallel)并发 (Concurrency)关键区别 进程和线程的区别协程解决的问题协程的优势 Go的并发模型-CSPGo的调度模型-GPM源码Goroutineg 结…

数仓建模(三)建模三步走:需求分析、模型设计与数据加载

本文包含&#xff1a; 数据仓库的背景与重要性数据仓库建模的核心目标本文结构概览&#xff1a;需求分析、模型设计与数据加载 目录 第一部分&#xff1a;需求分析 1.1 需求分析的定义与目标 1.2 需求分析的步骤 1.2.1 业务需求收集 1.2.2 技术需求分析 1.2.3 成果输出…

数据结构和算法-07平衡二叉树-01

树的发展历程 来认识我们的树结构 平衡二叉树 如果得知二叉树是否平衡&#xff0c;直接的方法是左右子树的高度值不大于1。我们在节点中添加height指标用来检验二叉树的高度差。 规范&#xff1a;如果为空&#xff0c;高度 0。默认产生一个节点高度为&#xff1a; 1 tips&…

微服务之松耦合

参考&#xff1a;https://microservices.io/post/architecture/2023/03/28/microservice-architecture-essentials-loose-coupling.html There’s actually two different types of coupling: runtime coupling - influences availability design-time coupling - influences…