IT兄弟连 Java语法教程 位运算符

Java定义了几个位运算符,它们都可以用于整数类型(long、int、short、byte以及char)。这些运算符对操作数的单个位进行操作。表1 对位运算符进行了总结。


表1  位运算符


796386ad155f4adf8947ba37a2bf38df.png


由于位运算符是对整数中的位进行操作,因此理解这类操作会对数值造成什么影响是很重要的。特别是,掌握Java存储整数数值的方式以及如何表示负数是有用的。因此,在介绍位运算符之前,先简要描述以下这两个主题。

在Java中,所有整数类型都由宽度可变的二进制数字表示。例如,byte型数值42的二进制形式是00101010,其中每个位置表示2的幂,从最右边的20开始。向左的下一个位置位21,即2;接下来是22,即4;然后是8、16、32,等等,所以42在位置1、3、5(从右边开始计算,最右边的位计数位0)被设置1;因此,42是21+23+25的和,即2+8+32。

所有整数类型(char类型除外)都是有符号整数,这意味着它们既可以表示正数,也可以表示负数。java使用所谓的“2的补码”进行编码,这意味着负数的表示方法为:首先反转数值中的所有位(1变为0,0变为1),然后再将结果加1.例如,-42的表示方法位:首先反转42中所有位(00101010),得到11010101,然后加1,结果为11010110,即-42。为了解码负数,首先先反转所有位,然后加1.例如,反转-42(11010110),得到00101001,即41,所以再加上1就得到了42。

如果分析“零交叉”问题,就不难理解Java(以及大多数其它计算机语言)使用2的补码表示负数的原因。假定对于byte型数值,0被表示为00000000.如果使用1的补码,简单地反转所有位,得到11111111,这会创建-0.但问题是,再整数数学中,-0是无效的。使用2的补码表示负数可解决这个问题。如果使用2的补码,1被加到补码上,得到100000000,这样就在左边新增了一位,超出了byte类型表示的范围,从而得到了所期望的行为,即-0和0相同,并且-1被编码位11111111.尽管再前面的例子中使用的是byte数值,但是相同的基本原则被应用与Java中的所有整数类型。

因为Java使用2的补码存储负数,并且因为Java中的所有整数都是有符号数值,所以应用位运算符时很有可能产生意外的结果。例如,不管是有意还是无意的,将高阶位改为1,都会导致结果值被解释为负数。为了避免产生不愉快的结果,只需要记住高阶位决定了整数的符号,而不管高阶位是如何设置的。

扫描二维码关注公众号,回复: 6710316 查看本文章


1  位逻辑运算符

位逻辑运算符包括&、|、^和~。表2显示了各种位逻辑运算的结果。在后续的使用中,请牢记位运算符是针对操作数中的每个位进行操作的。


表2  位逻辑运算

15dc87929c304133b7ca08a0fa978bb2.png



1)按位取反

也称为“位求补”。一元非运算符“~”可以反转操作数中的所有位。例如数字42,位模式如下:

00101010

进行“非”运算之后,变为:

11010101


2)按位与

对于按位与运算符“&”,如果两个操作数都是1,结果为1,只要其中任何一个操作数为0,结果就为0。下面是一个例子:

  00101010  42

&00001111  15

-------------

  00001010  10


3)按位或

按位或运算符“|”的运算规则为:只要两个操作数中有一个为1,结果就为1。实例如下所示:

 00101010   42

|00001111   10

-------------

 00101111   47


4)按位异或

按位异或运算符“^”的运算规则为:如果只有一个操作数为1,结果为1,否者结果为0。下面的例子演示了“^”运算符的效果。这个例子还演示了按位异或运算的一个有用特性。请注意,只要第二个操作数中的某位为1,就会反转42的位模式中的对应位;只要第二个操作数中的某位为0,第一个操作数中的对应就保持不变。当执行某些类型的位数操作时,将会发现该特性很有用。

 00101010   42

^00001111   10

-------------

 00101010   37


5)使用位逻辑运算符

下面的程序演示了位逻辑运算符的用法:

public class BitLogic{

    public static void main(String[] args){

         String[] binary = {"0000","0001","0010","0011","0100","0101","0110", "0111","1000","1001","1010","1011","1100","1101","1110","1111"};

         int a = 3;

         int b = 6;

         int c = a | b;

         int d = a & b;

         int e = a ^ b;

         int f = (~a & b) | (a & ~b);

         int g = ~a & 0x0f;

         System.out.println("        a = " + binary[a]);

         System.out.println("        b = " + binary[b]);

         System.out.println("      a|b = " + binary[c]);

         System.out.println("      a&b = " + binary[d]);

         System.out.println("      a&b = " + binary[e]);

         System.out.println("~a&b|a&~b = " + binary[f]);

         System.out.println("       ~a = " + binary[g]);

    }

}

在这个例子中,a和b的位模式包含了两个二进制位的所有4种可能:0-0、0-1、1-0以及1-1。根据c和d中的结果,可以看出“|”和“&”对每一位的操作方式。e和f被赋值为相同的值,并演示了“^”运算的工作原理。字符串数组binary中保存了介于0到15的数字的二进制表示形式。在这个例子中,为了显示每个结果的二进制表示形式,对数组进行了索引。二进制数值n的字符串表示恰好存储在binary[n]中。将~a和0x0f(二进制00001111)进行按位与运算,以减小其值,使其小于16,从而可以使用binary数组输出结果。下面图1是该程序的输出。

6c15d3637fc242aab4c55a85635fbaab.png

图1  BitLogic运行结果


2  左移

左移运算符“<<”可以将数值中的所有位向左一定指定的次数,它的一般形式为:

value << num

其中,num指定了将value中的数值向左移动的位数,即“<<”将指定值中的所有位向左移动由num指定的位数。对于每次位移,高阶位被移出(并丢失),右边的位用0补充。这意味着左移int型操作数时,如果某些位一旦超出位31,那么这些位将丢失。如果操作数为long类型,那么超出位63的位会丢失。

当左移byte和short型数值时,Java的自动类型转换会导致意外的结果。您知道,当表达式进行求值时,byte换个short型数值会被提升为int型。而且,这种表达式的结果也是int型。这意味着对byte和short型数值进行左移操作的结果为int型,并且移动的位不会丢失,除非它们超过位31。此外,当将负数的byte和short型数值提升为int型时,会进行符号扩展。因此,高阶位将使用1填充。所以,对byte和short型数值进行左移操作,必须抛弃int型结果的高阶字节。例如,如果左移byte型数值,会先将该数值提升为int型,然后左移。这意味着如果您想要的结果是移位后的byte型数值,就必须丢弃结果的前三个字节。完成这个任务最容易的方法是,简单地将结果转换为byte类型。下面的程序演示了这一概念。

public class ByteShift{

    public static void main(String[] args){

         byte a = 64;

        int i = a << 2;

        byte b = (byte)(a << 2);

        System.out.println("a : " + a);

        System.out.println("i : " + i);

        System.out.println("b : " + b);

    }

}

编译并运行这个程序,控制台将显示如图2所示的信息。

2ff19e37c1874c59b91b08cb93d078b4.png

图2  ByteShift运行结果


由于为了进行求值,a被提升为int类型,因此对64(01000000)左移两次,使得i包含256(100000000)。但是,b中的值为0,因为移位之后,现在低字节为0。只有一位被移出了。

因为每次左移相当于将原始值乘以2,所以程序员经常利用这个事实作为乘以2的高效替代方法。但是需要小心。如果将二进制1移进高阶位(第31位或第63位),结果会变为负数。


2.3  右移

右移运算符“>>”可以将数值中的所有位向右移动指定的次数,它的一般形式为:

value >> num


其中,num指定了将value中的数值向右边移动的位数,即“>>”将指定值中的所有位向右移动由num指定的位数。

下面的代码将数值32向右边移动两位,结果是a被设置为8:

int a = 32;

a = a >> 2;

如果数值中的有些位被“移出”了,这些位会丢失。例如,下面的代码将35右移两位,从而导致两个低阶位丢失,结果是再次将a设置为8:

int a = 35;

a = a >> 2;

用二进制形式分析同一操作,可以更清晰地看出操作过程;

00100011   35

>> 2

00001000    8


每次右移一个值,相当于将改值除以2,并丢弃所有余数。可以利用这一特性,实现高性能的整数除以2操作。

当进行右移操作时,右移后的顶部(最左边)位使用右移前顶部位的值填充。这称为符号扩展,当对负数景行右移操作时,该特性可以保留负数的符号。例如,-8>>1的结果是-4,用二进制表示为:

11111000   -8

>> 1

11111100   -4


有趣的是,如果对-1进行右移,结果总是-1,因为符号扩展使得高阶位总是1。


2.4  无符号右移

每次位移时,“>>”运算符自动使用原来的内容填充高阶位。这个特性可以保持数值的符号。但是,有时这不是期望的效果。例如,如果对那些表示非数值的内容进行位置操作,可能不希望发生符号扩展。当操作基于像素的值和图形时,这种情况非常普遍。对于这种情形,不管高阶位的初始值时什么,通常会希望将0移进高阶位。这就是所谓的无符号右移。为了完成无符号右移,需要使用Java的无符号右移运算符“>>>”,该运算符总是将0移进高阶位。

下面的代码演示了“>>>”的用法。其中,a被设置为-1,这会将所有的32位设置为二进制1。然后将该值右移24位,并使用0填充高端的24位,而忽略常规的符号扩展。该操作将a设置位255.

int a = -1;

a = a >>> 24;

下面时同一操作的二进制表示形式,以进一步演示这个操作的具体过程:

11111111 11111111 11111111 11111111   -1

>>> 24

00000000 00000000 00000000 11111111  255


“>>>”运算符可能不是那么有用,因为只有对于32位和62位数值它才有意义。请记住,表达式中更小的数值会自动提升为int型。


2.5  位运算符与赋值的组合

所有二元位运算符都具有与算术运算符类似的复合形式,这些运算符将赋值运算符和位运算组合到一起。例如,下面的两行语句是等价的。都是将a的值右移4位:

a = a >> 4;

a >>= 4;

类似地,下面这两行语句也是等价的,都是将a设置位表达式“a|b”:

a = a | b;

a |= b;

下面的程序创建了几个整型变量,然后使用复合位运算符对这些变量进行操作:

public class OpBitEquals{

    public static void main(String[] args){

         int a = 1;

         int b = 2;

         int c = 3;

         a |= 4;

         b >>= 1;

         c <<= 1;

         a ^= c;

         System.out.println("a = " + a);

         System.out.println("b = " + b);

         System.out.println("c = " + c);

    }

}

编译并运行这个程序,控制台将显示图3所示的信息。

2a3316fc3e1a4861a695851f3f7cccc9.png

图3  OpBitEquals运行结果


猜你喜欢

转载自blog.51cto.com/14311187/2416450