3个bytes, 怎么接?

这个问题,对于熟悉C语言的人来说,答案很简单。

不过对我这种不熟悉C语言的人,在坑中「摸索」良久,先后尝试好几种方法。

其实,生活中很多事情也像编程:解决问题的办法有万千,但某些方法确实是比较优雅的。

在这个「摸索」的过程中,也是一个蛮有趣的过程,遂记之。

问题

在过去的项目中,所接触到的「协议/指令(protocol/command)」,数据大多是以1个byte(字节),2个bytes,4个bytes,8个bytes......为单位进行切割组合的。类似如下指令:

xxxCommand:

  • [1]commandID# 0xFF // 方括号数字:该数据所占字节(byte)数
  • [1]week:
    • bit0:Mon
    • bit1:Tue
    • bit2:Wed
    • bit3:Thu
    • bit4:Fri
    • bit5:Sat
    • bit6:Sun
    • bit7:不使用
  • [4]ip address
  • [2]reserved
  • [1]checksum

这时候,1 byte的数据,用UInt8接,2 bytes的数据,用UInt16接,4 bytes的数据,用UInt32接——一切都很美好。

关于UInt8UInt16UInt32等数据类型,在MacTypes.h中,有相关说明:

/********************************************************************************

    Base integer types for all target OS's and CPU's
    
        UInt8            8-bit unsigned integer 
        SInt8            8-bit signed integer
        UInt16          16-bit unsigned integer 
        SInt16          16-bit signed integer           
        UInt32          32-bit unsigned integer 
        SInt32          32-bit signed integer   
        UInt64          64-bit unsigned integer 
        SInt64          64-bit signed integer   

*********************************************************************************/

也有具体定义:

    typedef unsigned char                   UInt8;
    typedef signed char                     SInt8;
    typedef unsigned short                  UInt16;
    typedef signed short                    SInt16;

而最近,遇到一种新情况:硬件那边发过来的数据,是3个bytes为单位的数据——有3个bytes的mac地址(截取了mac地址的一半,发送/广播给手机端),也有3个bytes的大气压数据。类似如下数据格式:

xxxCommand:

  • [2]UUID
  • [3]mac // 截取了mac地址的一半
  • [3]presure // 据闻大气压数值,2 bytes表示不完,4 bytes又太多了,所以定义了3 bytes~
  • ...

于是,就有了此文的标题:系统没有UInt24,3个bytes的数据,怎么接?(不要怪我问那么白痴的问题)

先贴出我所认为的「最优雅」解决方案,再描述一下我「踩坑」的心路历程。

Solution:

1.先定义一个UInt24

关于如何定一个UInt24,StackOverFlow上有人提问:How to define 24bit data type in C?

尝试过某个回答者的做法:

    struct int24{
        unsigned int data : 24;
    };

经验证,这个写法不work,因为这个类型还是占4个bytes(用sizeof()函数打印验证),这样拿去接数据,会把别人的那个byte也装过来,后面的数据就会乱掉。

那试着仿照MacTypes.h里的定义,定义如下:

typedef unsigned char[3] UInt24;

这样OK吗?事实上,也有问题,系统会报如下错误:

Brackets are not allowed here; to declare an array, place the brackets after the identifier. Replace '[3] UInt24' with ' UInt24'

报错说得很清楚:方括号放错地方。要定义一个array(数组/数列),方括号应该放在新定义类型名称的后面:

typedef unsigned char UInt24[3];

这样就OK了。

2.定义command

有了对应的「容器」装数据,那接下来可以定义command(指令)了:

    typedef struct __attribute__((packed)) {
        UInt16 UUID;
        UInt24 mac; // 用自己定义的UInt24接数据 
        UInt24 pressure;
    } D2MXxxCommand; // D2M: Device to Mobile phone

其实到这里,基本就把问题解决了,后面该干嘛干嘛了。但是在获取到数据,显示出来的过程中,有些写法还是刷新了我的认知(主要还是自己对C语言不熟)。

  • 将mac地址的3个bytes转为十六进制形式的字符串

一开始我用了很复杂的方法,网上查到的方法也大都比较复杂(下面会有叙述)。

而实际上,只需要一行就OK了:

NSString *macHexString = [NSString stringWithFormat:@"%02X%02X%02X", cmd->mac[0], cmd->mac[1], cmd->mac[2]];

正常的占位符应该是%X,而这里中间的02,表示该十六进制数限制固定两位数。

目的是预防这种情况:当第一个byte是小于16的数,只输出1位。例:0x014B5C,如果是用%X,则只输出14B5C;而用%02X,则可输出014B5C

直接用%02X,就无须再额外判断第一个byte长度是否小于1,如果小于1,再在前面补个零……

备注:这个写法,参考了以前公司boss的写法。

  • 将pressure(大气压)的3个byte转为十进制浮点数

比如,我们用UInt24接了一个数:0x0185B2(大气压),要转换为十进制的浮点数:

   // 大气压值 = 十进制值 / 100 
   float pressure = ((cmd->pressure[0]<<16) + (cmd->pressure[1]<<8) + cmd->pressure[2]) * 0.01; 

因为大气压的值,同事定义为:该十六进制数的十进制形式再除以100。所以,思路就是将该3个独立的byte组合成一个完整的数,再转十进制就OK了。

cmd->pressure[0]<<16的意思,就是将pressure中第一个byte左移16bit(位),也就是左移2个byte(字节)的位置——所以操作完后,pressure中第一个byte,从右往左数,就变成是第三个byte了。如下图所示(将0x01往左移16bit(位)):

将0x01往左移16bit(位)

cmd->pressure[1]<<8也做了类似的事情,将pressure中第二个byte左移8bit(位),也就是左移1个byte(字节)的位置,如下图(将0x85往左移8bit(位)):

将0x85往左移8bit(位)

最后把他们加起来,就是我们要的数了:997.62(Hpa)

另外,

float pressure = ((cmd->pressure1 * 65536) + (cmd->pressure2 * 256) + cmd->pressure3) * 0.01

也有同样的效果,但个人认为这样操作,没有用<<操作符直观易懂。

以上,就是关于「3个bytes, 怎么接?」的回答。

接下来描述一下踩过的「坑」。

其他的尝试:

1.定义成3个单独的UInt8

最开始想到,就是单独定义3个UInt8来接数据:

    UInt8 pressure1;
    UInt8 pressure2;
    UInt8 pressure3; 

写完这个还「怨气满满」地想:为什么非得要传3个bytes过来,多一个、少一个不行吗?

这埋怨虽是戏言,但是从「产品、消费者」的角度思考,又可以延伸到另外一件事:我们写的框架、软件、产品,有一个重要的准绳——「把复杂留给自己,把简单留给客户」。大部分人拿到一个东西,肯定希望是「插电即用」的,并不希望东折腾西捣鼓才能使用。

扯远了,继续:拿到这3个bytes后,第一反应就是NSData对象——于是就变着法把这三个bytes捣鼓成NSData对象:

    // 将3个bytes重新组合起来
    Byte pressureBytes[] = {cmd->pressure1, cmd->pressure2, cmd->pressure3};
    // 转为NSData
    NSData *pressureData = [NSData dataWithBytes:pressureBytes length:sizeof(pressureBytes)];

然后又想办法将NSData对象捣鼓成十六进制字符串,或者是十进制的浮点数——硬生生把一行代码搞定的事情,写成了几十行。

2.定义成UInt8 mac[3]

定义成UInt8 mac[3]形式,其实这和最上面定义UInt24是类似的,只是最上面的方法起了一个更易于理解的UInt 24而已。

3.用UInt32接数据,再截前面3个bytes

这种方法也work,不过要注意,UInt32接回来的数据是4bytes,最后一个byte要进行正确处理(正确给到其他需要的地方),否则后面数据的读取全会乱(少一个byte)。

另外,还试过定义成char *mac形式,不work,因为sizeof(cmd->mac)是8,一个指针占用了8 bytes,并不是我们想要的3bytes。

所以,

还要继续熟悉C语言。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏流川疯编写程序的艺术

windows linux—unix 跨平台通信集成控制系统----系统硬件信息获取

控制集成系统需要了解系统的各项硬件信息,之前我们设计的时候,习惯使用c函数来搞,后来可能发现程序的移植性收到了一些影响,比如unix内核的一些c函数在linux...

13030
来自专栏用户2119464的专栏

字节跳动EE部门前端面试经历及总结

你好,我是星辉,幸会幸会。 今天下午我参加了字节跳动EE部门的前端视频第一次面试,把它记录总结下来,希望能够对大家带来帮助。

1.5K20
来自专栏程序员互动联盟

编程语言中,c#、Python、JavaScript哪一个更接近c语言?

不要尝试比较几种编程语言的优劣,任何一种编程语言都有其存在的价值,适合的就是最好的,现在编程领域Python,JAVA等等发展势头非常迅猛,但并不意味着所有的企...

37720
来自专栏诸葛青云的专栏

C语言编程中的“堆”和“栈”七大不同之处

对于编程初学者来说会接触到一些难以理解的名称,比如堆(heap)、栈(stack)、堆栈(stack)等。初学开发过程中往往让人混淆不清。今天我们来谈谈堆和栈的...

20520
来自专栏石开之旅

小甲鱼《零基础学习Python》课后笔记(二):用Python设计第一个游戏

BIF(Built-in Functions)是Python的内置函数,为了方便程序员快速编写脚本程序。

35030
来自专栏程序员的碎碎念

LeeCode 每日一题121:股票卖出的最佳时机

首先来看看暴力解决这道题的算法,以类似冒泡算法的方式,两层遍历整个数组确定最大利润, 这种方式最蠢, 最容易想到. 在 LeeCode 中, C语言凭借更好的性...

11620
来自专栏老九学堂

最难学的十大编程语言 Java排第三 它竟是第一名!

编程语言是开发者们代码工作的核心,也是许多开发者最爱讨论的话题。编程语言的选择对开发者和工具制造商都十分重要,前者需要保持最新和具备市场潜力的技能,后者则亟需确...

45520
来自专栏Nicky's blog

Java编写的C语言词法分析器

    这是java编写的C语言词法分析器,我也是参考很多代码,然后将核心代码整理起来,准备放在QQ空间和博客上,目的是互相学习借鉴,希望可以得到高手...

25120
来自专栏代码男人

我与Android过往,从此更需努力,揭开新的篇章。

       早就写篇关于自己从接触Android到现在的文章,怕是更多的心灵鸡汤,或者自己的侃侃而谈,今天,在南京工作的倒数第二天,提笔写来,希望能够结识更多...

11030
来自专栏流川疯编写程序的艺术

leetcode 155 Min Stack

Design a stack that supports push, pop, top, and retrieving the minimum element ...

12120

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励