文章采摘自百度百科http://baike.baidu.com/view/379209.htm,有删减,将其pascal代码改为c语言代码。
程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算说穿了,就是直接对整数在内存中的二进制位进行操作。比如,and运算本来是一个逻辑运算符,但整数与整数之间也可以进行and运算。举个例子,6的二进制是110,11的二进制是1011,那么6 and11的结果就是2,它是二进制对应位进行逻辑运算的结果(0表示False,1表示True,空位都当0处理)。
如i*94 = i*(64 + 32 - 2) = i*64 + i*32 –i*2 = i<<6 + i<<5 – i<<1;后者运算速率远大于前者。
假设AB都是整型类型
Pascal |
C |
Java |
A and B |
A&B |
A&B(同1为1) |
A or B |
A|B |
A|B(同0为0) |
A xor B |
A^B |
A^B(异或,相同为0) |
not A |
~A |
~A(按位取反) |
A shl B |
A< |
A< |
A shr B |
A>>B |
A>>B(无符号右移,right) |
- |
- |
A>>>B(带符号右移) |
and运算通常用于二进制取位操作,例如一个数and 1的结果就是取二进制的最末位。这可以用来判断一个整数的奇偶,二进制的最末位为0表示该数为偶数,最末位为1表示该数为奇数。
相同位的两个数字都为1,则为1;若有一个不为1,则为0。(&;或者and)
00101
11100
----------------
00100
or运算通常用于二进制特定位上的无条件赋值,例如一个数or 1的结果就是把二进制最末位强行变成1。如果需要把二进制最末位变成0,对这个数or 1之后再减一就可以了,其实际意义就是把这个数强行变成最接近的偶数。(|或者or)
相同位只要一个为1即为1。
00101
11100
----------------
11101
异或的符号是⊕。按位异或运算, 对等长二进制模式按位或二进制数的每一位执行逻辑按位异或操作. 操作的结果是如果某位不同则该位为1, 否则该位为0.
xor运算的逆运算是它本身,也就是说两次异或同一个数最后结果不变,即(a xor b) xor b = a。xor运算可以用于简单的加密,比如我想对我MM说1314520,但怕别人知道,于是双方约定拿我的生日19880516作为密钥。1314520 xor 19880516 = 20665500,我就把20665500告诉MM。MM再次计算20665500xor 19880516的值,得到1314520,于是她就明白了我的企图。
相同位不同则为1,相同则为0。(^或者xor)
00101
11100
----------------
11001
延伸:
于是我们就有了一个看起来非常诡异的swap过程:
int swap(int a, int b){
a=a^ b;
b=a ^ b;
a=a ^ b;
}
not运算的定义是把内存中的0和1全部取反。使用not运算时要格外小心,你需要注意整数类型有没有符号。如果not的对象是无符号整数(不能表示负数),那么得到的值就是它与该类型上界的差,因为无符号类型的数是用00到$FFFF依次表示的。下面的两个程序(仅语言不同)均返回65435。
a shl b就表示把a转为二进制后左移b位(在后面添b个0)。例如100的二进制为1100100,而110010000转成十进制是400,那么100 shl 2= 400。可以看出,a shl b的值实际上就是a乘以2的b次方,因为在二进制数后添一个0就相当于该数乘以2。
通常认为a shl 1比a * 2更快,因为前者是更底层一些的操作。因此程序中乘以2的操作请尽量用左移一位来代替。
定义一些常量可能会用到shl运算。你可以方便地用1 shl 16 - 1来表示65535。很多算法和数据结构要求数据规模必须是2的幂,此时可以用shl来定义Max_N等常量。
和shl相似,a shr b表示二进制右移b位(去掉末b位),相当于a除以2的b次方(取整)。我们也经常用shr 1来代替div 2,比如二分查找、堆的插入操作等等。想办法用shr代替除法运算可以使程序效率大大提高。最大公约数的二进制算法用除以2操作来代替慢得出奇的mod运算,效率可以提高60%。
这里注意一下负数的左移和右移:
1)负数的右移:负数右移的话,由于要保持它是负数,所以负数的二进制的右边补1。如果一直右移的话,最后就就变成0xFFFFFFFF 即-1
如: -4>>1 为-2 ;-4>>2为-1
2)负数的左移:跟正整数左移一样,右边补0,一直左移的话,最后就是0啦。-2<<2为-4 ; -2<<31为0
优先级
C语言中位运算符之间,按优先级顺序排列为
1 |
~ |
2 |
<<、>> |
3 |
& |
4 |
^ |
5 |
| |
6 |
&=、 ^=、 |=、 <<=、 >>= |
功能 |
示例 |
位运算 |
去掉最后一位 |
101101->10110 |
x shr 1 |
在最后加一个0 |
101101->1011010 |
x shl 1 |
在最后加一个1 |
101101->1011011 |
x shl 1+1 |
把最后一位变成1 |
101100->101101 |
x or 1 |
把最后一位变成0 |
101101->101100 |
x or 1-1 |
最后一位取反 |
101101->101100 |
x xor 1 |
把右数第k位变成1 |
101001->101101,k=3 |
x or (1 shl (k-1)) |
把右数第k位变成0 |
101101->101001,k=3 |
x and not (1 shl (k-1)) |
右数第k位取反 |
101001->101101,k=3 |
x xor (1 shl (k-1)) |
取末三位 |
1101101->101 |
x and 7 |
取末k位 |
1101101->1101,k=5 |
x and (1 shl k-1) |
取右数第k位 |
1101101->1,k=4 |
x shr (k-1) and 1 |
把末k位变成1 |
101001->101111,k=4 |
x or (1 shl k-1) |
末k位取反 |
101001->100110,k=4 |
x xor (1 shl k-1) |
把右边连续的1变成0 |
100101111->100100000 |
x and (x+1) |
把右起第一个0变成1 |
100101111->100111111 |
x or (x+1) |
把右边连续的0变成1 |
11011000->11011111 |
x or (x-1) |
取右边连续的1 |
100101111->1111 |
(x xor (x+1)) shr 1 |
去掉右起第一个1的左边 |
100101000->1000 |
x and (x xor (x-1))(或 x and (-x)) |
我们可以用下面的代码来计算一个32位整数的二进制中1的个数的奇偶性,当输入数据的二进制表示里有偶数个数字1时程序输出0,有奇数个则输出1。例如,1314520的二进制101000000111011011000中有9个1,则x=1314520时程序输出1。
代码:
//针对三十二位数(int)
//每异或一次,最后一位代表加上自身往前供2^i个位置的1的个数
//最后取出最后一位的值
int is_Odd_Bits(int a){
a = a^(a>>1);
a = a^(a>>2);
a = a^(a>>4);
a = a^(a>>8);
a = a^(a>>16);
return a&1;
}
只用位运算来取绝对值。
int int_abs(int x)
{
//int 是32位,以补形式存储,如果是负数,则y为-1,如果是正数,则y为0
//11111111 11111111 111111111 11111111这是-1的补码
//return (x^x>>31) - x>>31;//由补码求原码取反加1,-y等于+1
return (x^(x>>31)) - (x>>31);//另外,运算符的优先级为:~优先于+-*/优先于>><<优先于&^
}
给出一个小于2^32的正整数。这个数可以用一个32位的二进制数表示(不足32位用0补足)。我们称这个二进制数的前16位为“高位”,后16位为“低位”。将它的高低位交换,我们可以得到一个新的数。试问这个新的数是多少(用十进制表示)。
例如,数1314520用二进制表示为0000 0000 0001 0100 0000 1110 1101 1000(添加了11个前导0补足为32位),其中前16位为高位,即00000000 0001 0100;后16位为低位,即0000 1110 1101 1000。将它的高低位进行交换,我们得到了一个新的二进制数0000 1110 1101 1000 0000 0000 0001 0100。它即是十进制的249036820。
代码:
int High_Low_Bit_Swap(int x){
return (x>>16)|(x<<16);
}
下面的程序读入一个32位整数并输出它的二进制倒序后所表示的数。
输入:1314520 (二进制为00000000000101000000111011011000)
输出:460335104 (二进制为00011011011100000010100000000000)
代码:
int Reverse_Bit(int x){
x= (x & 0x55555555)<<1 | (x & 0xAAAAAAAA)>>1;
x= (x & 0x33333333)<<2 | (x & 0xCCCCCCCC)>>2;
x= (x & 0x0F0F0F0F)<<4 | (x & 0xF0F0F0F0)>>4;
x= (x & 0x00FF00FF)<<8 | (x & 0xFF00FF00)>>8;
x= (x & 0x0000FFFF)<<16 | (x & 0xFFFF0000)>>16;
return x;
}
位数不同的运算数之间的运算规则补充:
C语言中,位运算的对象可以是整型(int)和字符型(char)数据。(整形数据可以直接转化成二进制数,字符型数据在内存中以它的ASCII码值存放,也可以站化成二进制数)当两个运算数类型不同时,位数亦会不同。遇到这种情况,系统将自动进行如下处理:
1将两个运算数右端对齐。
2 再将位数短的一个运算数往高位扩充,即:无符号数和正整数左侧用0补全;负数左侧用1补全;然后对位数相等的两个运算数,按位进行运算。