很多轮子的源码底层都会用到大量的位运算, 比如最经典的用 “>>1”代替“/2”
, 比如加密解密算法、哈希算法、置位
等等,都要用到位运算。因为,这可以提高性能, 加减乘除底层其实都是通过位运算实现的.
一、按位与( & )
按位&
操作 通常用于二进制取位操作
例如: 一个数 &(and) 1
的结果就是取二进制的最末位
这可以用来判断一个整数的奇偶,二进制的最末位为0表示该数为偶数,最末位为1表示该数为奇数.
相同位的两个数字都为1,则为1;若有一个不为1,则为0
00111
11100
(&或者and)
----------------
00100
二、按位或( | )
按位或操作通常用于二进制特定位上的无条件赋值
例如: 一个数 |(or) 1
的结果就是把二进制最末位强行变成1
如果需要把二进制最末位变成0,对这个数or 1之后再减一就可以了
00111
11100
(|或者or)
----------------
11111
三、按位异或( ^ )
按位异或
运算通常用于对二进制的特定一位进行取反
操作
按位异或
运算的逆运算是它本身,也就是说两次异或
同一个数最后结果不变
0和1异或
结果为1
1和0异或
结果为1
1和1异或
结果为0
0和0异或
结果为0
00111
11100
(^或者xor)
----------------
11011
四、右移位运算 ( >> )
右移1位即除以2,并且取整
m_Height>>4相当于:m_Height/16
五、左移位运算 ( << )
若左移一位,则其数值变为a*2
六、按位取反 ( ~ )
按位取反也就是基于二进制
进行的一个操作. 所不同的是,在完成按位取反之后,还需要转换为“原码”
如果开始二进制是 : 00111
按位取反后的二进制: 11000
⚠️: 比如十进制数字: 57
表示成8位二进制是 : 00111001
按位取反后的二进制: 11000110
负数
在计算机里要用其补码
来表示: 补码 = 符号位以后按位取反 + 1
所以我们得到的(11000110
)是一个补码, 知道了补码,然后求原码
:
负整数原码 = 补码除符号位后按位取反 + 1
所以11000110
除符号位以后按位取反后为 10111001 再加1 则为(10111010)
10111010 换成十进制为:-58
因此 ~57 = -58
例如: 32位cpu下, 计算~5的值, 过程如下:
5 的二进制为:0000 0000 0000 0000 0000 0000 0000 0101
~运算后: 1111 1111 1111 1111 1111 1111 1111 1010
如果忘记了负数的二进制表达方式, 可能会以为它(1……1010)应该表示-10
计算机使用补码表示负数, 所以
求原码 = 1111 1111 1111 1111 1111 1111 1111 1010 除符号位后取反 + 1
= 1000 0000 0000 0000 0000 0000 0000 0110
换成十进制等于: -6
即~5 = -6
因此,可以总结出~按位取反的计算结论是:~n = -(n+1)
例如: ~5 = -(5+1)
~3 = -(3+1) = -4 // 3按位取反后的十进制结果是: -4 ,即 ~3 = -4
~-4 = -(-4+1) = 3
按位取反应用
unsigned int a, b;
a=3; // 按位取反后的十进制结果是: -4 ,即 ~a = -4
b=~a;
printf("b is %d\n",b); // -4
a=~b;
printf("a is %d\n",a); // 3
✅ : 大胆猜测下, 关联对象中 HashMap的key 按位取反的原因是否是这样的原因
按位取反中,-1=0,0=-1, 在C语言中,非0即为真
底层其他常用位运算
常可以看到这样的表达 a|=b
,意思是a =(a|b)
, 这与 a+=b
有点类似(a = a+b
)
int a = 1 , b = 2 ,c = 4 ; // 0x0001,0x0010,0x0100
a |= b; // a = 0x0011 = 3
b |= c; // b = 0x0110 = 6
这种表达很有用,这个方式在很多需要一个值标识多种状态下普遍适用
网友评论