计算机采用的是二进制,二进制包括两个数码:0,1。在计算机的底层,一切运算都是基于位运算实现的。
位运算共有 6 种,分别是:与、或、异或、取反、左移和右移,其中左移和右移统称移位运算,移位运算又分为算术移位和逻辑移位。上述位运算中,只有取反是一元运算,其余的都是二元运算。
与运算的符号是 &,运算规则是:对于每个二进制位,当两个数对应的位都为 1 时,结果才为 1,否则结果为 0。
或运算的符号是 ∣,运算规则是:对于每个二进制位,当两个数对应的位都为 0 时,结果才为 0,否则结果为 1。
异或运算的符号是 (在代码中用 ∧ 表示异或),运算规则是:对于每个二进制位,当两个数对应的位相同时,结果为 0,否则结果为 1。
取反运算的符号是 ,运算规则是:对一个数的每个二进制位进行取反操作,0 变成 1,1 变成 0。
以下例子显示上述四种位运算符的运算结果,参与运算的数字都采用有符号的 8 位二进制表示。
46 的二进制表示是 00101110,51 的二进制表示是 00110011。考虑以下位运算的结果。
- 46 & 51 的结果是 34,对应的二进制表示是 00100010。
- 46 ∣ 51 的结果是 63,对应的二进制表示是 00111111。
- 46⊕51 的结果是 29,对应的二进制表示是 00011101。
- 46∼46 的结果是 −47,对应的二进制表示是 11010001。
- 51∼51 的结果是 -52,对应的二进制表示是 11001100。
移位运算按照移位方向分类可以分成左移和右移,按照是否带符号分类可以分成算术移位和逻辑移位。
左移运算的符号是 <<。左移运算时,将全部二进制位向左移动若干位,高位丢弃,低位补 0。对于左移运算,算术移位和逻辑移位是相同的。
右移运算的符号是 >>。右移运算时,将全部二进制位向右移动若干位,低位丢弃,高位的补位由算术移位或逻辑移位决定:
- 算术右移时,高位补最高位;
- 逻辑右移时,高位补 0。
以下例子显示移位运算的运算结果,参与运算的数字都采用有符号的 8 位二进制表示。
29 的二进制表示是 00011101。29 左移 2 位的结果是 116,对应的二进制表示是 01110100;29 左移 3 位的结果是 -24,对应的二进制表示是 11101000。
50 的二进制表示是 00110010。50 右移 1 位的结果是 25,对应的二进制表示是 00011001;50 右移 2 位的结果是 12,对应的二进制表示是 00001100。对于 0 和正数,算术右移和逻辑右移的结果是相同的。
-50 的二进制表示是 11001110。-50 算术右移 2 位的结果是 -13,对应的二进制表示是 11110011;-50 逻辑右移 2 位的结果是 51,对应的二进制表示是 00110011。
右移运算中的算术移位和逻辑移位是不同的,计算机内部的右移运算采取的是哪一种呢?
对于C/C++ 而言,数据类型包含有符号类型和无符号类型,其中有符号类型使用关键字 signed 声明,无符号类型使用关键字unsigned 声明,两个关键字都不使用时,默认是有符号类型。对于有符号类型,右移运算为算术右移;对于无符号类型,右移运算为逻辑右移。
对于 Java 而言,不存在无符号类型,所有的表示整数的类型都是有符号类型,因此需要区分算术右移和逻辑右移。在Java 中,算术右移的符号是 >>,逻辑右移的符号是 >>>。
观察上面的例子可以看到,移位运算与乘除法有密切的关联性。由于计算机的底层的一切运算都是基于位运算实现的,因此使用移位运算实现乘除法的效率显著高于直接乘除法的效率。
左移运算对应乘法运算。将一个数左移 kk 位,等价于将这个数乘以 。例如,29 左移 2 位的结果是 116,等价于
。当乘数不是 22 的整数次幂时,可以将乘数拆成若干项 2 的整数次幂之和,例如,
等价于 (a<<2)+(a<<1)。对于任意整数,乘法运算都可以用左移运算实现,但是需要注意溢出的情况,例如在 8 位二进制表示下,29 左移 3 位就会出现溢出。
算术右移运算对应除法运算。将一个数右移 k 位,相当于将这个数除以 。例如,50 右移 2 位的结果是 12,等价于 50/4,结果向下取整。
从程序实现的角度,考虑程序中的整数除法,是否可以说,将一个数(算术)右移 k 位,和将这个数除以 等价?对于 0 和正数,上述说法是成立的,整数除法是向 0 取整,右移运算是向下取整,也是向 0 取整。但是对于负数,上述说法就不成立了,整数除法是向 0 取整,右移运算是向下取整,两者就不相同了。例如,(−50)>>2 的结果是 -13,而 (−50)/4 的结果是 -12,两者是不相等的。
因此,将一个数(算术)右移 k 位,和将这个数除以 是不等价的。
位运算的性质有很多,此处列举位运算中的与、或、异或和取反的常见性质。假设以下出现的变量都是有符号整数。
-
其他性质:
利用上述性质,可以巧妙地解决很多位运算的题目。