位运算的 C 程序实现
看完你能读懂并亲手写出操作二进制位的 C 代码
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
位运算的 C 程序实现
看完你能读懂并亲手写出操作二进制位的 C 代码
数据在内存里是二进制
上一页我们写了 b & 1 判断奇偶,C 编译器为什么能这么算?因为变量在内存里本来就是一串 0 和 1。位运算不把这些位当成数字,而是当成一排可以逐个操作的开关。
每个开关只有开/关两种状态,对应一位的 1 或 0
什么是位运算
上一页我们确认了:数据在内存里就是一串 0 和 1。既然每个位都看得见,那能不能不把整数当整体,而是按位逐个操作?位运算就是干这件事的工具。
每位像一个独立开关,位运算就是对整排灯逐个拨动
按位与 &
上一页说过,数据在内存里就是一串 0 和 1。那两个数做运算时,C 语言怎么操作这些二进制位?最基础的一种就是按位与 &:把两个数的二进制上下对齐,一位一位地比。
每道锁对应一个二进制位,两把钥匙都插得进(都是 1)门才开
按位或 |
上一页的按位与要求两人都点头才算数。按位或换个脾气:只要有一方说是,结果就是是。电路里的并联开关正是这样——任意一条路通,灯就亮。
任一支路接通灯就亮,对应任一位为 1 结果为 1
按位异或 ^
按位与挑「都为1」,按位或挑「有1就收」。异或换个口味:它专盯「不一样」——两位不同才给1,一样就给0。先记住这句口诀,再看细节。
两人答案不同算「有分歧」(1),完全一致算无分歧(0)
按位取反 ~
前面几个运算符都是两个数在"对暗号",而这页只有一个操作数:按位取反 ~。它把每个二进制位掀过来看——0 变 1、1 变 0,像把一串灯开关全部拨到反方向。
每个开关拨到相反状态,对应每个二进制位 0、1 互换
左移 <<
前面按位运算都是两数对齐逐位比。左移不一样:一个数自己整体向左挪,腾出来的低位补 0。就像排队向左走一步,队尾空位站新人。
每人左移一步对应各二进制位左移一位,队尾空位补 0
右移 >>
左移是整体往左挪、低位补 0。那反过来挪呢?比如一个数除以 2,二进制下其实就是把所有位往右挪一格——这就是右移。
座位号大的先出门(低位丢弃),前排空位补 0
用 & 清零指定位
看一个把指定二进制位清 0 的真实 C 程序:与 0 相与即清零,与 1 相与保持原值。
掩码中 0 的位置(第 4 位)被强制清 0,其余位与 1 相与保持不变——这就是清零指定位的通用写法。
用 | 置位指定位
用 | 配上恰当的掩码,把指定位置设为 1,其余位原样保留。
逐位看:掩码为 1 的位,或运算强制变 1;掩码为 0 的位结果等于 x 原值。这就是「只置指定位置」的原理。
用 ^ 翻转指定位
演示用一个掩码配合 ^,只翻转指定的二进制位,其余位保持不变。
x 与 mask 逐位异或:mask 为 1 的位,结果 = x 该位取反;mask 为 0 的位,结果 = x 原值。这正是 ^ 翻转指定位的机制。
用 & 判断某位是否为 1
看到:一个数与掩码相与,结果非零即说明目标位是 1。
掩码只有目标位是 1,相与后其他位全变 0;结果非 0 说明目标位是 1,为 0 则是 0。
左移实现乘 2 的幂
这段代码演示左移几位,数值就乘以 2 的几次方——连溢出行为也看得见。
x<<1 得 6、x<<4 得 48:每移一位,二进制整体左移一格,低位补 0,数值翻倍,所以左移 n 位就是乘 2ⁿ。
右移实现除 2 的幂
用右移代替除法:x>>n 的结果正是 x/2ⁿ 向下取整,正负数表现不同。
右移 n 位丢弃低 n 位,剩下的就是除以 2ⁿ 的商;负数因高位补符号位,结果相当于向下取整而非向零取整。
位运算符与逻辑运算符
写 if(x & 1) 还是 x && 1?& 与 &&、| 与 || 长得像但行为完全不同,混淆会埋下隐蔽 bug。
- 按位操作,操作数的每个二进制位独立运算
- 结果是数值,如 6 & 3 得 2
- 不短路,两边表达式都会求值
- 作用于整个值,只判真或假
- 结果是 0 或 1,如 6 && 3 得 1
- 短路求值,左边能定结果则跳过右边
移位位数的边界
上一页我们用左移实现了乘 2 的幂,那移动 100 位会怎样?就像钟表拨针,拨过头只会绕回原点——移位也有自己的「表盘刻度」,超过了就出问题。
拨针超过一圈刻度就绕回原点,移位超过类型位数结果不可信
自测:位运算综合
已知 int x = 12(二进制 1100),表达式 (x >> 2) & 1 的值是多少?
位运算要点收束
- ✓六种运算符都逐位作用,与逻辑运算符 && || 是两回事
- ✓清零用 &、置位用 |、翻转用 ^,判断某位也靠 &
- ✓移位即乘除 2 的幂,有符号数右移结果依赖实现
- ✓移位位数必须小于类型位宽,越界是未定义行为
课后思考
先自己想一会儿,再看参考答案——答案只是思考的起点。
参考答案x-1 会把最低位的 1 变 0、其右边的位全变 1,与 x 按位与后正好抹掉这一段。循环执行 x &= x-1 直到 x 为 0,循环次数就是 1 的个数。
参考答案正确三步是 a^=b; b^=a; a^=b。失效场景:当两个变量指向同一块内存(如 swap(&a,&a))时,第一步自异或直接清零,交换失败;另外可读性差,现代编译器下未必更快。
参考答案C 标准对负数的右移结果未做硬性规定(通常实现为算术右移、补符号位),属于实现定义行为。可移植做法:避免对负数移位,先转成无符号类型,或用除法/掩码显式表达意图。