程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。比如,and运算本来是一个逻辑运算符,但整数与整数之间也可以进行and运算。举个例子,6的二进制是110,11的二进制是1011,那么6 and 11的结果就是2,它是二进制对应位进行逻辑运算的结果(0表示False,1表示True,空位都当0处理)。
含义 | Pascal | c | java |
按位与 | and | & | & |
按位或 | or | | | | |
按位异或 | xor | ^ | ^ |
按位取反 | not | ~ | ~ |
按位左移 | shl | << | << |
带符号右移 | shr | >> | >> |
无符号右移 | >>> |
运算符说明:
1.按位与:
and运算通常用于二进制取位操作,例如一个数 and 1的结果就是取二进制的最末位。这可以用来判断一个整数的奇偶,二进制的最末位为0表示该数为偶数,最末位为1表示该数为奇数。
2.按位或:
or运算通常用于二进制特定位上的无条件赋值,例如一个数or 1的结果就是把二进制最末位强行变成1。如果需要把二进制最末位变成0,对这个数or 1之后再减一就可以了,其实际意义就是把这个数强行变成最接近的偶数。
3.按位异或:
异或的符号是^。按位异或运算, 对等长二进制模式按位或二进制数的每一位执行逻辑按位异或操作. 操作的结果是如果某位不同则该位为1, 否则该位为0.下面是一个交换例子:
a=a ^ b;
b=a ^ b;
a=a ^ b;
4.按位取反:
~运算的定义是把内存中的0和1全部取反。使用~运算时要格外小心,你需要注意整数类型有没有符号。如果~的对象是无符号整数(不能表示负数),那么得到的值就是它与该类型上界的差,因为无符号类型的数是用00到$FFFF依次表示的。如果~的对象是有符号的整数,写在后面.
5.按位左移:
a << b就表示把a转为二进制后左移b位(在后面添b个0)。例如100的二进制为1100100,而110010000转成十进制是400,那么100 << 2 = 400。可以看出,a << b的值实际上就是a乘以2的b次方,因为在二进制数后添一个0就相当于该数乘以2。
可以用<<来定义Max_N等常量,例如 1<<16 -1表示65535
6.按位右移:
和<<相似,a >>b表示二进制右移b位(去掉末b位),相当于a除以2的b次方(取整)。我们也经常用>> 1来代替div 2,比如二分查找、堆的插入操作等等。想办法用shr代替除法运算可以使程序效率大大提高。
注意:左移:
高位删除,低位补0,但是注意有时会溢出
int i = 0x40000000; //16进制的40000000,为2进制的01000000...0000
i = i << 1;那么,i在左移1位之后就会变成0x80000000,也就是2进制的 100000...0000,符号位被置1,其他位全是0,变成了int类型所能表示的最小值,32位的 int这个值是-2147483648,溢出,若再左移一位则为0.
右移:
无符号整数不影响,对于有符号整数符号位不变,低位删除
int i = 0x80000000;
i = i >> 1; //i的值不会变成0x40000000,而会变成0xc0000000
如何消除右移对符号的影响,如java的<<<
1.用unsigned int强制转换
2.与0x7fffffff与操作
#include<iostream> using namespace std; int main() { short int a=0x8000; cout<<(a>>1)<<endl; cout<<((unsigned short int)a>>1)<<endl; cout<<((0x7fff)&(a>>1)); return 0; }
优先级
1 | ~ |
2 | <<,>> |
3 | & |
4 | ^ |
5 | | |
6 | &=、^=、|=、<<=、>>= |
简单事例
去掉最后一位 | (101101->10110) | x >> 1
在最后加一个0 | (101101->1011010) | x << 1
在最后加一个1 | (101101->1011011) | x << 1+1
把最后一位变成1 | (101100->101101) | x | 1
把最后一位变成0 | (101101->101100) | x | 1-1
最后一位取反 | (101101->101100) | x ^ 1
把右数第k位变成1 | (101001->101101,k=3) | x | (1 << (k-1))
把右数第k位变成0 | (101101->101001,k=3) | x & ~ (1 << (k-1))
右数第k位取反 | (101001->101101,k=3) | x ^ (1 << (k-1))
取末三位 | (1101101->101) | x & 7
取末k位 | (1101101->1101,k=5) | x & (1 << k-1)
取右数第k位 | (1101101->1,k=4) | x >> (k-1) & 1
把末k位变成1 | (101001->101111,k=4) | x | (1 << k-1)
末k位取反 | (101001->100110,k=4) | x ^ (1 << k-1)
把右边连续的1变成0 | (100101111->100100000) | x & (x+1)
把右起第一个0变成1 | (100101111->100111111) | x | (x+1)
把右边连续的0变成1 | (11011000->11011111) | x | (x-1)
取右边连续的1 | (100101111->1111) | x ^ (x+1)) >> 1
去掉右起第一个1的左边 | (100101000->1000) | x & (x ^ (x-1))(或 x & (-x))
1 #include<stdio.h> 2 int main() 3 { 4 short int a,b; 5 a=0x0000; 6 b=0x0001; 7 printf("%d %d ",a,b); 8 a=0xFFFE; 9 b=0xFFFF; 10 printf("%d %d ",a,b); 11 a=0x7FFF; 12 b=0x8000; 13 printf("%d %d ",a,b); 14 return 0; 15 }
其中前两个数是内存值最小的数,中间两个数则是内存值最大的数,最后输出的两个数是正数与负数的分界处。二进制的第一位是用来表示正负号的,0表示正,1表示负。正数的原码,补码,反码都相同,都等于它本身.负数的补码是:符号位为1,其余各位求反,末位加1,反码是:符号位为1,其余各位求反,但末位不加1