欢迎光临散文网 会员登陆 & 注册

C语言 int 整型转换为 2 进制字符串__按位操作实现

2023-01-02 08:31 作者:凫水亿  | 我要投稿

编译器版本: gcc.exe (Rev3, Built by MSYS2 project) 12.1.0

windows 版本: win11

VsCode版本: 1.70.1 (system setup)

经过几个月的刻苦学习,对 C 语言有有了新的了解,在本文中,将使用按位操作将 int 整型转换为 2 进制字符串。晦涩难懂的部分将给出解释,看不懂的地方可以多看几遍,或者在评论区进行讨论。


设计思路

在计算机中所有的数据都以 2 进制的形式储存,int 整型也不例外,我们以高位向低位的顺序依次读取每一位的数据,再将数据映射到 2 进制字符串中。无论在 win32 还是 win64 中,int 都以 4 字节 (Byte) 大小进行储存,每一个字节 (Byte) 占用 8 比特 ( bit ),数据位一共为 32 bit,即 32 位,我们知道 32 位的数据一共可以表示 2^32 次方个数,通过 32 个不同位置的 bit 进行表示。

8bit数据存储展示图


那么,要如何读取特定位置的信息呢?在 C 语言中,提供了一些按位操作符:&(按位与),|(按位或),^(按位异或) ,<<(左移),>>(右移),通过常用的这五个操作符,可以将特定位置的数据读取出来,如果想要查看更多按位操作符,可以在 <iso646.h> 头文件中查看,这里不在赘述。按位操作是有使用前提条件的,按位操作只对整型变量有意义,对其他如浮点型数据无意义,浮点类型数据以另一种方式进行存储。接下来简述这五个按位操作符的运算原则:按位与 —— &,双目运算符,两个相同位置上的位运算,两者为 1 ,结果为1。

8bit按位与运算

按位或—— |,双目运算符,两个相同位置上位运算,其中一者为 1,结果为 1。

8bit按位或运算

按位异或—— ^,双目运算符,两个相同位置上位运算,不带进位的加法。

8bit按位异或运算

左移——<<,双目运算符,将所有位向左移动 n 位,如 93 << 2,低位补零,高位销毁。

93 << 2

右移——>>,双目运算符,将所有的位向右移动 n 位,如 93 >> 2,高位补零,低位销毁。

93 >> 2

如上图所示,如果我们想提取数 93 第 7 位的数据,先使用按位或将低 6 位置 1,第 8 位置 1,然后使用按位异或将低 6 位置 0,第 8 位置 0 ,最后使用移位,将第 7 位数据移动到第 1 位即可知道该位是 1 还是 0。

获取第 7 位的数据

通过这种方法,可以读取任意位置的数据。我们从高位向低位读取数据,这样方便字符数组存储数据。

映射

代码实现

int 整型占用 4 字节 32 位,如果使用外部字符数组接收,其长度应当大于 32 ,但在实际开发过程中,我们为了能够更好优化代码,在函数内部定义了一个临时字符数组,其长度为 64,因此当外部数组的长度小于 64 时,函数退出,这是为了保证字符串数组不被截断或者溢出。

判断应当位于所有入栈数据之前,当发生错误时,不会因为其余数据入栈出栈而占用时间、空间上的开销。

我们知道函数出栈后,数据就会被丢弃,可以通过传递二级指针来实现函数内部操作外部数据。这是函数声明:

形参 number 不是无符号 int,其高位表示这个数是否为负数,高位为 1 时,这个数为负数,高位为 0 时这个数为正数,但是这种读取原则,可能干扰我们转换为 2 进制的思路,可以将接收进来的 number 使用 unsigned int 接收,2 进制是不分正负的,当一个特殊需求产生时,任何一个位都可以当做正负号。

无论在 win32,还是 win64 平台中,int 都是 4 字节,为了保险起见,我们使用 sizeof() 获取占用的空间大小,* 8 是得到所有的 bit 数目。

这是代码核心的部分,为了读取特定位的数据,我们需要两把刷子,将位左右的数据清洗掉。

数据清洗

代码最关键的部分,我们使用 i,j,分别指定左边刷子和右边刷子的位置,在系统中,如果直接左移或者右移 32 位,系统就会认定数据回到了原来的位置,所以位置不变,为了避免这种情况发生,我们先移动 31 位,再移动 1 位,移动其他位数时,不会产生影响。

左刷子 bitwiseLeft 是通过 0xFFFFFFFF 向左移 i 而得到的,在程序中刷子的位置是根据 i 定位的,i 随着程序运行逐渐变小,刷子逐渐向右移动。

右刷子 bitwiseRight 通过 0xFFFFFFFF 向右移 i 得到,在程序中刷子的位置根据 j 定位,j 随着程序运行而变大,刷子逐渐向右移动。

两个刷子中间总是保留一位的数据供读取。其运行轨迹如下图。

刷子移动方向

将刷子的行为细分,它其实分两个动作的,第一步,将不要的数据刷上漆,第二步,将刷漆的部分扯下来;它所对应的部分是 (_number | bitwiseRight) | bitwiseLeft 刷左漆与右漆,^ bitwiseLeft + bitwiseRight 将刷漆的部分扯下来。剩下的就是要提取的数据,该数据的位置通过 i 锁定,右移 i ,就可以将其移动到第一位,其值为 1 或 0,将其转换为字符 1 或者 0,需要加上 48 ,字符 '0' 的 ASCII 编码为 48,字符 '1' 的 ASCII 编码为 49,通过加上常数 48 就可转换为字符了,左边 binary 数组的索引是 j ,j 随着程序运行而增大,从 0 开始,至 31 结束,可以保存 32 位 2 进制值。

最后通过字符串复制函数将字符串复制到外部字符数组中。*recvArray 取 2 级指针的值,即外部字符数组的地址。


视觉优化

到这里应该就结束了,但实际运行中会出现很多 0 ,如输入17 得到 00000000000000000000000000010001,我们可以通过简单的语句将 0 消除,得到 10001。

感兴趣的朋友可以研究一下这段代码,说不定有意外收获,解答在代码展示下面。


代码展示


解答

在 for 括号中,定义了 size_t 类型的变量,它和指针类型的长度是一致的。所以 j = (size_t)--pValidChar 是不会报错的。

在括号中,定义了 i,j 两个变量,通过判断两个变量值是否一致来决定函数出口,for 循环中只有一句语句,这句语句本质上是一个三目运算,它分为判断语句  *pValidChar ++ == 49 ,? 后面会有两个分支,第一个分支是 ? 与 : 中间的部分,它可以是一个表达式,也可以是一个值,第二个分支是 : 与 ; 中间的部分,它可以是一个表达式,也可以是一个值。当前面判断语句结果为 true 时,返回第一个分支的值,若为 false 返回第二个分支的值;在此句中没有变量接收返回的值,所以值会被丢弃;在两个分支中还可以使用带返回值的函数如 printf();

17 转换为:10001 产生的输出

在三目运算符中 j = (size_t)--pValidChar 运算是被允许的,但 j = printf("hello j:%lld\n") 就会报错。


C语言 int 整型转换为 2 进制字符串__按位操作实现的评论 (共 条)

分享到微博请遵守国家法律