跳转至

位寻址与掩码置位:i/8、i%8 与 1<<(7-i%8)

i/8 定位字节、7-i%8 定位 bit、1<< 造单点位掩码、|= 只置位不破坏其他位。

背景

位图编码的核心一行:

out[i/8] |= 1 << (7 - uint(i%8))

它把第 i 个 bool 值"装进"字节的对应 bit 位,一行包含四个惯用法:除法定位字节、取模定位 bit、移位造掩码、按位或赋值置位。拆开看:

out[i/8] |= 1 << (7 - uint(i%8))
└───┬───┘     └──────┬──────┘
  哪个字节         哪个 bit

核心内容

三个子表达式

表达式 含义 例子(i=13)
i / 8 第 i 位属于第几个字节(每字节装 8 位) 13/8 = 1 → 第 1 个字节
i % 8 在该字节内是第几个位置 13%8 = 5 → 字节内第 5 格
1 << (7 - i%8) 生成一个只有那一位是 1 的掩码 1<<2 = 0b00000100

除法与取模在这里是配对技巧:i/8i%8 分别回答"第几组"和"组内第几个"。

字节的 8 格模型

一个字节就是 8 个格子,bit 编号即 2 的幂次:

        out[1] 这个字节的 8 个 bit 格子:
        ┌───┬───┬───┬───┬───┬───┬───┬───┐
        │ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │   ← bit 编号(2 的幂次)
        └───┴───┴───┴───┴───┴───┴───┴───┘
          ↑                                   ← bit 7 是最高位(最左)

1 << k 的意思是"把 1 移动到编号为 k 的格子":

1 << 0 = 0b00000001   ← 编号 0 的格子
1 << 2 = 0b00000100   ← 编号 2 的格子
1 << 7 = 0b10000000   ← 编号 7 的格子(最高位)

|= 只置位、不破坏

|=(按位或赋值)的作用是只置位、不破坏其他位——这是设置标志位的标准手法:

假设 out[1] 原来是 0b01000000,要置编号 2 的格子:
   01000000
 | 00000100
 ──────────
   01000100        ← 只把编号 2 的格子点亮,其他位原样保留

位序约定:为什么是 7 - i%8

两种都能工作(只要编码解码对称),区别是字节内的排列方向:

高位在前(7 - i%8)          低位在前(i%8)
bit0 占最高位                  bit0 占最低位
┌───────────────┐            ┌───────────────┐
│0 1 2 3 4 5 6 7 │            │7 6 5 4 3 2 1 0 │
└───────────────┘            └───────────────┘

out[0] = 0b10100000          out[0] = 0b00000101
       = 0xA0                      = 0x05

选高位在前的原因是可读性:打印十六进制/二进制时,字面上的左→右顺序与位图下标顺序一致,排查问题时"第 0 位在哪儿"一眼可见。这是纯粹的工程约定,不是协议要求。

示例

单点置位:i = 13

i = 13, bits[13] = true

i/8  = 1         目标是 out[1]
i%8  = 5
7-5  = 2         目标格子是编号 2
1<<2 = 0b00000100

out[1] |= 0b00000100

完整位图编码(12 位 → 2 字节)

bits = [T, F, T, F, F, F, F, F, F, T, F, F]
        0  1  2  3  4  5  6  7  8  9  10 11   ← 下标 i

i=0:  out[0] |= 1<<7  → 0b10000000
i=2:  out[0] |= 1<<5  → 0b10100000
i=9:  out[1] |= 1<<6  → 0b01000000

最终:
out[0] = 0b10100000   ← 装了下标 0~7
out[1] = 0b01000000   ← 装了下标 8~11(末 4 格闲置补 0)

一句话总结:i/8 找到字节,7-i%8 找到格子,1<< 造出只点亮那个格子的掩码,|= 点亮它而不碰别的格子。

注意事项

  • ⚠️ 编码解码必须对称:编码用 7 - i%8,解码就必须用同一公式读,混用两种位序会得到错乱的数据
  • ⚠️ uint(i%8) 的显式转换在 Go 1.13+ 已非必需(有符号移位数已合法),保留多是兼容旧代码的习惯
  • ✅ 掩码操作三件套:置位用 x |= mask、清位用 x &^= mask、测试位用 x & mask != 0
  • ✅ 配套的字节容量计算 (len(bits)+7)/8 见同子域的 整数向上取整惯用法

延伸阅读


维护人:yiiewang · 最后更新:2026-09-04

评论