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代表真