一 基本概念
机器码 = 符号位 + 真值
符号位:
正数:0
负数:1
1 原码
原码 = 符号位 + 真值
比如:
- +1: 0 001
- -1: 1 001
2 反码
- 正数反码 = 原码
- 负数反码 = 符号位 + 真值取反
比如:
- +1: 0 001
- -1: 1 110
3 补码
- 正数反码 = 原码
- 负数反码 = 反码 + 1
比如:
- +1: 0 001
- -1: 1 111
-1的补码计算过程:
-1 = 1 110 + 1 = 1 111
二 常用位运算
1 按位与
操作位同为1,则为1,否则为0
比如:
1 & 2 = 0001 & 0010 = 0000 = 0
1 & -1 = 0001 & 1111 = 0001 = 1
2 按位或
操作位同为0,则为0,否则为1
比如:
1 | 2 = 0001 | 0010 = 0011 = 3
1 | -1 = 0001 | 1111 = 1111 = -1
3 按位异或
操作位不同则为1,否则为0
比如:
1 ^ 2 = 0001 ^ 0010 = 0011 = 3
1 ^ -1 = 0001 ^ 1111 = 1110 = -2
ps: 异或也叫半加运算,其运算法则相当于不带进位的二进制加法
4 按位取反
操作位0变1,1变0
比如:
~1 = ~0001 = ~1110 = -2
~2 = ~0010 = 1101 = -3
~-1 = ~1111 = 0000 = 0
5 左移
右边补0,去掉多余的高位
比如:
1 << 1 = 0001 << 1 = 00010 = 2
2 << 2 = 0010 << 2= 001000 = 8
-1 << 3 = 1111 << 3= 1111000 = -8
6 右移
左边补符号位,去掉多余的低位
比如:
1 >> 1 = 0001 >> 1 = 0000 = 0
2 >> 1 = 0010 >> 1= 0001 = 1
4 >> 1 = 0100 >> 1= 0010 = 2
4 >> 2 = 0100 >> 2= 0001 = 1
-1 >> 1 = 1111 >> 1 = 1111 = -1
7 无符号右移
左边补0,去掉多余的低位
比如:
1 >>> 1 = 0001 >> 1 = 0000 = 0
2 >>> 1 = 0010 >> 1= 0001 = 1
4 >>> 1 = 0100 >> 1= 0010 = 2
4 >>> 2 = 0100 >> 2= 0001 = 1
-1 >>> 1 = (1...111) >> 1 = (0...111) = 最大正整数
三 位运算应用
1 判断奇偶
- 偶数:a & 1 == 0
- 奇数:a & 1 == 1
比如:
1 & 1 = 0001 & 0001 = 0001 = 1
2 & 1 = 0010 & 0001 = 0000 = 0
3 & 1 = 0011 & 0001 = 0001 = 1
4 & 1 = 0100 & 0001 = 0000 = 0
2 运算
- 乘法:a * (2^n) = a << n
- 除法:a / (2^n) = a >> n
- 取模: a % (2^n) = a & (2^n - 1)
比如:
3 * 4 = 3 * (2 ^ 2) = 3 << 2 = 0011 << 2 = 1100 = 12
12 / 4 = 12 / (2 ^ 2) = 1100 >> 2 = 0011 = 3
3 % 4 = 3 & (2^2 - 1) = 3 & 3 = 0011 & 0011 = 0011 = 3
11 % 8 = 11 & (2^3 - 1) = 11 & 7 = 1011 & 0111 = 0011 = 3
3 记录多个状态
假如,有一个需求,要记录角色头顶的公告板要显示哪些内容,比如有名字,等级,称号,工会三个,通常我们需要三个变量,分别记录每个是否显示,使用位运算可以精简如下:
// 定义
let showFlag = 0;
const let NAME_POS = 1 << 0; // 0001 第一位表示名字
const let LV_POS = 1 << 1; // 0010 第二位表示等级
const let TITLE_POS = 1 << 2; // 0100 第三位表示称号
const let GUILD_POS = 1 << 3; // 1000 第四位表示工会
let isShowName, isShowLv, isShowTitle, isShowGuild;
// 名字操作
showFlag = showFlag | NAME_POS; // 0001 显示名字
isShowName = showFlag & NAME_POS = 0001 & 0001 = 0001 = 1;
showFlag = showFlag & ~NAME_POS; // 0000 隐藏名字
isShowName = showFlag & NAME_POS = 0000 & 0001 = 0000 = 0;
// 等级操作
showFlag = showFlag | LV_POS; // 0010 显示等级
isShowLv = showFlag & LV_POS = 0010 & 00010 = 0010 = 2;
showFlag = showFlag & ~LV_POS; // 0000 隐藏等级
isShowLv = showFlag & LV_POS = 0000 & 0010 = 0000 = 0;
// 显示名字和称号
showFlag = showFlag | NAME_POS; // 0001 显示名字
showFlag = showFlag | TITLE_POS; // 0101 显示等级
isShowName = showFlag & NAME_POS = 0101 & 0001 = 0001 = 1;
isShowTitle = showFlag & TITLE_POS = 0101 & 0100 = 0100 = 4;
......
这段代码涉及到几个点:
-
将a第k设为0: a = a & ~(1<<k)
-
将a第k设为1: a = a | (1<<k)
-
取a第k位的值: (a>>k) & 1
例如:
let a = 10;// 1010
// 第3位设为0
a = a & (1 << 3) = 1010 & ~(0001 << 3) = 1010 & ~1000 = 1010 & 0111 = 0010 = 2;
// 取3位的值
(a >> 3) & 1 = (0010 >> 3) & 0001 = 0000 & 0001 = 0
// 第2位设为1
a = a | (1 << 2) = 0010 | (0001 << 2) = 0010 | 0100 = 0110 = 6;
// 取2位的值
(a >> 2) & 1 = (0110 >> 2) & 0001 = 0001 & 0001 = 1
2019/11/04更新
4 计算加法
function add(x, y) {
let result;
while (x != 0) {
result = x ^ y;
x = (x & y) << 1;
y = result;
}
return y;
}
5 xor加密
原理:
b ^ a ^ a == b ^ (a ^ a) == b ^ 0 == b
例如,我们要加密一段文本 txt,密钥为 key,那么:
加密:encrypt_txt = txt ^ key
解密:txt = encrypt_txt ^ key
6 查找数组中只出现一次的数
初级版
一个整型数组里除了1个数字之外,其他的数字都出现了两次。请写程序找出这个数字。
class Solution {
public:
int FindNumsAppearOnce(vector<int> data) {
// 两个相同数字异或=0,一个数和0异或还是它本身。
int len = data.length;
int res = 0;
for (int i = 0; i < len; i++) {
res ^= data[i];
}
return res;
}
};
进阶版
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
class Solution {
public:
void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
int len = data.size();
if (len < 2) {
return;
}
// 对所有元素进行异或运算,得到num1 ^ num2的结果
int res = 0;
for (int i = 0; i < len; i++) {
res ^= data[i];
}
// 异或结果1所在的最低位
int mask = 1;
while ((res & mask) == 0) {
mask = mask << 1;
}
// 将数组分成两部分,然后分别异或得到对应的数字(类似初级版)
*num1=*num2=0;
for(int i = 0; i < len; i++) {
if((data[i] & mask)) {
*num1 = *num1 ^ data[i];
} else {
*num2 = *num2 ^ data[i];
}
}
}
};
终极版
一个整型数组里除了一个数字之外,其他的数字都出现了三次。请写程序找出这两个只出现一次的数字。
class Solution {
public:
int FindNumsAppearOnce(vector<int> data) {
// 记录每一位1出现的次数
int[] bits = new int[32];
int len = data.length;
for(int i = 0; i < len; i++){
for(int j = 0; j < 32; j++){
bits[j] = bits[j] + ( (data[i]>>>j) & 1);
}
}
// 统计1出现的次数不为3的整数倍位
int res = 0;
for(int i = 0; i < 32; i++){
if(bits[i] % 3 !=0){
res = res | (1 << i);
}
}
return res;
}
};