Python位运算的应用
较难3位运算的魔法:让代码又快又酷
位运算就是直接对二进制位(0和1)进行操作。计算机底层所有数据都是二进制,所以位运算非常快,常用于底层编程、游戏开发、硬件控制等。学会它,你会发现很多巧妙的小技巧,能让代码既高效又有趣!
1. 用按位与判断奇偶:比取模更快
任何整数在二进制中,最低位(最右边)决定奇偶:0表示偶数,1表示奇数。用 x & 1 就可以取出最低位,结果0是偶数,1是奇数。
num = 7 # 要判断的数字
if num & 1: # 按位与1,检查最低位
print(num, "是奇数")
else:
print(num, "是偶数")
num = 10 # 另一个数字
if num & 1:
print(num, "是奇数")
else:
print(num, "是偶数")
生活中的例子:
老师让学号是奇数的同学坐左边,偶数的坐右边。如果学号是 37,用 37 & 1 得到1,就是奇数,去左边。用位运算比 num % 2 快很多,尤其在处理大量数据时(比如学校几千个学生的座位分配)。
常见错误:
不要写成 num & 1 == 1 来判断奇偶,因为 & 优先级比 == 低,会先比较再按位与。正确写法是 (num & 1) == 1,或者直接 if num & 1: 更简洁。
2. 用异或交换两个数:不用第三杯水
平时交换两个变量需要第三个临时变量,像用第三杯水来倒换。但位运算可以用三次异或 ^ 完成交换,原理是 x ^ y ^ y = x。
a = 5 # 第一个数字
b = 3 # 第二个数字
a = a ^ b # a = 5 ^ 3 = 6
b = a ^ b # b = 6 ^ 3 = 5 (此时b得到原来的a)
a = a ^ b # a = 6 ^ 5 = 3 (a得到原来的b)
print("交换后:a =", a, "b =", b) # 输出 a = 3, b = 5
生活中的例子:
小明和小红各有一杯水,小明的是可乐,小红的是雪碧。不用第三个杯子,怎么交换?可以先把两杯水倒在一起(异或),再分别倒出。虽然现实中不能这样,但计算机里位运算就像魔法!
常见错误:
如果 a 和 b 是同一个变量(比如 a = a ^ a),会把自己变成0,然后永远变不回来。所以交换的两个数必须是不同的内存地址(比如不同变量名)。另外,这种方法虽然省内存,但可读性不如临时变量,日常编程建议用 a, b = b, a。
3. 把多个开关状态打包进一个整数
游戏里有四个技能:火球、冰盾、雷击、治疗。每个技能要么学会(1)要么没学会(0)。用四个变量记录太浪费,可以用一个整数的四个二进制位来存储。位运算可以轻松修改或读取某一位。
# 假设用最低4位表示四个技能:从右往左第1位火球,第2位冰盾,第3位雷击,第4位治疗
skills = 0b1010 # 二进制1010,表示冰盾和治疗已学(第2、4位为1),其他未学
# 打开火球技能(第1位)
skills = skills | (1 << 0) # 1<<0 = 1,或运算将第1位置1
print(bin(skills)) # 输出 0b1011 (火球也学会了)
# 关闭治疗技能(第4位)
skills = skills & ~(1 << 3) # 1<<3 = 8 (二进制1000),取反后...0111,与运算清零第4位
print(bin(skills)) # 输出 0b11 (第4位变0,只剩火球和冰盾)
生活中的例子:
你可以用一个整数记录班级里8位同学的请假状态:位0表示小明,位1表示小红……用 status | (1 << 3) 表示小红请假(第3位)。读取时用 status & (1 << 3) 判断小红是否请假。这样比用列表或字典快得多,尤其适合硬件设备的配置。
常见错误:
- 搞错位数:
1 << n表示把1左移n位,最低位是第0位。要操作第1位用1 << 0,第2位用1 << 1,以此类推。 - 关闭某位时用了
& ~(1 << n),但注意~的优先级高,要加括号。写成skills = skills & ~(1 << 3)没问题,因为~优先级高于&。但为了安全,可以写成skills = skills & (~(1 << 3))。
4. 用左移快速计算乘以2的n次方
左移 << 相当于乘以2的n次方,右移 >> 相当于除以2的n次方(整数除法)。这在处理颜色值、坐标缩放等场景非常高效。
# 快速计算 32 * 8 = 256
result = 32 << 3 # 32乘以2的3次方,即32 * 8
print(result) # 输出 256
# 也可以用于除法:64 // 4 = 16
result2 = 64 >> 2 # 64除以2的2次方,即64 / 4
print(result2) # 输出 16
生活中的例子:
你每周零花钱32元,妈妈说要连续给8周(2的3次方),你总共能拿到多少?用 32 << 3 一秒算出256元。如果是256元分给4位同学(2的2次方),每人 256 >> 2 = 64元。
常见错误:
- 左移不能越界:如果整数位数太大(比如32位整数左移33位),会溢出或结果不正确。Python整数无限大,但实际硬件有限。
- 右移对于负数有舍入问题:负数的右移在不同语言中可能补1(算术右移)或补0(逻辑右移)。Python中负数右移是算术右移(补1),例如
-8 >> 2得到 -2(向下取整),而不是2。这点要小心。
常见错误小总结
- 优先级混淆:位运算符优先级比比较运算符低,记得加括号。例如
if (x & 1) == 0而不是if x & 1 == 0。 - 异或交换同变量:不要对同一个变量做
a = a ^ a,会得到0。 - 左移右移方向记反:左移
<<是乘,右移>>是除,数值变大用左移。 - 位掩码计算错误:要操作第n位(从0开始),用
1 << n,不要想成2**n没问题但左移更快。 - 忘记清除某位时取反:关闭某位需要
& ~(1 << n),不能只用& (1 << n)(那会保留其他位为0)。
完整示例:游戏角色状态管理器
下面用位运算管理一个游戏角色的四个属性:火(1)、冰(2)、雷(4)、光(8),每个属性可以用一个位表示。程序演示了激活、关闭、判断和列出所有激活属性。
# 定义四位标志的位号
FIRE = 1 << 0 # 火属性,二进制0001
ICE = 1 << 1 # 冰属性,二进制0010
THUND = 1 << 2 # 雷属性,二进制0100
LIGHT = 1 << 3 # 光属性,二进制1000
# 初始状态:只有冰和雷激活
state = ICE | THUND # 二进制 0110,十进制6
print("初始状态", bin(state)) # 0b110
# 激活火属性
state |= FIRE # 或运算,等价于 state = state | FIRE
print("激活火后", bin(state)) # 0b111
# 关闭冰属性
state &= ~ICE # 与取反,关闭第1位
print("关闭冰后", bin(state)) # 0b101
# 判断光是否激活
if state & LIGHT:
print("光已激活")
else:
print("光未激活") # 输出这行,因为光未激活
# 列出所有激活属性
print("当前激活属性:", end="")
if state & FIRE: print("火", end=" ")
if state & ICE: print("冰", end=" ")
if state & THUND: print("雷", end=" ")
if state & LIGHT: print("光", end=" ")
print() # 输出:火 雷
运行结果:
初始状态 0b110
激活火后 0b111
关闭冰后 0b101
光未激活
当前激活属性:火 雷
相关指引
如果你想更深入学习位运算,可以研究:
- 位运算与集合:可以用整数位表示集合,并集、交集、补集对应
|、&、~。 - 颜色值处理:RGB颜色常用24位表示(红8位、绿8位、蓝8位),通过移位和掩码提取分量。
- 补码与负数:了解负数的二进制表示(补码),以及位运算对负数的影响。
- 高效算法:比如用位运算统计二进制中1的个数(
x & (x-1)技巧),或判断2的幂(x & (x-1) == 0)。
位运算就像编程世界里的“魔法”,虽然一开始有点绕,但用多了你就会爱上它的简洁和速度!
例题精讲
下列哪个表达式可以正确判断整数x是奇数?
使用位运算交换两个整数a和b,以下代码是正确的:a ^= b; b ^= a; a ^= b。
以下函数用位运算判断正整数n是否为2的幂,请填空。
def is_power_of_two(n):
return n > 0 and (n & ___) == 0已知整数 x = 0b1101(二进制),执行 x >> 2 的结果是多少?
以下代码使用位运算交换两个变量a和b的值(不使用临时变量),请填空。
a = a ^ b
b = a ^ b
a = ___