位掩码(bit masking)就是把数据真正按位来存,不用 int、char 各自占一大块,而是把一个整数的各个二进制位当成一个个开关(flag)。省内存又高效,适合"开/关""动/静"这种只有两种状态的信息。
基础:布尔逻辑与位
位掩码的思想建立在布尔逻辑上,核心四件事:
| 运算 | 规则 |
|---|---|
| NOT(非) | 1→0,0→1 |
| AND(与) | 两个都是 1 结果才为 1 |
| OR(或) | 任一个是 1 结果就为 1 |
| XOR(异或) | 一个 1 一个 0 结果才为 1 |
一个布尔值就是一个位(bit)。C 语言的 int、char 由若干位组成,8 位是一字节。标准保证 char 至少 1 字节、int 至少 16 位。C11 的 stdint.h 还允许指定精确字节数的类型,做掩码特别方便。
一个 int 的各二进制位当开关用:置位/清零/翻转只动某一位,比每个状态各存一个 int 省内存。
四个基本操作
假设要操作第 n 位,掩码就是 1 << n(把 1 推到第 n 位)。四个招式:
/* 1. 置位:把第 n 位设成 1 */
storage |= (1 << n);
/* 2. 清零:把第 n 位设成 0 */
storage &= ~(1 << n);
/* 3. 翻转:第 n 位 1↔0 */
storage ^= (1 << n);
/* 4. 检查:第 n 位是 1 吗 */
bit = storage & (1 << n); /* 非 0 即该位为 1 */置位(Set bit n)
用 OR 把目标位或成 1。例:给 storage 置第 3 位:
01000010 (当前 storage)
OR
00001000 (1 << 3,即 2^3)
==
010010101 << n 这步把"1"放到掩码的正确位置,OR 之后只碰那一位,别的位原封不动。
清零(Clearing bit n)
用 AND 上掩码的取反:目标位被清零,其余位保持。接着上面的结果清第 3 位:
01001010
AND
11110111 (~(1 << 3))
==
01000010翻转(Flipping bit n)
用 XOR:该位原来是 0 变 1、是 1 变 0,其余位不变:
01000010 01001010
XOR XOR
00001000 00001000
== ==
01001010 01000010检查(Checking bit n)
用 AND 上掩码,结果非 0 就是"该位为 1":
01000010 01001010
AND AND
00001000 00001000
== ==
00000000 00001000 ← 后者说明第 3 位是 1实战:一组游戏状态标记
把一堆开关装进一个 short。先定义各个位的常量:
const short FLAG_ON = 1 << 0; // 1 (0x01)
const short FLAG_MOVEMENT = 1 << 1; // 2 (0x02)
const short FLAG_TRANSPARENT = 1 << 2; // 4 (0x04)
const short FLAG_ALIVE = 1 << 3; // 8 (0x08)
const short FLAG_BROKEN = 1 << 4; // 16 (0x10)
const short FLAG_EDIBLE = 1 << 5; // 32 (0x20)
int main() {
short attributes = 0;
/* 一次打开 ON、TRANSPARENT、BROKEN */
attributes |= FLAG_ON;
attributes |= FLAG_TRANSPARENT;
attributes |= FLAG_BROKEN;
/* 保留 ON 和 ALIVE,其余全清 */
attributes &= FLAG_ON | FLAG_ALIVE;
/* 检查 ALIVE 是不是开着 */
if (attributes & FLAG_ALIVE) {
/* ... */
}
/* 检查 BROKEN 是不是关着 */
if (!(attributes & FLAG_BROKEN)) {
/* ... */
}
return 0;
}一个 short 就能同时记 16 个开关,而 16 个 bool 要占 16 字节,位掩码的省法就是这么来的。
什么时候用位掩码
- 权限系统:读、写、执行三个位的组合。
- 硬件寄存器:状态/命令按位解读。
- 大量布尔状态要省内存。
练手题:给上面那组 flag 定义好 FLAG_ALIVE、FLAG_BROKEN,然后用"置位/清零/翻转"把状态改成恰好是 ON + ALIVE,再用位运算断言检查 ALIVE 开着、BROKEN 关着。