位运算总结

目录

1 概念

2 位运算的性质

 2.1 运算符的优先级

2.2 位运算的运算律

3 负数的位运算

4 位运算的应用


1 概念

现代计算机中,几乎都是二进制计算机,所有的数据都以二进制的形式存储在设备中。位运算就是直接对存储在内存中的数据的二进制位进行操作。在计算机中,每一个二进制位称为1个bit,单位简写做 b。通常8个bit为一个单位,称为字节(byte),单位简写作 B。位运算执行效率非常高,在程序中尽量使用位运算进行操作,这样会大大提高程序的性能。

涉及位运算的运算符如下表所示:

符号描述运算规则实例(以四位二进制为例)
&两个位都为1时,结果才为10001&0001=1,0001&0000=0,0000&0000=0000
|两个位都为0时,结果才为00001|0001=0001,0001|0000=0001,0000|0000=0000
^异或两个位相同为0,相异为10001∧0001=0000,0001∧0000=1,0000∧0000=0
~取反0变1,1变0∼0=1,∼1=0
<<左移各二进位全部左移若干位,高位丢弃,低位补00001<<k=0100,k=2,k是左移的位数,这里k = 2 k=2k=2
>>右移各二进位全部右移若干位,对无符号数,高位补0,有符号数,右移补10100>>k=0001,k=2,k是右移的位数,这里k = 2 k=2k=2

2 位运算的性质

 2.1 运算符的优先级

优先级需要弄清楚,如果不太清楚可以加小括号确保是想要的运算顺序,这里只是相对优先级,即只是和一些常用的算术运算符做比较。

优先级运算符结合方向
1−(符号运算符),∼(取反运算符),++(自增),−−(自减)从右到左
2∗(乘),/(除),%(取余)从左到右
3+(加),−(减)从左到右
4<<(左移),>>(右移)从左到右
5>(大于),<(小于),>=(大于等于),<=(小于等于)从左到右
6==(等于),!=(不等于)从左到右
7&(按位与)从左到右
8∧(按位异或)从左到右
9|(按位或)从左到右

2.2 位运算的运算律

公式名称运算规则
交换律A&B=B&A,A&B=B&A,A∧B=B∧A
结合律(注意结合律只能在同符号下进行)(A&B)&C=A&(B&C)
等幂律A&A=A,A|A=A
零律A|1=1,A&0=0
互补律(注意,这不同于逻辑运算)A&∼A=0,A|∼ A = − 1
同一律A|0=A,A∧ 0 =A

以上仅为已证明的运算律(可能存在遗漏),注意:千万不要将逻辑运算的运算律或者其他的运算律与这混为一谈

3 负数的位运算

首先,我们要知道,在计算机中,运算是使用的二进制补码,而正数的补码是它本身,负数的补码则是符号位不变,其余按位取反,最后再+1得到的, 例如:

15,原码:00001111 补码:00001111

−15,原码:10001111 补码:11110001

那么对于负数的位运算而言,它们的操作都是建立在补码上的,得到的运算结果是补码,最后将补码结果转化成一个普通的十进制数结果。但需要注意的是,符号位是需要参与运算的,而在左移右移操作中,负数右移补1,左移右边补0。例如对于−15,其补码为11110001 ,右移一位(−15>>1)得到的是11111000,即−8,其他的同理。

这里我们介绍几个特殊的性质:

  • 快速判断是否为−1

在链式前向星中,我们初始化head数组为−1,最后判断是否遍历完u的所有边时,即判断i是否为−1,我们直接用∼ i 即可。原因就在于−1的补码是11111111,按位取反就变为00000000,这实际上就是0。

  • 取最低位的1,lowbit函数

也就是x & ( − x ),这在树状数组中起着巨大作用,这里指路一篇树状数组讲解blog:点这里,我们来证明一下,这里取x=15,对于15 & ( − 15 ),我们知道,在补码上进行运算得到的是00000001,需要注意二元运算的符号位我们需要进行运算。

4 位运算的应用

  • 位运算实现乘除法

将x左移一位实现× 2,将x右移一位实现÷ 2。

a < < 1 ≡ a ∗ 2

a > > 1 ≡ a / 2

  • 位运算交换两个整数
 void swap(int &a,int &b){
      a ^= b;
      b ^= a;
      a ^= b;
  }

这效率非常高,我们来剖析其原理,对于a = a ∧ b,则b = b ∧ ( a ∧ b ),根据交换律以及异或性质,得b = b ∧ b ∧ a = 0 ∧ a = a,同理a = ( a ∧ b ) ∧ a = 0 ∧ b = b,这样就实现了交换操作。

  • 位运算判断奇偶数

我们知道,在二进制中,最低位决定了是奇数还是偶数,所以我们可以提取出最低位的值,即与1 相与即可实现目的,为0则是偶数,为1则是奇数。

  • 位运算改变正负值和求绝对值
int change(int a){
    return ~ a + 1;
}

对于正数而言,补码就是原码,所以按位取反再+ 1则得到对应真值负数的补码,而对于负数,其补码进行按位取反再+1则得到对应真值正数的补码,变为原码。那么知道这个我们就可以特判是否为负数==(这里通过右移31位,若为正数,则得到的是0,若为负数,则得到的是−1,而0的补码为0000,−1的补码为1111,根据异或性质即可判断。)== 再进行反转即可实现求绝对值了。如下:

int abs(int a){
    return a ^ (a >> 31) ? a : ~ a + 1;
}
  • 位运算实现对p取余(p为2*2*2*......(k个2相乘))
int mod(int a,int p){
    return a & (p - 1);
}

取余实际上就是舍去大于等于p的位数,所以我们只需要保留在p范围内的数。由于我们限定了p,所以(p−1)一定是将小于p的最高位全部变为了1,这样再进行与操作即可得到余数。

  • 位运算统计二进制数中1的个数
    int count(int x){
        int cnt = 0;
        while(x){
            x = x & (x - 1);
            cnt ++;
        }
        return cnt;
    }
    

    对于任意的x,转换成二进制后,是形如这样的数字:aa…aa10…00,从右向左数有任意多个0,直到遇见第一个1,字母a用来占位,代表1左边的任意数字。x-1转换成二进制后,是形如这样的数字:aa…aa01…11,从右向左数,原来的任意多个0都变成1,原来的第一个1,变成0,字母a部分不变。对x 和 x-1 进行 按位与 计算,会得到:aa…aa00…00,从右向左数,原来的第一个1变成了0,字母a部分不变。所以 x & (x-1)相当于消除了 x 从右向左数遇到的第一个1。那么,x转换成二进制后包含多少个1,count函数里的循环就会进行多少次,直到x所有的1都被“消除”。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Kk.巴扎嘿

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值