zoukankan      html  css  js  c++  java
  • 位操作基础篇位操作全面总结

    一. 位操作基础

    基本的位操作符有与、或、异或、取反、左移、右移这6种,它们的运算规则如下所示:


    注意以下几点:

    1.  在这6种操作符,只有~取反是单目操作符,其它5种都是双目操作符。

    2.  位操作只能用于整形数据,对float和double类型进行位操作会被编译器报错。

    3.  对于移位操作,在微软的VC6.0和VS2008编译器都是采取算术称位即算术移位操作,算术移位是相对于逻辑移位,它们在左移操作中都一样,低位补0即可,但在右移中逻辑移位的高位补0而算术移位的高位是补符号位。如下面代码会输出-4和3。

    [cpp]  view plain copy
     
    1. int a = -15, b = 15;  
    2. printf("%d %d\n", a >> 2, b >> 2);  

    因为15=0000 1111(二进制),右移二位,最高位由符号位填充将得到0000 0011即3。-15 = 1111 0001(二进制),右移二位,最高位由符号位填充将得到1111 1100即-4(见注1)。

    4.  位操作符的运算优先级比较低,因为尽量使用括号来确保运算顺序,否则很可能会得到莫明其妙的结果。比如要得到像1,3,5,9这些2^i+1的数字。写成int a = 1 << i + 1;是不对的,程序会先执行i + 1,再执行左移操作。应该写成int a = (1 << i) + 1;

    5.  另外位操作还有一些复合操作符,如&=、|=、 ^=、<<=、>>=。

    二. 常用位操作小技巧

    下面对位操作的一些常见应用作个总结,有判断奇偶、交换两数、变换符号及求绝对值。这些小技巧应用易记,应当熟练掌握。

    1.判断奇偶

    只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。

    下面程序将输出0到100之间的所有奇数。

    [cpp]  view plain copy
     
    1. for (i = 0; i < 100; ++i)  
    2.     if (i & 1)  
    3.         printf("%d ", i);  
    4. putchar('\n');  

    2.交换两数

    一般的写法是:

    [cpp]  view plain copy
     
    1. void Swap(int &a, int &b)  
    2. {  
    3.     if (a != b)  
    4.     {  
    5.         int c = a;  
    6.         a = b;  
    7.         b = c;  
    8.     }  
    9. }  

    可以用位操作来实现交换两数而不用第三方变量:

    [cpp]  view plain copy
     
    1. void Swap(int &a, int &b)  
    2. {  
    3.     if (a != b)  
    4.     {  
    5.         a ^= b;  
    6.         b ^= a;  
    7.         a ^= b;  
    8.     }  
    9. }  

    可以这样理解:

    第一步  a^=b 即a=(a^b);

    第二步  b^=a 即b=b^(a^b),由于^运算满足交换律,b^(a^b)=b^b^a。由于一个数和自己异或的结果为0并且任何数与0异或都会不变的,所以此时b被赋上了a的值。

    第三步 a^=b 就是a=a^b,由于前面二步可知a=(a^b),b=a,所以a=a^b即a=(a^b)^a。故a会被赋上b的值。
    再来个实例说明下以加深印象。int a = 13, b = 6;

    a的二进制为 13=8+4+1=1101(二进制)

    b的二进制为 6=4+2=110(二进制)

    第一步 a^=b  a = 1101 ^ 110 = 1011;

    第二步 b^=a  b = 110 ^ 1011 = 1101;即b=13

    第三步 a^=b  a = 1011 ^ 1101 = 110;即a=6

    3.变换符号

    变换符号就是正数变成负数,负数变成正数。

    如对于-11和11,可以通过下面的变换方法将-11变成11

          1111 0101(二进制) –取反-> 0000 1010(二进制) –加1-> 0000 1011(二进制)

    同样可以这样的将11变成-11

          0000 1011(二进制) –取反-> 0000 0100(二进制) –加1-> 1111 0101(二进制)

    因此变换符号只需要取反后加1即可。完整代码如下:

    [cpp]  view plain copy
     
    1. //by MoreWindows( http://blog.csdn.net/MoreWindows )    
    2. #include <stdio.h>  
    3. int SignReversal(int a)  
    4. {  
    5.     return ~a + 1;  
    6. }  
    7. int main()  
    8. {  
    9.     printf("对整数变换符号 --- by MoreWindows( http://blog.csdn.net/MoreWindows )  ---\n\n");  
    10.     int a = 7, b = -12345;  
    11.     printf("%d  %d\n", SignReversal(a), SignReversal(b));  
    12.     return 0;  
    13. }  

    4.求绝对值

    位操作也可以用来求绝对值,对于负数可以通过对其取反后加1来得到正数。对-6可以这样:

          1111 1010(二进制) –取反->0000 0101(二进制) -加1-> 0000 0110(二进制)

    来得到6。

    因此先移位来取符号位,int i = a >> 31;要注意如果a为正数,i等于0,为负数,i等于-1。然后对i进行判断——如果i等于0,直接返回。否之,返回~a+1。完整代码如下:

    [cpp]  view plain copy
     
    1. //by MoreWindows( http://blog.csdn.net/MoreWindows )  
    2. int my_abs(int a)  
    3. {  
    4.     int i = a >> 31;  
    5.     return i == 0 ? a : (~a + 1);  
    6. }  

    现在再分析下。对于任何数,与0异或都会保持不变,与-1即0xFFFFFFFF异或就相当于取反。因此,a与i异或后再减i(因为i为0或-1,所以减i即是要么加0要么加1)也可以得到绝对值。所以可以对上面代码优化下:

    [cpp]  view plain copy
     
    1. //by MoreWindows( http://blog.csdn.net/MoreWindows )  
    2. int my_abs(int a)  
    3. {  
    4.     int i = a >> 31;  
    5.     return ((a ^ i) - i);  
    6. }  

    注意这种方法没用任何判断表达式,而且有些笔面试题就要求这样做,因此建议读者记住该方法(^_^讲解过后应该是比较好记了)。

  • 相关阅读:
    Java对象的生命周期与作用域的讨论(转)
    [置顶] Oracle学习路线与方法
    Java实现 蓝桥杯 算法训练 未名湖边的烦恼
    Java实现 蓝桥杯 算法训练 未名湖边的烦恼
    Java实现 蓝桥杯 算法训练 未名湖边的烦恼
    Java实现 蓝桥杯 算法训练 最大的算式
    Java实现 蓝桥杯 算法训练 最大的算式
    Java实现 蓝桥杯 算法训练 最大的算式
    Java实现 蓝桥杯 算法训练 最大的算式
    Java实现 蓝桥杯 算法训练 最大的算式
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3049973.html
Copyright © 2011-2022 走看看