位运算

前言:程序中的所有数在计算机内存中都是以二进制的形式储存的。所以一些底层代码的操作也是有位运算的身影;对于机器而言,它们只认识0和1,因为这样更加容易理解和执行,速度也会更快。通过学习也有助于程序员理解底层操作,优化自己的代码,也可以从多个角度,多个思路来解决问题。

位运算: 用于对整数类型变量的二进制形式进行操作。

  • 位逻辑运算符:
    & (位 “与”) and
    ^ (位 “异或”)
    | (位 “或”) or
    ~ (位 “取反”)
  • 移位运算符:
    (左移) <<
    (右移) >>

(>>>)表示不带符号向右移动二进制数,移动后前面统统补0;两个箭头表示带符号移动。
没有<<<这种运算符,因为左移都是补零,没有正负数的区别。

优先级
位“与”、位“或”和位“异或”运算符都是双目运算符,其结合性都是从左向右的,优先级高于逻辑运算符,低于比较运算符,且从高到低依次为&、^、|

在Java中Integer包装类型有将十进制转化为二进制的方法 toBinaryString()

int a = 33, b = 36;
System.out.println("默认十进制输出 a : " + a);
System.out.println("默认十进制输出 b : " + b);
System.out.println("二进制输出");
System.out.println("转换二进制输出 a : " + Integer.toBinaryString(a));
System.out.println("转换二进制输出 b : " + Integer.toBinaryString(b));

输出结果为
十进制二进制图
可以看到33的二进制为100001,而36的二进制为100100。

十进制转为二进制就是除以2能不能除的掉,能就是0,不行就是1。

像100100,我们通常将前面的1叫做高位,而最后面的0就叫做低位(底位)

1. 按位与 “&”

 int n1 = a & b;
 System.out.println("a & b : "+n1);
 System.out.println("a & b : "+Integer.toBinaryString(n1));
 System.out.println(" & 与运算 两个位都为 1 时,结果才为 1");
 System.out.println("【口诀】: 与同 1 为 1");

我们将3336,会发现他的十进制结果是32,二进制的结果为100000
&运行图
&的口诀:与同1为1

说明:为什么a&b等于32,利用与(&)的口诀,与同1为1。a的二进制是100001,b的二进制是100100,两者的比较图如下:
33和36进制的比较图
可以看到,从右边开始是2的0次方,往左依次增加,有1的地方就累加下来,就是他的十进制结果。而a&b的结果就是32了。
32的十进制结果图
2. 按位或 “|”

 int n2 = a | b;
 System.out.println("a | b : "+n2);
 System.out.println("a | b : "+Integer.toBinaryString(n2));
 System.out.println(" | 或运算 两个位都为 0 时,结果才为 0");
 System.out.println("【口诀】: 或同 0 为 0");

那么如果我们将3336呢?
33或36的结果图
或的口诀:或同0为0

二进制的特点:

除了底位是1,其他位都是2的次方,也就是说,除了底位是奇数,其他位都是偶数。所以如果是奇偶数的判断,只需要判断底位上放的是0还是1。

那么我们可以来一个判断奇偶数判断的技巧

// 与运算的技巧使用 【判断奇偶数】
 if((a & 1) == 1){
 System.out.println("奇数 : " + a);
 }else{
 System.out.println("偶数 : " + a);
 System.out.println("奇数 & 1 : " + (a & 1));// 注意优先级
 System.out.println("偶数 & 1 : " + (b & 1));
 System.out.println("【口诀】: 1 前补 0 高归底 奇偶只看底 10");
 }

只要它的底位是1的话,那么他就是奇数了,否则就是偶数。
也可以这样子写:

System.out.println("((奇数 >> 1 << 1) == 自己) : " + ((a >> 1 << 1) == a)); // 注意优先级
System.out.println("((偶数 >> 1 << 1) == 自己) : " + ((b >> 1 << 1) == b));

这里的<< 和 >> 在下文会介绍到

3. 按位异或 “^”

 int n3 = a ^ b;
 System.out.println("a ^ b : "+n3);
 System.out.println("a ^ b : "+Integer.toBinaryString(n3));
 System.out.println(" ^ 异或 两个位相同为 0,相异为 1");
 System.out.println("【口诀】: 异或 同为 0 异为 1");

异或图
异或的口诀:异或 同为 0 异为 1

 // 异或运算的技巧使用 【自己和 0】
 int n4 = a ^ a;
 System.out.println("a ^ a : "+n4);
 int n5 = a ^ 0;
 System.out.println("a ^ 0 : "+n5);
 System.out.println("【口诀】: 异或自己为 0 异或 0 为自己");

这里还有一个小技巧,如果异或的是自己的话,则为0,异或0的话则为自己。

口诀:异或自己为 0 异或 0 为自己
我们平常有遇到两个变量交换值的操作,一般我们都是会用一个临时的变量temp变量来就行交换。而如果高级一点的玩法的话就会用到异或。

 // 异或运算的技巧使用 【交换两个数】
 a = a ^ b; // 符合结合律
 b = a ^ b;
 a = a ^ b;
 System.out.println("默认十进制输出 a : "+a);
 System.out.println("默认十进制输出 b : "+b);

那如果想要更加老练的话呢,就利用到了交换律了。

 a ^= b; // 符合交换律
 b ^= a;
 a ^= b;
 System.out.println("默认十进制输出 a : "+a);
 System.out.println("默认十进制输出 b : "+b);
 System.out.println("【口诀】: 我异或等你 你异或等我 我异或等你 我们就不是自己");

当然,这些交换只是整形类型的交换,如果是数组或者对象的交换,还是要用一个temp变量的。

4. 按位非 “~”

 int n6 = ~a;
 System.out.println("~a : "+n6);
 // ~x = -(x+1) ~33 = -34
 int n7 = ~n6;
 // ~x = -(x+1) ~(-34) = -(-34+1) = 33
 System.out.println("~~a : "+n7);
 System.out.println(" ~ 取反 0 变 1,1 变 0");
 System.out.println("【口诀】: 加 1 取负就是位取反 但他不是逻辑取反 反反还是自己哦");

(非)~ 的其实就是取反,但它不是逻辑取反。
(非)~ 图
(非~)的口诀: 加 1 取负就是位取反 但他不是逻辑取反 反反还是自己哦

说到这里,前面的口诀实属有点多,而且容易混乱,特地全部总结了一下,以防忘记。

同1则为1(&),同0则为0(|)
异或 同为 0,异或 异为1
异或自己为0,异或0为自己
非常手段要加1,括号记得别忘记

5. 左移运算符 “<<”

 int n8 = a << 2;
 System.out.println("a << 2 : "+n8);
 System.out.println("a << 2 : "+Integer.toBinaryString(n8));
 int n9 = a << 34;
 System.out.println("a << 34 : "+n9);
 System.out.println("a << 34 : "+Integer.toBinaryString(n9));
 System.out.println(" << 左移 各二进位全部左移若干位,高位丢弃,低位补 0");
 System.out.println("【口诀】: 左移 N 就是乘以 2 的 N 次方 超出则模 2 的 32 次方");

左移其实就是转化为二进制之后全部往左边移动,也可以认为就是乘以2的 N 次方(N代表移动的位数),最大是2的32次方。
左移 << 结果图

像代码中 a << 2就是左移2位,换成十进制就是乘以2的2次方,所以33乘以4等于132。如果是移动34的话,就相当于走了大半天绕回了原点,还是2次方。

例如 a * 32 == a << 5
如果 a * 31 == a << 5 - a 因为他等于32的次方减去一个a

(左移<<)的口诀: 左移 N 就是乘以 2 的 N 次方 超出则模 2 的 32 次方

6. 右移运算符 “>>”

 int n10 = a >> 2;
 System.out.println("a >> 2 : "+n10);
 System.out.println("a >> 2 : "+Integer.toBinaryString(n10));
 int n11 = a >> 34;
 System.out.println("a >> 34 : "+n11);
 System.out.println("a >> 34 : "+Integer.toBinaryString(n11));
 System.out.println(" >> 右移 各二进位全部右移若干位,对无符号数,高位补 0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补 0(逻辑右移)");
 System.out.println("【口诀】: 右移 N 就是除以 2 的 N 次方 超出则 N-32");

右移 >> 结果图右移其实和左移差不多,只不过相反而已,就是往右边移动了,而是除以2的N次方。33除以2的2次方,四舍五入得出结果是8。

(右移>>)的口诀:右移 N 就是除以 2 的 N 次方 超出则 N-32

求a绝对值

通常我们会用到下面这种写法

 if(a>=0) {
	 return a;
 }else {
	 return -a;
 }

但是如果用位运算的角度来看的话,就可以这么写

 // 求 a 的相反数 -a 除了 (-1*a) 也可以 (~a+1)
 // 求 a 的 绝对值 |a| a>>31==0?a:(~a+1)  
 a>>31==0?a:(~a+1)  

这里相当于右移31位,只保留最高位的数,如果是0,那么就是a自己本身了,如果是负数,那我们就取反,变成他的绝对值。

7. 右移运算符 “>>>”

(>>>)表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0
正数: r = 20 >>> 2 的结果与 r = 20 >> 2 相同;

负数: r = -20 >>> 2 的结果是1073741819

注:以下数据类型默认为int 32位

分析图
如果想深入了解一下原码、反码、补码可以看看这篇文章 Java 原码、反码、补码分析

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值