位运算符作用于二进制数的位上。
用位运算就是直接对整数在内存中的二进制位进行操作,一些场景中使用位运算,其执行效率更高。
原码、反码、补码是有符号整数的三种不同表示方式,其中正数的原码、反码、补码一致。
计算机在存储一个数字时并不是直接存储该数字对应的二进制数字(原码),而是存储该数字对应二进制数字的补码。
原码转换为补码的过程,也可以理解为数据存储到计算机内存中的过程。
本小节例子的二进制位数为八位。
graph LR
0(计算机输入-1) --> 1(原码1000 0001) --> 2(反码1111 1110) --> 3(补码1111 1111) --> 4(内存)
有符号的八位二进制数中,最大数是1000 0000,而左侧1表示-
,但是-0
并没有什么实际意义,因此这个数字被用以表示十进制整数-128
(它也不存在原码和补码),这也可以解释为什么计算机中一个字节的取值范围是[-128,127]。
-
原码
1位符号位+数字绝对值转换的二进制;符号位0表示正,1表示负。
+1 原码: 0000 0001
-1 原码: 1000 0001
-
反码
表示负数的一种方法,本质是通过取反实现正负的区分。
- 正数的反码是其本身(即原码)
- 负数的反码:符号位不变,对其余各位取反
+1 原码: 0000 0001 反码: 0000 0001
-1 原码: 1000 0001 反码: 1111 1110
-
补码
补码是通过反码实现正负数区分的一种方式。
- 正数的补码就是其本身(即原码),因此直接转换位十进制数字输出;
- 负数的补码:第1位为1不变,其余为原码取反再加一
+1 原码:0000 0001 反码: 0000 0001 补码: 0000 0001
-1 原码:1000 0001 反码: 1111 1110 补码: 1111 1111
以下例子中除按位取非运算中均使用正数,略去了符号位以让描述更简约(并不影响计算结果的准确性)。
对两个操作数的每一位执行逻辑与操作,如果两个相应的位都为 1,则结果为 1,否则为 0。
1&2 == 0
# 01
# 11
# 01 --> 1
对两个操作数的每一位执行逻辑或操作,如果两个相应的位都为 0,则结果为 0,否则为 1。
1|2 == 3
# 01
# 11
# 11 --> 3
对两个操作数的每一位执行逻辑异或操作,如果两个相应的位值相同,则结果为 0,否则为 1。
1^2 == 3
# 01
# 10
# 11 -> 3
对操作数的每一位执行逻辑取反操作,即将每一位的 0 变为 1,1 变为 0。
~1 == -2
-
十进制2用八位二进制表示为:
0000 0001
(原码即补码——正数) -
对二进制的
0000 0001
取反得到:1111 1110
由于的符号位是1,计算机认为这是一个负数的补码,负数输出时先按规则处理:
-
符号位不变,符号为
-
-
补码减一取反得原码
-
二进制计算
1111 1110
减1
得到1111 1101
1111 1110 - 0000 0001 1111 1101 #10 - 01 -> 01
-
上一步结果取反
1111 1101
取反得到000 0010
即为二进制的2
-
-
-
原码输出使进制数字即为
-2
将操作数的所有位向左移动指定的位数。左移 n 位相当于乘以 2 的 n 次方。二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
5*2**3 == 5<<3 # 5x8 == 5*2**3 == 40
将操作数的所有位向右移动指定的位数。右移n位相当于除以 2 的 n 次方。二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补 0,负数左补 1,右边丢弃。
40/2**3 == 40>>3 # 40/8 == 40/2**3 == 5
丢弃各二进位全部右移若干位,高位补0,低位丢弃。某些语言不支持。
12>>>2 == 3
-12>>>2 == 1073741821
用整数和1进行与运算,结果值为0则为偶数,否则为基数:
n&1 == 0 #为真则n为偶数,否则n为奇数
(n - 1) & n==0 #为真则n是2的幂
如果两个变量的值都为整数,且希望交换两个变量的值,可使用按位异或:
a ^= b
b ^= a
a ^= b
#如果语言支持连续^=
#a ^= b ^= a ^= b
#---需要额外空间的方法
# 方法 1 使用临时变量
# 方法 2 使用加减法
a = a+b
b = a-b
a = a-b