考察对Java位运算的理解和掌握
问:
8|9&10^11的结果是多少?
a.8
b.9
c.10
d.11
解析:
Java的位运算直接对整数类型的位进行操作,这些整数类型包括long,int,short,char,byte
位运算符及其结果
运算符 作用
~ 按位非(NOT)(一元运算)
& 按位与(AND)
| 按位或(OR)
^ 按位异或(XOR)
>> 右移
>>> 右移,左边空出的位以0填充
<< 左移
&= 按位与赋值
|= 按位或赋值
^= 按位异或赋值
>>= 右移赋值
>>>= 右移赋值,左边空出的位以0填充
<<= 左移赋值
位运算符在整数范围内对位操作,因此必须理解两点:Java如何存储整数值、如何表示负数。
所有的整数类型以二进制数位来表示。例如,byte型值42的二进制代码是00101010,其中每个位置代表2次方,最右边以2的0次开始,以此类推。42的二进制代码中即2+8+32=42
所有整数类型(除了char类型)都是有符号的整数,这意味着它们既能表示正数,也能表示负数。Java使用2的补码来表示负数,也就是将其对于的正数的二进制代码取反(即1变0,0变1),然后对取反后的结果加1。例如,42二进制代码取反,由00101010取反得到1010101,再加1,得到11010110,即-42的二进制代码。要对一个负数解码,首先对其所有位取反,然后加1。例如-42,取反后为00101001,然后加1,得到00101010,即42。
如果考虑到零的交叉问题,就容易理解Java语言使用2的补码的原因。假如byte类型的零值用00000000代表,它的补码是仅仅将每一位取反,即生成11111111,代表负零。但问题是负零在整数数学中是无效的。为了解决负零问题,在使用2的补码代表负数的值时,对其值加1。负零加1后就为100000000,但位1太靠左,不适合返回到byte类型的值,因此规定,-0和0的表示方法一样,-1的补码位11111111。
Java使用2的补码存储负数,并且因为Java中的所有整数都是有符号的,所以应用位运算符能够容易地达到意想不到的结果。例如,Java用高位来代表负数,为避免意外,不管高位顺序如何,它决定一个整数的符号。
1.位逻辑运算符
与&,或|,异或^,非~
位逻辑运算符的结果
A B A|B A&B A^B ~A
0 0 0 0 0 1
1 0 1 0 1 0
0 1 1 0 1 1
1 1 1 1 0 0
2.左移运算符
左移运算符<<使指定值的所有位都左移规定的位数。格式如下
value<<num
这里,num指定value移动的位数。左移运算符<<使指定值的所有位都左移num位,每左移一个位,高阶位都被移出(并且丢弃),并用0填充右边位。这意味着当左移的运算数是int类型时,每移动1位,它的第31位就要被移出并且丢弃;当左移的运算数是long类型时,每移动1位,它的第63位就要被移出并且丢弃。
在对byte和short类型的值进行移位运算时,求职者必须小心。因为Java在对表达式求值时,将自动把byte和short类型扩大为int型,而且表达式的值也是int型。对byte和short类型的值进行移位运算的结果是int型,如果左移不超过31位,原来byte和short对应各位的值也不会丢弃。但是,如果对一一个负的byte或者short类型的值进行移位运算,被扩大为int型后,它的符号也被扩展。这样,整数值结果的高位就会被1填充。因此,为了得到正确的结果,必须舍弃得到结果的高位。这样做最简单的办法是将结果转换为byte型。示例代码如下:
public class ByteShift {
public static void main(String[] args) {
byte a = 64, b;
int i;
i = a << 2;
b = (byte) (a << 2);
System.out.println(a);//64
System.out.println(i);//256
System.out.println(b);//0
}
}
变量a在赋值表达式中被扩大为int型,64(01000000)左移两次后生成值256(100000000),被赋值被变量i。经过左移后变量b中唯一1被移除,全部是0所以才是0。
每次左移都可以使原来的操作数翻倍,可以使用这个办法来快速的2的乘法。但要小心,如果将1移进高阶位(31或63位),该值会变成负值
public class MultByTwo {
public static void main(String[] args) {
int num = 0xFFFFFFE;// 268435454
System.out.println(num);
for (int i = 0; i < 4; i++) {
num = num << 1;
System.out.println(num);
//536870908
//1073741816
//2147483632
//-32
}
}
}
3.右移运算符
右移运算符>>使指定值的所有位都右移规定的位数。格式如下
value>>num
这里,num指定value移动的位数。右移运算符>>使指定值的所有位都右移num位。
将值32右移两次,结果为8。int a=32;a=a>>2;//a值为8
当值中某些位被移出时,这些位的值会舍弃。如:35右移两次,他的两个低位会舍弃,结果还是8。int a=35;a=a>>2;//a值为8
将值右移一次,就相当于将该值除以2并舍弃了余数。可以利用这个特点将一个整数进行快速的2的除法。当然,要确保不会将该数原有的任何一位移出。
右移时,被移走的最高位(最左边的)由原本最高位的数字补充。例如:如果要移走的值为负数,每次右移都要在左边补1;如果移走的数是正数,每次右移都要在左边补0,这叫做符号位扩展。例如,-8>>1是-4,二进制表示如下:
11111000 -8
>>1
11111100 -4
注:由于符号位扩展每次高位补1,所以-1右移结果总是-1
4.无符号右移
正如上面描述的,每一次右移,>>运算符总是自动地用它的先前最高位的内容补移位后的最高位。这样做保留了原值的符号,但有时这样的操作并不是我们想要的。例如,如果进行移位操作的运算数不是数字值,就不希望进行符号位扩展(保留符号位),在处理像系值或图形时,这种情况相当普遍。在这种情况下,不管运算数的初值是什么,希望移位后总是在高位(最左边)补0无符号移动(unsignedshift)。这时可以使用Java的无符号右移运算符>>>,它总是在左边补0。下面的程序段说明了无符号右移运算符>>>.在示例中,受量a被赋值为-1,用二进制表示32位全是1.这个值被无符号右移24位,当然忽略了符号位扩展,左边总是补0.最后将得到的值255赋给变量a.示例代码如下:
int a=-1;
a=a>>>24;
下面用二进制形式进一步 说明该操作。
int型-1的二进制代码为11111111 11111111 11111111 11111111。
无符号右移24位:>>> 24。
得到int型255的二进制代码00000000 00000000 00000000 11111111由于无符号右移运算符>>>只是对32位和64位的值有意义,所以并不像想象中那样有用。在表达式中,过小的值总是被自动扩大为int型。这意味着符号位扩展和移动总是发生在32位,而不是8位或16位。这样,对第7位以0开始的byte型的值进行无符号移动是不可能的,因为在实际移动运算时,是对扩大后的32位值进行操作。示例代码如下:
public class demo {
public static void main(String[] args) {
char hex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte b = (byte) 0xf1;
byte c = (byte) (b >> 4);
byte d = (byte) (b >>> 4);
byte e = (byte) ((b & 0xff) >> 4);
System.out.println("b=0x" + hex[(b >> 4) & 0x0f] + hex[b & 0x0f]);//b=0xf1
System.out.println("b>>4=0x" + hex[(c >> 4) & 0x0f] + hex[c & 0x0f]);//b>>4=0xff
System.out.println("b>>>=0x" + hex[(d >> 4) & 0x0f] + hex[d & 0x0f]);//b>>>=0xff
System.out.println("(b&0xff)=0x" + hex[(e >> 4) & 0x0f] + hex[e & 0x0f]);//(b&0xff)=0x0f
}
}
5.位运算符赋值
所有的二进制位运算符都有一种将赋值与位运算组合在一起的简写形式。
例如:
两个语句都是将变量a右移4位赋给a。a=a>>4;a>>=4;
两个语句都是将表达式aORb运算后的结果赋给a。a=a|b;a|=b;
答:d