前言
本期我们将学习位运算,与本期类型的考点(二进制转换)反码、补码、原码。
1、位运算是什么
首先我们需要先了解位运算是什么。
我们知道,计算机中的数在内存中都是以二进制形式进行存储的 ,而位运算就是直接对整数在内存中的二进制位进行操作,因此其执行效率非常高,在程序中尽量使用位运算进行操作,这会大大提高程序的性能。
那么,涉及位运算的运算符如下表所示:
符号 | 描述 | 运算规则 | 实例(以四位二进制数为例) |
---|---|---|---|
& | 与 | 两个位都为1时,结果才为1。 | 0001&0001=1,0001&0000=0,0000&0000=0000 |
| | 或 | 两个位都为0时,结果才为0。 | 0001∣0001=0001,0001∣0000=0001,0000∣0000=0000 |
^ | 异或 | 两个位相同为0,相异为1。 | 0001∧0001=0000,0001∧0000=1,0000∧0000=0 |
~ | 取反 | 0变1,1变0。 | ∼0=1,∼1=0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0。 | 0001<<k=0100,k=2,k kk是左移的位数,这里k = 2 k=2k=2 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,右移补1 11。 | 0100>>k=0001,k=2,k kk是右移的位数,这里k = 2 k=2k=2 |
2、位运算的性质
2.1 运算符的优先级
优先级需要弄清楚,如果不太清楚可以加小括号确保是想要的运算顺序,这里只是相对优先级,即只是和一些常用的算术运算符做比较。
2.2 位运算符的运算律
3、位运算高级操作
如下表,请读者认真阅读理解,在阅读的过程中可以对示例进行运算。
当然,这里只是一些常用的,并不是全部,位运算的神奇远不止于此。
4、负数的位运算
首先,我们要知道,在计算机中,运算是使用的二进制补码,而正数的补码是它本身,负数的补码则是符号位不变,其余按位取反,最后再+ 1 +1+1得到的, 例如:
15 1515,原码:00001111 00001111\space00001111 补码:00001111 0000111100001111
− 15 -15−15,原码:10001111 10001111\space10001111 补码:11110001 1111000111110001
那么对于负数的位运算而言,它们的操作都是建立在补码上的,得到的运算结果是补码,最后将补码结果转化成一个普通的十进制数结果。
但需要注意的是,对于有符号数的右移操作,不同的处理器架构可能有不同的规定。在某些架构中(如x86),如果对有符号数执行算术右移(arithmetic right shift),则高位空出来的位置会补上符号位;对于无符号数的右移操作,所有架构都遵循相同的规则:高位空出来的位置会补0。例如对于− 15 -15−15,其补码为11110001 , 11110001,11110001,右移一位( − 15 > > 1 ) (-15>>1)(−15>>1)得到的是11111000 1111100011111000,即− 8 -8−8,其他的同理。
在大多数现代处理器上,无论是有符号数还是无符号数,左移操作总是将空出来的低位补0。
这里我们介绍几个特殊的性质:
- 快速判断是否为-1
在链式前向星中,我们初始化h e a d headhead数组为− 1 -1−1,最后判断是否遍历完u uu的所有边时,即判断i ii是否为− 1 -1−1,我们直接用∼ i \sim i∼i即可。原因就在于− 1 -1−1的补码是11111111 1111111111111111,按位取反就变为00000000 0000000000000000,这实际上就是0 00。
- 取最低位的1,lowbit函数
也就是x & ( − x ) x\&(-x)x&(−x),这在树状数组中起着巨大作用,这里指路一篇树状数组讲解b l o g blogblog:点这里,我们来证明一下,这里取x = 15 x=15x=15,对于15 & ( − 15 ) 15\&(-15)15&(−15),我们知道,在补码上进行运算得到的是00000001 0000000100000001,需要注意二元运算的符号位我们需要进行运算。
位运算的运用
1、判断第i位是否为0
2、将第i位设置为1
3、统计有多少个1
int count(int x){int cnt = 0;while(x){x = x & (x - 1);cnt ++;}return cnt;
}
对于任意的x xx,转换成二进制后,是形如这样的数字:a a . . . a a 10...00 aa...aa10...00aa...aa10...00,从右向左数有任意多个0 00,直到遇见第一个1 11,字母a aa用来占位,代表1 11左边的任意数字。x − 1 x-1x−1转换成二进制后,是形如这样的数字:a a . . . a a 01...11 aa...aa01...11aa...aa01...11,从右向左数,原来的任意多个0 00都变成1 11,原来的第一个1 11,变成0 00,字母a aa部分不变。对x xx 和 x − 1 x-1x−1 进行 按位与 计算,会得到:a a . . . a a 00...00 aa...aa00...00aa...aa00...00,从右向左数,原来的第一个1 11变成了0 00,字母a部分不变。所以 x & ( x − 1 ) x \& (x-1)x&(x−1)相当于消除了 x xx 从右向左数遇到的第一个1 11。那么,x xx转换成二进制后包含多少个1 11,count函数里的循环就会进行多少次,直到x xx所有的1 11都被“消除”。