python中的位运算符
本文由gpt生成,仅作为本人自用的参考资料使用,不保证完全正确!
Python 中的位运算是非常常用且高效的操作,尤其在算法题、图论、压缩状态、权限管理等场景中非常有用。
1️⃣ 位运算符总览
运算符 | 名称 | 作用 | 示例 ( a = 0b0110 ,b = 0b1011 ) | 结果(二进制) |
---|---|---|---|---|
& | 按位与(AND) | 两位都为 1 ⇒ 1,否则 0 | a & b | 0b0010 |
| | 按位或(OR) | 只要有一位为 1 ⇒ 1 | a | b | 0b1111 |
^ | 按位异或(XOR) | 不同为 1,相同为 0 | a ^ b | 0b1101 |
~ | 按位取反(NOT) | 0→1,1→0 (含符号位) | ~a | …1111 1001 † |
<< | 左移 | 高位丢弃,低位补 0 | a << 2 | 0b11000 |
>> | 右移 | 正数高位补 0;负数补 1 | b >> 1 | 0b0101 |
† 取反得到的是 无限长补码 表示;见下节说明。
另外:
0b
是 Python 中表示 “二进制” 的前缀,0b中的0并不是符号位
特性一览,摘自**力扣评论** :
-
与运算(AND):
-
任何数和0做与运算,结果是0,即 x & 0 = 0。
例如,5(101) & 0 = 0。
-
任何数和其自身做与运算,结果是自身,即 x & x = x。
例如,5(101) & 5(101) = 5(101)。
-
用途:清除某些位,比如清除最低位:
x & (x - 1) # 去掉最低位的 1
-
-
或运算(OR):
-
任何数和0做或运算,结果是自身,即 x | 0 = x。
例如,5(101) | 0 = 5(101)。
-
任何数和其自身做或运算,结果是自身,即 x | x = x。
例如,5(101) | 5(101) = 5(101)。
-
用途:设定某些位为 1
-
-
异或运算(XOR):
-
任何数和0做异或运算,结果是自身,即 x ^ 0 = x。
例如,5(101) ^ 0 = 5(101)。
-
**任何数和其自身做异或运算,结果是0,即 x ^ x = 0**。
例如,5(101) ^ 5(101) = 0。
-
异或运算满足交换律和结合律,即 a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c。
例如,5(101) ^ 3(011) ^ 4(100) = 5 ^ (3 ^ 4) = (5 ^ 3) ^ 4。
-
用途:
- **消除重复元素:**
a ^ a = 0
- **交换两个数:**
a ^= b; b ^= a; a ^= b
- **消除重复元素:**
-
-
非运算(NOT):
-
非运算会反转操作数的所有位,包括符号位。
-
对整数
x
,返回-(x+1)
~5 # = -6,因为:~x = -x - 1
-
-
左移运算(SHL):
-
左移n位等于乘以2的n次方,即 x << n 等价于 x * 2^n。
例如,5(101) << 2 = 20(10100)。
-
x << k
不会改变 x
本身 -
左移运算不改变操作数的符号位。
- Python 的整数是无限精度的;
- 所以左移只是不断乘以 2,不会自动溢出或改变符号;
-
-
右移运算(SHR):
-
每右移 1 位,相当于 //2(向下取整)
右移n位等于除以2的n次方,即 x >> n 等价于 x // 2^n。
例如,20(10100) >> 2 = 5(101)。
-
x >> k
不会改变 x
本身 -
右移运算正数高位补 0;负数补 1
-
右移并不会改变符号,Python 会保留负号
-
2️⃣ 负数与补码
Python 的整数没有位数上限,用二进制补码无限延伸。
- e.g.
x = -5
→ 内部补码为…11111011
- 所以
~x
相当于-(x+1)
x = -5
print(~x) # 4
print(~4) # -5
3️⃣ 进制转换与位处理辅助函数
函数 / 方法 | 作用 | 栗子 |
---|---|---|
bin(x) /oct(x) /hex(x) | 转 2/8/16 进制字符串 | bin(10) →'0b1010' |
int(s, base) | 任意进制转十进制 | int('FF',16) →255 |
int.bit_length() | 去掉符号位后所需位宽 | (1023).bit_length() →10 |
int.bit_count() (Py 3.8+) | Hamming weight,统计 1 的个数 | (0b1011).bit_count() →3 |
int.to_bytes() /int.from_bytes() | 整数↔字节序列 | (255).to_bytes(2,'big') |
4️⃣ 常见技巧 / 场景
✔️ 交换两数
a ^= b
b ^= a
a ^= b
✔️ 判断奇偶
x & 1 == 1 # 奇数
x & 1 == 0 # 偶数
✔ 检查第 k 位是否为 1(从右数起,0-based)
0-based 的意思是「从 0 开始编号」,位运算相关的最低位也就是第0位一般是最右边的那一位
(x >> k) & 1 == 1
# 或:
x & (1 << k) != 0
含义:
-
x >> k
把第k
位移到最右边 -
& 1
只保留最右边那一位(高位如果还有1,就被消去了) - 看它是不是等于
1
✔️ 清零 / 置位 / 翻转指定位
mask = 1 << k # 第 k 位掩码 (0‑based),例如1 << 3 就等价于0b1000,第0位的1左移了三位抵达第k位
x |= mask # 把第 k 位变成 1(无论原来是几)
x &= ~mask # 把第 k 位清 0
x ^= mask # 把第 k 位翻转(0→1, 1→0)
✔️ 提取低位 / 高位
low8 = x & 0xff # 仅保留最低 8 位
high4 = (x >> 4) & 0xf
背后的思路:用掩码(mask)保留需要的位
1️⃣ 0xff
是什么?
0xff = 0b11111111 = 255
8 个 1
,用于掩盖(保留)最低 8 位。
x = 0b1011011100101101
x & 0xff = 仅保留最后 8 位,其它全部变 0
👉 所以 x & 0xff
就是 保留最低 8 位,其余清零。
2️⃣ (x >> 4) & 0xf
是什么?
-
x >> 4
:将 x 向右移 4 位,相当于丢掉低 4 位; -
& 0xf = 0b1111
:只保留当前最低的 4 位; - 所以它实际上是提取了 原来第 4~7 位 的内容。
✅ 举个例子
x = 0b10110110_00111100
low8 = x & 0xff # 得到 0b00111100 = 0x3C = 60
high4 = (x >> 4) & 0xf # 移掉低 4 位再取低 4 位 = 0b0110 = 6
✔️ 子集枚举(按位集合的所有子集)
S = 0b10110 # 假设 S 表示一个集合
sub = S
while sub:
# 处理子集 sub
sub = (sub - 1) & S
🧠 这个技巧是干嘛的?
它是用来枚举一个集合(用位表示)的所有子集。
-
集合 S 用一个二进制数表示,比如
S = 0b10110
表示集合{1, 2, 4}
,也就是第
i
位若为1,代表i
在集合中; -
它的子集也用相同形式表示,比如
0b10010
表示{1, 4}
; -
每次减 1 再与原集合相与,就能跳转到下一个有效子集。
🔄 每一步发生了什么?
sub = S # 从全集 S 开始
while sub:
# 处理 sub,例如打印、统计等
sub = (sub - 1) & S
这个过程枚举了 S 的所有非空子集,不重复、也不多余。
✅ 举个例子
假设 S = 0b110
,对应集合 {1, 2}
:
枚举过程如下:
step | sub (二进制) | 子集(集合) |
---|---|---|
1 | 110 | {1, 2} |
2 | 101 | {0, 2} ❌不合法(超出 S)→ 被掩码去掉 →100 {2} |
3 | 011 | {0, 1} ❌也不合法 → 掩码 →010 {1} |
4 | 001 | {0} ❌ → 掩码 →000 |
5️⃣ 运算符优先级 & 结合律
- 位运算符的优先级低于算术运算
+ - *
,但高于比较运算< > ==
。 - 同一行里,
~
><< >>
>&
>^
>|
。 - 全都是 左结合(
~
是单目)。
x = 1 << 3 + 1 # 等价于 1 << (3+1) → 16
y = 1 | 2 & 3 # 等价于 1 | (2 & 3) → 1|2 → 3
6️⃣ 小结
- 与/或/异或/取反/移位 是位运算的核心。
- 补码导致
~x == -(x+1)
。 -
bit_length
&bit_count
、掩码技巧让位运算在性能与内存受限的场景(如算法竞赛、嵌入式、加密)非常有用。 - 牢记优先级或加括号,避免坑。