按位或 按位与 按位异或 (| & ^) 入门及技巧

| 按位或:
    参与运算的两数各对应的二进位相或.只要对应的二个二进位有一个为1时,结果位就为1
        例如: 1|2 : 0001 | 0010 = 0011
                 9|5 : 1001 | 0101 = 1101    所以9|5=13

& 按位与:
    参与运算的两数各对应的二进位相与.只有对应的两个二进位均为1时,结果位才为1,否则为0
        例如: 1&2 : 0001 & 0010 = 0000
                 9&5 : 1001 & 0101 = 0001    所以9&5=1

^ 按位异或:
    参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1
        例如: 1^2 : 0001 ^ 0010 = 0011
                 9^5 : 1001 ^ 0101 = 1100    所以9^5=12

记忆表
名称符号记忆技巧
按位或|2个中有一个为1,结果为1
按位与&2个中有两个都为1,结果为1
按位异或^2个中有两个都不相同,结果为1

(1)按位异或可以用来使某些特定的位翻转,如对数10100001的第2位和第3位翻转,可以将数与00000110进行按位异或运算。

          10100001^00000110=10100111 //1010 0001 ^ 0x06 = 1010 0001 ^ 6

(2)通过按位异或运算,可以实现两个值的交换,而不必使用临时变量。例如交换两个整数a,b的值,可通过下列语句实现:

    a=10100001,b=00000110

    a=a^b;   //a=10100111

    b=b^a;   //b=10100001

    a=a^b;   //a=00000110

(3)异或运算符的特点是:数a两次异或同一个数b(a=a^b^b)仍然为原值a.

(4)快速比较两个值

先让我们来一个简单的问题;

判断两个int数字a,b是否相等,你肯定会想到判断a - b == 0,但是如果判断a ^ b == 0效率将会更高.

(5)我们使用异或来判断一个二进制数中1的数量是奇数还是偶数

例如:求101000011的数量是奇数还是偶数; 答案:1 ^ 0 ^ 1 ^ 0 ^ 0 ^ 0 ^ 0 ^ 1 = 1,结果为1就是奇数个1,结果为0就是偶数个1; 应用:这条性质可用于奇偶校验(Parity Check),比如在串口通信过程中,每个字节的数据都计算一个校验位,数据和校验位一起发送出去,这样接收方可以根据校验位粗略地判断接收到的数据是否有误.

(6)面试题:互换二进制数的奇偶位;

题目:写一个宏定义,实现的功能是将一个int型的数的奇偶位互换,例如6的2进制为00000110,(从右向左)第一位与第二位互换,第三位与第四位互换,其余都是0不需要交换,得到00001001,输出应该为9;

思路:我们可以把我们的问题分为三步(难道这也是分治法吗 -。-),第一步,根据原值的偶数位获取到目标值的奇数位,并把不需要的位清零;第二步,根据原值的奇数位获取到目标值的偶数位,并把不需要的位清零;第三步:把上述两个残缺的目标值合并成一个完整的目标值;

public static void main(String[] args) {
        int n = 6;
        System.out.println(n);
        int end = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1);
        System.out.println(end);
    }

0xAAAA=1010 1010 1010 1010

0x5555=0101 0101 0101 0101

(通过按位与& ,2个相同的才会被保留下来)

6 = 00000110 & 0xAAAA  = 0010  取出偶数位

6 = 00000110 & 0x5555   = 0100  取出奇数位

偶数位右移一位,奇数位左移一位(偶数位末尾为0,奇数为末尾可能会有值)

010 >> 1 = 0001 , 0100  <<  1 =  1000

两者相加就得到了交换后的数字

1000 | 0001 = 1001 

结果就为9

(7)最最常出现的面试题:一个整型数组里除了N个数字之外,其他的数字都出现了两次,找出这N个数字;

比如,从{1, 2, 3, 4, 5, 3, 2, 4, 5}中找出单个的数字: 1

让我们从最简单的,找一个数字开始;

  public static void SingleNumber() {
        int[] a = { 1, 2, 3, 4, 5, 3, 2, 4, 5 };
        int result = 0;
        for (int i = 0; i < a.length; i++) {
            result ^= a[i];
        }
        System.out.println(result);
    }

当两对应的二进位相异时,结果为1。结果就会保留只出现一次的数字

比如,从{1, 2, 3, 4, 5, 3, 2, 4, 5,6}中找出单个的数字: 1 6

同理如果有多个的话,就会出现这2个数的按位异或值

1 ^ 6 = 1 ^ 110 = 111 也就是 7 

public static void SingleNumber2() {
        int[] a = { 1, 2, 3, 4, 5, 3, 2, 4, 5, 6 };
        int result = 0;
        for (int i = 0; i < a.length; i++) {
            result ^= a[i];
        }
        System.out.println(result);
    }

直接找最低位1,最低为的1一定是这2个数的某一个(相同都为0了),那么将根据这个最低位为1的数,将数组分为二部分,这样就变成了每个数组单独求一个出现一次的数了(重复的肯定在同一个数组中,二个数相同)

  public static void SingleNumber2() {
        int[] a = { 1, 2, 3, 4, 5, 3, 2, 4, 5, 6 };
        int result = 0;
        for (int i = 0; i < a.length; i++) {
            result ^= a[i];
        }
        int swap = result & ~(result - 1);// 算出最低位为1的数
        int num1 = 0, num2 = 0;
        for (int i = 0; i < a.length; i++) {
            if ((a[i] & swap) == 0) {
                num1 ^= a[i];
            }
        }
        num2 = result ^ num1;
        System.out.println(num1 + " " + num2);
    }

 已知:三个数的低位第一位为1的位置有三种情况,一种就是全相同,一种就是两个不同,一个相同,一种就是三个不同;

比如 第3位是最低位

全相同000100  ^  001100 ^ 010100  = 011100结果成立
两个不同,一个相同000100  ^  011000 ^ 010000  = 001100结果成立
三个不同010000  ^  010010 ^ 010001 =  010011结果不成立

          

目前无法做

 

(8)实现一个方法,判断一个正整数是否是2的乘方(比如16是2的4次法,返回True;18不是2的乘方,返回False)

先观察一下  

10进制2进制等于2的乘方
1610000
32100000
1810010不是
3011110不是

发现:

        等于2的乘方的数,最高位为1,其余都为0.

        不等2的乘方的数,最高位为1,其余有其他1.

现在这些数减去1

10进制2进制        N-1等于2的乘方
1601000015001111
3210000031011111
1801001017010001不是
3001111029011101不是

发现了没有,15,31的二进制全为1.如果他们与16,32做&运算

          结果为  16  &  15  ➨ 010000 & 001111 = 0 

                       31 &  32  ➨  100000 & 011111 =0 

而 18 和 17 做 & 运算

          结果为  18 & 17  ➨ 010010 & 010001 = 010000 =16

为什么了?

          因为满足2的乘方的数(除了最高为1,其他都为0),减去一后,全部位为1.做&运算全部被消除;而不满足2的乘方的数(除了最高位为1,其他位还有1),减去一后,做&运算不能全部都消除,最少会保留下最高位(即结果不为0)。

代码如下:

private static boolean Ispower(int n) {
        return (n & n - 1) == 0;
    }

时间复杂度 O(1)

(9)用位运算实现加法,(比如 1 和 2 相加不能用“+”符号, 1 ADD 2 = 3)

    比如  2 + 3 = 5

       2        3          5

       ↓        ↓           ↓

     10       11       101

2 ^ 3  =  10 ^ 11  = 01  这里求到是没进位的值(还需要进位)

比如 2  ^ 1 = 10 ^ 01 = 11 (这个2个数就没有进位,就不用进位)

2 & 3 =  10 & 11 = 10  这里求到是 进位值 

2 & 1 = 10 &  01 = 00  进位为0

那么进位左移一位,即为进位数(2 & 3) << 10 → 10 << 1 → 100

直到进位等于0为止.

进位值  和 没进位的值 做 ^运算 → 这里求到是没进位的值(程序不知道后面有没有进位)

100  ^ 01 = 101

进位值  和 没进位的值(旧) 做 &运算 →这里求到是 进位值 

100 ^ 01 = 0

000 << 1 = 0000   →  进位左移一位

进位等于0 说明没有进位了

那么和值就为101 ,也就是这新值。

public static int add(int a, int b) {
        int swap = 0;
        while (b != 0) {
            swap = a ^ b;
            b = (a & b) << 1;
            a = swap;
        }
        return swap;
    }

 

 

 

参考来源:http://lijinma.com/blog/2014/05/29/amazing-xor/
参考来源:https://blog.csdn.net/kybd2006/article/details/3727218 
参考来源:https://blog.csdn.net/Super_Me_Jason/article/details/79707992 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值