首页 > 编程语言 >C/C++中拆分long/float/double等数据并重新组合的方法

C/C++中拆分long/float/double等数据并重新组合的方法

时间:2022-11-22 10:36:12浏览次数:72  
标签:u8 temp double float long data f32 buf cout


在嵌入式编程时,常常会遇到需要做数据通信的场景。单片机往往只支持一次8位的数据传递,为了传输较长的数据类型,只能先在主机将数据拆分,再在从机重新组合,这里介绍一些实用的数据拆分组合方法


文章目录

  • ​​一、数据类型分析​​
  • ​​1、类型长度​​
  • ​​2、类型存储方法​​
  • ​​(1)整形​​
  • ​​1、补码存储​​
  • ​​2、unsigned修饰​​
  • ​​(2)浮点形​​
  • ​​二、数据类型的拆分与合并​​
  • ​​(1)利用位运算​​
  • ​​(2)利用指针​​
  • ​​1、数据的拆分​​
  • ​​2、数据的拼接​​
  • ​​(1)分析​​
  • ​​(2)一种常见错误​​

一、数据类型分析

1、类型长度

C/C++中有多种数据类型,但不管什么类型的数据都是以二进制形式存储的,在不同的系统和编译器中,各种类型转换为二进制后的长度有时会不一样,可以利用sizeof函数来查看你的环境中数据类型的长度,如下:

#include "iostream"
#include "iomanip"
using namespace std;

int main()
{
cout<<left;
cout<<setw(18)<<"char:"<<sizeof(char)<<endl;
cout<<setw(18)<<"unsigned char:"<<sizeof(unsigned char)<<endl;

cout<<setw(18)<<"short:"<<sizeof(short)<<endl;
cout<<setw(18)<<"unsigned short:"<<sizeof(unsigned short)<<endl;

cout<<setw(18)<<"int:"<<sizeof(int)<<endl;
cout<<setw(18)<<"unsigned int:"<<sizeof(unsigned int)<<endl;

cout<<setw(18)<<"long:"<<sizeof(long)<<endl;
cout<<setw(18)<<"unsigned long:"<<sizeof(unsigned long)<<endl;

cout<<setw(18)<<"float:"<<sizeof(float)<<endl;
cout<<setw(18)<<"double:"<<sizeof(double)<<endl;

return 0;
}

运行程序,可以看到我的环境中各数据类型的长度如下

C/C++中拆分long/float/double等数据并重新组合的方法_#include

2、类型存储方法

从上面的示例中我们注意到,unsigned关键字不会改变类型长度,而且unsigned只能修饰整形数据,这些都是C/C++中类型存储方法决定的。

(1)整形

1、补码存储

整形数据在内存中用补码形式存储,补码定义最高位为符号位,0代表非负数,1代表负数。具体转换方法如下

非负数

负数

直接转换为二进制,高位用0填充

先得到此负数绝对值的补码(直接转二进制),然后最高位置1,再把除了最高位以外的所有数取反,最后对结果再加1

举例来说,定义一个int型的变量a

a=4

a=-4

补码0000 0000 0000 0000 0000 0000 0000 0100

补码1111 1111 1111 1111 1111 1111 1111 1100

a=4的情形一目了然,这里分析一下a=-4的情况是怎么得到的

  1. 首先得到a绝对值4的补码,注意int是4个字节:0000 0000 0000 0000 0000 0000 0000 0100
  2. 符号位置1:1000 0000 0000 0000 0000 0000 0000 0100
  3. 除符号位以外全部取反:1111 1111 1111 1111 1111 1111 1111 1011
  4. 整体加一:1111 1111 1111 1111 1111 1111 1111 1100

在C/C++中,用十六进制或八进制输出数据,即可看到补码的效果

#include "iostream"
#include "iomanip"
using namespace std;

int main()
{
int a=-4;
int b=4;
cout<<left<<hex;
cout<<a<<endl<<b<<endl;

return 0;
}

结果如下图所示,与我们的分析相符

C/C++中拆分long/float/double等数据并重新组合的方法_#include_02

2、unsigned修饰

unsigned关键字强制程序不考虑符号位,但不会改变整形数据补码存储的存储方式。也就是说,程序会按上面的方法将变量值转换为补码,然后直接转为十进制数。
在这种情况下,-4会先被存为0xfffffffc,再转十进制为4294967292,这一点也可以编程验证

#include "iostream"
#include "iomanip"
using namespace std;

int main()
{
unsigned int a=-4;
cout<<left;
cout<<a<<endl;
cout<<hex<<a<<endl;
return 0;
}

C/C++中拆分long/float/double等数据并重新组合的方法_数据_03


因此unsigned修饰过的数据类型与未经修饰过类型长度一样就显而易见了

(2)浮点形

浮点型数据采用IEEE格式,与整形的存储格式完全不同,也不能用unsigned进行修饰,具体可以参考这篇文章:
​​​单双精度浮点数的IEEE标准格式​

二、数据类型的拆分与合并

(1)利用位运算

说道数据的拆分与合并,本质上就是把数据按8位长度拆开与拼装,首先想到的就是利用位运算处理。
按位与​​​&​​​运算可以用来拆分,按位或​​|​​运算可以用来合并,关于位运算可参考我的这篇文章:​​C语言位运算应用实例​​

下面是一个利用位运算拆分与合并4字节长long型数据的例子

#include "iostream"
#include "iomanip"
using namespace std;

typedef unsigned char u8;
typedef long s32;

//拆分数据
void dataSplit(s32 data,u8 *buf)
{
s32 temp=0xFF;
for(int i=0;i<4;i++)
buf[i]=(data & temp<<8*i)>>8*i;

/*
buf[0]=data & 0xFF;
buf[1]=(data & 0xFF00)>>8;
buf[2]=(data & 0xFF0000)>>16;
buf[3]=(data & 0xFF000000)>>24;
*/

}

//拼接数据
void dataAssmeble(s32 *data,u8 *buf)
{
s32 temp=buf[3];
for(int i=2;i>=0;i--)
temp=(temp<<8)|buf[i];

*data=temp;
}

int main()
{
s32 a=-1024;
s32 res;

u8 buf[4];
dataSplit(a,buf); //拆分,主机可以发送
dataAssmeble(&res,buf); //合并,从机接收后可以拼装

cout<<"原始数据:"<<a<<endl;
cout<<"拆分合并后:"<<res<<endl;

return 0;
}

C/C++中拆分long/float/double等数据并重新组合的方法_数据_04


可见处理正确

需要注意的一点是,这种方法只适用于处理整形数据因为浮点型数据的存储比较特殊,强行规定了各个位域的含义,若直接进行位运算取出一部分,取出的数据无法被正确解释,所以浮点型不能直接位运算,也就不能直接用这种方法处理
看到这里有人可能会想:若想先把浮点型转成整形,处理后再转回来不就好了吗。注意,别忘了浮点型转整形时会丢失精度,这个方法也不太好

(2)利用指针

大家应该注意到了,这个问题在本质上涉及到如何把内存中的数据解释成变量值,在这一点上或许指针可以帮到我们。我们知道,当你用一个指针指向内存中的一段数据,这段数据就会被解释为这个指针的类型的变量值。这启发我们用以下方法处理:

  1. 不要进行任何类型转换,以免破坏原始数据
  2. 找到一个方法,可以在不破坏数据的情况下将其拆开为8位一组,这个需要利用指针
  3. 现在可以进行数据传输
  4. 接收到数据后,用位运算把它们按序拼接为一个足够长的整形
  5. 定义一个原变量类型的指针,指向拼接成的整形的地址
  6. 取出指针指向的变量值,这就是被发送的原始变量的值了

示例程序如下:

#include "iostream"
#include "iomanip"
using namespace std;


typedef unsigned char u8;
typedef float f32;
typedef unsigned long u32;

//拆分数据
void dataSplit(f32 data,u8 *buf)
{
for(int i=0;i<4;i++)
buf[i]=(*((u8 *)(&data)+i));
}

//拼接数据
f32 dataAssmeble(u8 *buf)
{
u32 temp=buf[3];
for(int i=2;i>=0;i--)
temp=(temp<<8)|buf[i];

f32 *data=(f32*)(&temp);
return *data;
}

int main()
{
f32 a=-3.456;
f32 res;

u8 buf[4];
dataSplit(a,buf); //拆分
res=dataAssmeble(buf); //合并


cout<<"原始数据:"<<a<<endl;
cout<<"拆分合并后:"<<res<<endl;

return 0;
}

注意拆分与拼接的程序出现了变化,下面详细分析一下

1、数据的拆分

//拆分数据
void dataSplit(f32 data,u8 *buf)
{
for(int i=0;i<4;i++)
buf[i]=(*((u8 *)(&data)+i));
}
  1. (&data)取出原始数据data的地址
  2. (u8 *)(&data),用一个u8(即unsigned char)型指针指向这个地址
  3. ((u8 *)(&data)+i),指针加减法会移动指向位置,这里按u8长度为一个单位进行移动,从而依次指向原始数据中的每一段u8数据
  4. (*((u8 *)(&data)+i)),将这个指针的值取出,也就是取出了原始数据中的每一段u8数据的值

2、数据的拼接

(1)分析

//数据拼接
f32 dataAssmeble(u8 *buf)
{
u32 temp=buf[3];
for(int i=2;i>=0;i--)
temp=(temp<<8)|buf[i];

return *(f32*)(&temp);
}
  1. 将若干u8数据用位运算拼成一个长度相同的整形数据temp
  2. (&temp) 把temp的地址取出
  3. (f32*)(&temp),用一个(f32*)类型指针指向这个地址,确认了解释方法
  4. (f32)(&temp)取出这个指针的值

(2)一种常见错误

需要注意的是,以下写法是错的

//错误的拼接
void dataAssmeble(f32 *data , u8 *buf)
{
u32 temp=buf[3];
for(int i=2;i>=0;i--)
temp=(temp<<8)|buf[i];

data=(f32*)(&temp);
}
//-----------------------------------------
//和这个犯得是一样的错误
void func(int a)
{
a=10;
}

还记得初学C/C++时函数传参那部分的内容吗?看下面的fun函数,它并不能对a的实参产生影响,因为这里只是对局部变量形参a做了赋值,函数func退出后就没了
上面的程序也是一样的错误,我们只是对局部的形参指针*data进行了赋值,并没能影响实参指针

上面func函数的错误可以用指针来解决,dataAssmeble函数也可以类似地用双指针解决,参考如下:

//类似这种方法改正
void func(int *a)
{
*a=10;
}

//--------------------------------------
#include "iostream"
#include "iomanip"
using namespace std;


typedef unsigned char u8;
typedef float f32;
typedef unsigned long u32;

void dataSplit(f32 data,u8 *buf)
{
for(int i=0;i<4;i++)
buf[i]=(*((u8 *)(&data)+i));
}

void dataAssmeble(f32 **data , u8 *buf)
{
u32 temp=buf[3];
for(int i=2;i>=0;i--)
temp=(temp<<8)|buf[i];

f32 *p=(f32*)(&temp);//暂存
**data=*p;
}

int main()
{
f32 a=-3.456;
f32 res;
f32 *p=&res;//暂存

u8 buf[4];
dataSplit(a,buf); //拆分
dataAssmeble(&p,buf); //合并


cout<<"原始数据:"<<a<<endl;
cout<<"拆分合并后:"<<res<<endl;

return 0;
}

欢迎讨论~


标签:u8,temp,double,float,long,data,f32,buf,cout
From: https://blog.51cto.com/u_15887260/5876707

相关文章