我们之前,在计算机当中,它是以二进制的形式来进行数的存储和加减乘除的。
讲解之前,我们先来了解一下基本的位操作
位操作 | 含义 | 具体含义 |
---|---|---|
& | 表示与 | 两位同时为 1,结果才为 1,否则为 0 |
"| " | 表示或 | 两位中只要有一个为 1,结果为 1 |
^ | 表示异或 | 两位中数字不相同为 1,否则为 0 |
~ | 表示取法 | 为单目运算符,表示取反 |
<< | 左移运算符 | 向左移动一位 |
>> | 右移运算符 | 向右移动一位 |
两位同时为“1”,结果才为“1”,否则为“0”。
0 & 0 = 0; 0 & 1 = 0; 1 & 0 = 0; 1 & 1 = 1
两位中只要有一位为 1,结果就为 1
0 | 0 = 0; 0 | 1= 1; 1 | 0 = 1; 1 | 1 = 1
两位中只要数字不相同,结果即为 1
0 ^ 0 = 0;1 ^ 0= 1;0 ^ 1 = 1; 1 ^ 1 = 0
左移运算 左移n位的时候,最左边的n位将被丢弃,同时在最右边补上n个0.比如:
对于 >> 运算符来说,右移:(正数左补0、负数左补1)
System.out.println(-3>>1);
结果是 -2 ,为什么会是 -2 呢?下面我们来看一下
转换成2进制为1111 1111 1111 1111 1111 1111 1111 1101
右移一位为1111 1111 1111 1111 1111 1111 1111 1110,显而易见此为-2补码.
对于 >>> 运算符
无符号右移(正数左补0负数左补1)
System.out.println(-3>>>1);
结果为 2147483646
1111 1111 1111 1111 1111 1111 1111 1101无符号右移,高位补0,
01111 1111 1111 1111 1111 1111 1111 1110,其为2147483646的原码.
或运算符可以用来组合多种值。
举个例子,在 Android 中,我们经常会看到这样的写法
TextView tv = new TextView(context);
tv.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
即 TextView 垂直居中并且向左对齐。
如果不采用或运算符来写,采用布尔值来记录每一种状态,那每一次绘制 TextView 的时候,你得判断多少次,才能得出 TextView 的对其方向。
因为 TextView 的对齐方向有可能 是左上,左下,左中,右上,右下,右中,中上,中下,垂直居中 ----。
采用或运算符来组合多种值的时候,为了便于获取原来的状态,这里我们需要注意一下,采用位上错开的原则:
什么叫位上错开?
举个例子,假设 LEFT 的值为 0x0001,即第一位为1,CENTER_VERTICAL 的值为 0x0002 ,即第二位为 1,与 LEFT 第二位的值不同,那这样就叫位上错开。
into LEFT = 0x0001;
int CENTER_VERTICAL = = 0x0002;
位上错开有什么好处呢?
上面我们说到或运算符可以用来组合多种值,那我们如何判断组合后的值含有某种状态,其实很简单。
跟原来的某一状态进行与,若值与该状态相等,证明含有该状态
int gravity = tv.getGravity();
if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
}
public boolean isOdd(int num) {
return (num & 1) != 0;
}
异或运算符,只要两位不相等,结果为 1, 否则为 0.
因此,我们容易得出这一样的结果
使用异或运算符实现值的交换
void Swap(int a, int b)
{
if (a != b)
{
a ^= b;
b ^= a;
a ^= b;
}
}
再来个实例说明下以加深印象。int a = 13, b = 6;
a的二进制为 13=8+4+1=1101(二进制)
b的二进制为 6=4+2=110(二进制)
非运算符的作用就是按位取反。
或许,你会有这样的一个疑问,如果我想剔除当前已经包含的一个值,需要怎么办?这时候就是“非”和“与”运算符联合使用的时候了,且看下面代码
int left = 0x001;
int right = 0x002;
int top = 0x008;
int state = left | right;
System.out.println("剔除 right 状态前 " + state);
state &= ~right;
System.out.println("剔除 right 状态后 " + state);
state &= ~top;
System.out.println("剔除不存在的 top 状态 " + state);
输出 log 如下
剔除 right 状态前 3 剔除 right 状态后 1 剔除不存在的 top 状态 1
如对于-11和11,可以通过下面的变换方法将-11变成11
1111 0101(二进制) –取反-> 0000 1010(二进制) –加1-> 0000 1011(二进制)
同样可以这样的将11变成-11
0000 1011(二进制) –取反-> 0000 0100(二进制) –加1-> 1111 0101(二进制)
int SignReversal(int a)
{
return ~a + 1;
}
int my_abs(int a)
{
int i = a >> 31;
return i == 0 ? a : (~a + 1);
}
现在再分析下。对于任何数,与0异或都会保持不变,与-1即0xFFFFFFFF异或就相当于取反。因此,a与i异或后再减i(因为i为0或-1,所以减i即是要么加0要么加1)也可以得到绝对值。所以可以对上面代码优化下:
int my_abs(int a)
{
int i = a >> 31;
return ((a ^ i) - i);
}
/**
* 判断第 n 位是 否为 1,注意 n 从 0 开始
* @param num
* @param n
* @return
*/
public static boolean nBitisOne(long num, int n) {
if (num <= 0) {
return false;
}
return (1 & (num >> n)) == 1;
}
其实位操作符还有很多妙用,由于篇幅有限,这里不再一一展开描述,下一篇,准备讲解常见的位操作算法题,敬请期待。