Bit Manipulation, 程序运算中对位模式或二进制数的一元或二元操作。说白了,就是直接对整数的二进制进行运算操作。
在计算机的世界中,一切都是二进制机器码。熟练掌握位操作,有助于我们更好的理解程序运行原理。
在以往古老的微处理器中,位运算比加减运算略快,比乘除运算要快很多。在现代架构中,情况有所改变,位运算和加减运算速度接近相同,但仍然快于乘除运算。
将一个十进制正数转换为二进制的时候,只需要通过除2取余的方法即可,比如:2 = 0010 但是将一个十进制负数转为二进制?
负数是以补码的形式表示,其转换方式,简单来讲:先按正数转换,然后取反加1. -2 = ~2 + 1 = 1101 + 0001 = 1110 (符号位 1)。
例如:十进制 -10
10 0000 0000 0000 1010
取反 1111 1111 1111 0101
加1 1111 1111 1111 0110
-10 1111 1111 1111 0110
正数X -x = ~x + 1
取反是一元运算符,对一个二进制数的每一位执行逻辑取反的操作。
运算规则:使数字1变成0,0变成1.
C/C++ 中使用 ‘~’做为取反运算符
例如:
NOT 0111 (十进制 7) = ... 1000 (十进制 -8)
~ 0000 0111 7
----------------------
1111 1000 -8
/*
* 注意:许多程序语言中使用"~"作为取反的操作符号.需要注意的是取反(~)操作和逻辑非(!)操作符不同,
* 在C++程序中,逻辑非将数字整体看成一个布尔类型--将真值转化为假值。逻辑非并非一个位运算符。
*/
主要用途
1. 使一个数的最低位变为0
// a 的最低位变为 0: a & ~1, ~1的二进制除了末尾变成0,其余的位全是1.
2. ~运算符的优先级比算术运算符、关系运算符、逻辑运算符及其他运算符的优先级都高
同1为1,同0为0。
运算规则:非1跟1按位与保持不变,1跟1按位与为1,跟0按位与清零。
C/C++ 中使用 ‘&’ 做为按位与运算符
例如:
0110 (十进制 6) AND 1010 (十进制 10) = 0010 (十进制 2)
0000 0110 6
& 0000 1010 10
----------------------
0000 0010 2
/*
* 按位与操作是比较常见的运算符,如果在LeetCode刷题的话,位运算很多题中会使用到按位与操作。
* 1 & 1 = 1 | 1 & 0 = 0 | 0 & 1 = 0 | 0 & 0 = 0
*/
主要用途
1. 清零
// 如果将一个数清零,即使其二进制位全变为0,只要与一个各位都为零的数值进行按位与运算,结果为零
2. 取一个数的指定位
// 比如取一个数的后四位, X = 1011 0110,只要 0000 1111 进行与运算,这样可以获取最后4位的值:0110.
3. 判断奇偶
// 最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数
4. 求一个数最低位1
// int val = x & (~x + 1); 如 x = 0001 1100, ~x = 1110 0011, ~x + 1 = 1110 0100, val = 0000 0100
5. 2的幂次方
// X & (X - 1) == 0, X为2的幂次方。
遇1为1, 即参加运算的两个对象中只要有一个是1,结果为1
运算规则: 0 | 0 = 0 , 1 | 1 = 1 , 0 | 1 = 1, 1 | 0 = 1
负数按补码的形式参加按位与运算。
例如
0010 (十进制 2) | 1010 (十进制 10) = 1010 (十进制 10)
0000 0010 2
| 0000 1010 10
--------------------
0000 1010 10
主要用途
1. 对一个数据的某些位设置为1
// 如 X = 1101 1001 , 取数Y,Y的最低4位为:1111,(X|Y) = 1101 1111
不同为1, 相同为0
运算规则: 1 ^ 0 = 1, 0 ^ 1 = 1, 0 ^ 0 = 0, 1 ^ 1 = 0
异或运算的性质:
1. 交换律 a ^ b = b ^ a
2. 结合律 (a ^ b) ^ c = a ^ (b ^ c)
3. 对与任何数X, 都有 X ^ X = 0, X ^ 0 = X
4. 自反律 a ^ b ^ b = a ^ 0 = a
例如
0010 (十进制 2) ^ 0010 (十进制 2) = 0
0000 0010 2
0000 0010 2
^
---------------------
0000 0000 0
// leetCode题库中很多位运算,使用异或会让算法更简单直观.
主要用途
1. 翻转指定位数
// 如X = 1010 1110, 对X的个位进行翻转,取数 Y = 1111 1111 进行异或, (X ^ Y ) = 0101 0001
2. 与0异或,值不变。
// 如 X = 1010 1110 ^ 0000 0000 = 1010 1110
3. 交换两个数
void swap(int& a, int& b) {
if (a !=b) {
a ^= b;
b ^= a;
a ^= b;
}
}
将一个数的各二进制位全部左移若干位,左边的位数遗弃,右边补0.
例如
a = 1010 1100 左移动两位:1010 1011 0000 补零
如若左移的高位舍弃的不包括1,则每次左移一位,原数值增加2倍。即 a << 1 = a * 2
将一个数的各二进制位全部右移若干位,正数补0,负数补1,右边的丢弃。
例如
a = 1010 1100 右移动两位: 0010 1011 正数补0,负数补1.
操作数右移1位,等价于操作数除2.
位运算和赋值运算符的组合,组成了新的复合运算符。
&= a &= b --> a = a & b;
|= a |= b --> a = a | b;
>>= a >>= b --> a = a >>= b;
<<= a <<= b --> a = a <<= b;
^= a ^= b --> a = a ^= b;
本文对基本的位操作运算做了整理和说明,后续结合一些算法题,将会整理描述位运算的一些运算技巧和性质。时间允许的话,也希望能够把加减乘除也来一页做说明。