c语言位操作的高级应用

c语言里下有丰富的位运算,它们能很好的帮助我们对位进行操作

~、<<、>>、&、^、|、&=、^=、|=、<<=、>>=

这些位运算里,常用的有就是:与运算(&),位移运算(<< >>),还有或运算和异或运算(|,^)

许多c语言初学者可能只知道这些运算的意思,但是很少在实际编程中去用到它,其次是即便已经参与工作的程序员来说,也相对的较少用到它们,但是这些运算在Linux内核里非常容易见到,还有底层开发人员(单片机和嵌入式),因为底层开发人员需要对bit位进行转换(高低电平)

Linux内核里特别喜欢使用大量的位运算,这样有效加速运算,其次也显得很酷

如你想要做一个乘法,且这个数是2的倍数那么你可以这样做:

a = a * 64;

用位运算:

a = a << 6;

这样CPU就无需去调用ALU运算单元来执行对应的加法指令集,这样CPU只需要把a这个变量上的bit位向左移动6位就可以了。

几乎可以节约很大程度的计算,因为乘法需要累加器,而且不是一次就能做完的,详细可以参考我写的这篇文章:加减乘除实现

而位移就不一样了,只是简单的取出变量的bit,然后移动一下,在放回去,效率可以质上的提升。

这里解释一下,为什么2的倍数可以以位移的方式来完成

bit是以2的次方为单位的,这点学过计算机原理的应该都很清楚,从低位到高位每一次都是一个次方的叠加

而从低位到高位上面的64,刚好位移6次可以得到,所以把a变量的原bit,左移6位,随着次方的叠加,就相当于乘上64

有兴趣的小伙伴可以试试,把a的值转化为二进制数,然后右移6位,看一下算出的值是否与乘法结果后的值一样。

 

当我们写程序时,想要传递一些参数时

如有这样的一个场景,我们编写了一个程序,这个是一个窗体组件,是以接口形式提供给用户的,当用户调用时不可避免的需要传递一些参数

如:

组件的样式,标题,宽度和高度,x和y坐标等

就拿最简单的样式吧,样式可以有多种,如,凹凸,平滑,无边框,且这些样式允许同时存在,我们不可能傻乎乎的写很多样式宏吧?

类似这种:

#define 凹凸 0

#define 平滑 1

#define 无边框 2

#define 凹凸加平滑 3

#define 凹凸加无边框 4

...

类似这种的,还有使用大量参数的,这种写法我也见过,我觉得这样非常浪费内存空间,以及开发效率,和代码质量

这些样式都是很简单的1-2-3这样的数字编号组成的,非常小,以至于8个bit位就能表示

如果我们使用一个int类型,把低8位用来存储凹凸,中间八位用来存储平滑,高八位用来存储无边框

然后在接口里取出int类型的低八位和中间八位还有高八位,然后判断是否有值就可以了,很简单

int是32位(随编译位数),这样的情况下还有八位可以当作保留,那么这样的好处就在于可以有效处理多个样式。

当然,这只是例子,你可以用在其它问题上。

下面给大家说一下,如何在实际运用中去运用位运算来解决上面这个问题

使用按位"&"方式来对位进行操作

复习“&”

1与0为0;1与1为1;0与0为0。

因为按位与的特点,我们可以通过按位一些不同的bit来达到赋予值的目的

如10的二进制是1010

当int a变量的值是0

那么

1010&00000000 00000000 00000000 000000000

则是

00000000 00000000 00000000 00001010

是不是发现好多0,这是因为int a是32位的,在底层运算时,完整的运算bit就是这样,且是从低到高位依次运算

同时当我们c语言赋予值的时,其实编译器会把它看成一个32位整型如:

int a = 0;

a = a&10;

位运算:

00000000 00000000 00000000 00001010&00000000 00000000 00000000 000000000

这里10 c语言编译器会通过一些类型推举,认为它是32位整数,然后赋值给int a

那么假如int a是1234,二进制是:00000000 00000000 00001001 1010010

我们a&10 就是:00000000 00000000 00000000 00001010&00000000 00000000 00001001 1010010

因为与的特性,所以只有最低的八位发生了变化,但是如果我们要是想把10这个值赋到高八位怎么办?

答:位移运算

通过位移把10的二进制数左移位就可以了

10左移八位是:00000000 00000000 00001010 00000000

然后与运算一下就可以了

实战一下吧~

#define INPUT_LOW_8(SOUR,VAR)({\
    SOUR|=VAR&0x00FF;\   
})
#define INPUT_HEIGHT_8(SOUR,VAR)({\
    SOUR|=(VAR<<8)&0x0FF00;\   
})

#define OUTPUT_LOW_8(SOUR) (SOUR&0X00FF)
#define OUTPUT_HEIGHT_8(SOUR) (SOUR&0xFF00)>>8

上面这个是我在一个项目里写的,这里大家可以根据我们上面说的原理自己先分析一下

先说第一个

#define INPUT_LOW_8(SOUR,VAR)({\
    SOUR|=VAR&0x00FF;\   
})

这里SOUR就是变量,而VAR就是要写入低八位的值

SOUR|=VAR&0xFF;

这里用或运算是因为为了不覆盖旧值,比如高位已经有值了,如果你用=号的话那么高位会被覆盖掉

如SOUR=VAR

即便VAR是0001这样的小值,被c语言会翻译成0000 0000 0000 0000 0000 0001这样的32位整型,然后赋予给SOUR

但是如果是|运算就不一样了,这里给大家顺便说一下或运算的特点:

1001|101=1101

有一时为一,都是0时为0,不相同时为一

在复习一下&和|

&: 二进制“与”(都为1时,结果是1,否则是0。),比如:1010 & 1011 = 1010,1010 & 1000 = 1000。

|: 二进制“或”(有1时,结果是1,都是0时,结果为0。),比如:1010 | 1011 = 1011,1010 | 1000 = 1010。

这两个运算符特点很相似,一般使用时容易混淆

后面的VAR&0xFF是因为假如传递进来的是一个大值,比如1234这样的值,我们只要低八位,所以&上0xFF,就可以取到低八位的值,然后写进去,这样可以防止一些额外的bit运算

如果你能确定VAR的值会小于8位,那么你可以省略掉后面的&0xFF

#define INPUT_HEIGHT_8(SOUR,VAR)({\
    SOUR|=(VAR<<8)&0x0FF00;\   
})

这一个其实很简单,结合前面说的,左移8位,c语言会把它堪称32位整型处理,后面的&0x0FF00;也是左移了八位的FF,跟上面一样的原理。

这里说一下

一个F最大能表示15,而15的bit是1111,也就是说一个F能运算四个bit位,FF刚好8位

一般都会简写成0xFFFFFFF,实则上c语言会看成0x00FFFFFFFF

#define OUTPUT_LOW_8(SOUR) (SOUR&0X00FF)
#define OUTPUT_HEIGHT_8(SOUR) (SOUR&0xFF00)>>8

如果前面所说的你已经理解了,那么这段代码可以很轻松的理解

SOUR&0xFF,这里是将SOUR的值与低位运算一下,忽略高位的运算

而SOUR&0xFF00>>8 则是将高位的运算结果(同时也忽略其它位,只运算高16位)左移八位,因为c语言是从低位开始运算的,如果左移八位则:

假如SOUR&0xFF00的结果是0000 1111 0000 如果不左移八位,那么结果就是:240,这跟原本存入进去的值就不一样了,所以要左移八位到低位上去

demo:

#include <stdio.h>

#define INPUT_LOW_8(SOUR,VAR)({\
    SOUR|=VAR&0x00FF;\   
})
#define INPUT_HEIGHT_8(SOUR,VAR)({\
    SOUR|=(VAR<<8)&0x0FF00;\   
})

#define OUTPUT_LOW_8(SOUR) (SOUR&0X00FF)
#define OUTPUT_HEIGHT_8(SOUR) (SOUR&0xFF00)>>8

int main(){
    int a=0;
    INPUT_LOW_8(a,30);
    INPUT_HEIGHT_8(a,20);
    printf("%d %d",OUTPUT_LOW_8(a),OUTPUT_HEIGHT_8(a));
}

结果:

30 20

但是这样的运算是有一个问题的

比如我们修改一下main函数:

int main(){
    int a=1234;
    INPUT_LOW_8(a,30);
    INPUT_HEIGHT_8(a,20);
    printf("%d %d",OUTPUT_LOW_8(a),OUTPUT_HEIGHT_8(a));
}

这里将a初始化为1234:

222 20

问题来了,很明显 在获取低位的时出错啦

这里经过我调试分析得出的结果:

1234的bit=10011010010

30的bit=11110

当写入到低位时,原本的1234就会变成:10011011110

可以看到低位已经被赋予了30的bit

那么当10011011110 &11111111(0xFF)

10011011110

&

000011111111

=

000011011110

可以看到,是1的都保留了下来,因为30的bit 5个bit就能表示,但是恰巧在&上0xFF的时,其它位上也有值

也是1,导致被保留了下来

所以解决办法就是,在赋予LOW的时用=号,或者给变量初始化0都可以

 

这里在介绍一个如何获取每个变量的bit?

这里是我在项目里写的一个宏:

#define GET_BIT(NUM,I) (bool)(NUM&(1<<I))

NUM代表变量,I代表要获取的第几位的bit

(NUM&(1<<I))

这里其实也是用了位移

比如:

NUM=5(101)

1<<2,就是将1这个值左移两位

而1的bit位是0001,左移两位0100

然后5(101)&(100)=(100)

就取到了这个位上有没有值,但是不会返回1,返回结果是与运算后的结果100(4)

最前面的bool完美解决这个问题

因为在布尔值里,1代表TRUE,0代表FALSE

所以转化为布尔值后c语言语言会帮我们进行一个类型转换,当结果非0时,c语言会转化为1,0时就会返回0

在c语言里0代表假,非0代表真

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

17岁boy想当攻城狮

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值