&:按位与
二进制位均为1,则为1。
举例:
1001 & 1000 = 1000
|:按位或
二进制位有一个为1,则为1。
举例:
1001 & 1000 = 1001
^:按位异或
二进制位不同为1。
举例:
1001 ^ 1000 = 0001
~:取反
按位取反,为一元运算符。
举例:
~1001 = 0110
<<:左移运算符
一个数的二进制位全部左移N位,右位补0。
举例:
1001 << 1 = 10010
(a<<b,实际上为a * 2^b)
>>:右移运算符
一个数的二进制位全部右移N位,左位补0还是1取决于计算机系统。
对于无符号数,左位补0。
对于有符号数,有机器将对左位用符号位填补(即“算术移位”),有机器则对左位用0填补(即“逻辑移位”)。即:如果原来符号位为0(正数),则左位补0。如果符号位原来为1(负数),则左位补0还是1,要取决于所用的计算机系统。
当a是正整数时,a>>b == a / (2^b)
当a是负整数时,a>>b == a / (2^b) 上取整。
位运算技巧
技巧一:N&(N-1)可以消去N的最后一位的1
应用:
用O(1)时间检测整数n是否是2的幂次(N的二进制表示中只有一个1,且N>0)
二进制表示中有多少个1(循环消1即可)
将整数A转换为B,需要改变多少个bit位(A和B有多少个BIT位不相同,A异或B之后这个数中1的个数)
技巧二:子集枚举
对于大小为N的数组,判断N个二进制位中每一位是0还是1,1表示包括该位置数,0表示不包括,可生成2^N个子集
技巧三:a^b^b=a
应用:
只有一个数出现一次,剩下都出现两次,找出出现一次的(所有的数异或可以得到唯一的数)
只有一个数出现一次,剩下都出现三次,找出出现一次的(如果只出现一次的数在该二进制位为1,那么这个二进制位在全部数字中出现次数无法被3整除)
只有两个数出现一次,剩下都出现两次,找出出现一次的(所有的元素异或的结果就是res=x^y。并且res!=0,那么我们可以找出res二进制表示中的某一位是1,那么这一位1对于这两个数x,y只有一个数的该位置是1。对于原来的数组,我们可以根据这个位置是不是1就可以将数组分成两个部分。求出x,y其中一个,就能求出两个了。)
1 #include<stdio.h> 2 3 void findNum(int *a,int n) 4 { 5 int ans=0; 6 int pos=0; 7 int x=0,y=0; 8 for(int i=0;i<n;i++) 9 ans^=a[i]; 10 int tmp=ans; 11 while((tmp&1)==0){ 12 //终止条件是二进制tmp最低位是1 13 pos++; 14 tmp>>=1; 15 } 16 for(int i=0;i<n;i++){ 17 if((a[i]>>pos)&1){//取出第pos位的值 18 x^=a[i]; 19 } 20 } 21 y=x^ans; 22 if(x>y) swap(x,y);//从大到小输出x,y 23 printf("%d %d ",x,y); 24 } 25 int main() 26 { 27 int a[8]={1,2,2,3,4,4,5,3}; 28 findNum(a,8); 29 }
技巧总结
功能 | 示例 | 位运算公式 |
去掉最后一位 | 101101 -> 10110 | x >> 1 |
最后加一个0 | 101101 -> 1011010 | x << 1 |
最后加一个1 | 101101 -> 1011011 | (x << 1) + 1 |
最后一位变成0 | 101101 -> 101100 | (x | 1) - 1 |
最后一位变成1 | 101100 -> 101101 | x | 1 |
最后一位取反 | 101101 -> 101100 | x ^ 1 |
右边第k位变成0 | 101101 -> 101001 k=3 | x | (1 << (k-1)) |
右边第k位变成1 | 101001 -> 101101 k=3 | x &~ (1 << (k-1)) |
右边第k位取反 | 101101 -> 101001 k=3 | x ^ (1 << (k-1)) |
取最后三位 | 101101 -> 101 | x & 0b111 |
取最后k位 | 101101 -> 1101 k=4 | x & ((1<<k) - 1) |
取右边第k位 | 101101 -> 1 k=4 | x>>(k - 1) & 1 |
右边k位变成1 | 101101 -> 101111 k=4 | x | ((1<<k) - 1) |
右边k位取反 | 101101 -> 100010 | x ^ ((1<<k) - 1) |
右边连续的1变成0 | 100101111 -> 100100000 | x & (x + 1) |
右边连续的0变成1 | 101101000 -> 101101111 | x | (x - 1) |
右边第一个0变成1 | 101101 -> 101111 | x | (x + 1) |
取右边连续的1 | 101111 -> 1111 | (x ^ (x + 1)) >> 1 |
去掉右边第一个1的左部分 | 101101000 -> 1000 | x & (-x) |
负数的二进制表示
计算机中:负数以原码的补码形式表达
为何101101000 -> 1000 ===== x & (-x) ?
296:00000000 00000000 00000001 00101000
296的原码:00000000 00000000 00000001 00101000(正数,按照绝对值大小转换成的二进制数)
-296的原码:10000000 00000000 00000001 00101000(负数按照绝对值大小转换成的二进制数,然后最高位补1)
反码:
正数的反码与原码相同。296的反码:00000000 00000000 00000001 00101000
负数的反码为对原码除符号位外取反。-296的反码:11111111 11111111 11111110 11010111
补码:
正数的补码与原码相同。296的补码:00000000 00000000 00000001 00101000
负数的补码为对原码除符号位外各位取反,然后在最后一位加1。-296的补码:11111111 11111111 11111110 11011000
那么-296在计算机中表示为:11111111 11111111 11111110 11011000
因此:296 & (-296) = 8 100101000 & 011011000 =(0b1000)