C 位运算符入门
更新时间:2026-08-26。本文是
languages/c/主题入门层第 10 篇。上一课的关系/逻辑运算符操作的是"真/假",这一课的位运算符直接操作二进制位。位运算是 C 的独门绝技——权限、状态标志、颜色、网络协议、嵌入式寄存器,全是位运算的天下。
本文要回答的问题
&、|、^、~分别对二进制位做什么?- 怎么用位运算做"置位 / 清除 / 翻转"?
- 左移右移时,无符号和有符号差在哪?
一、五个位运算符
| 运算符 | 名称 | 作用 | 示例(8 位) |
|---|---|---|---|
& | 按位与 | 两个位都是 1 才为 1 | 1100 & 1010 = 1000 |
| ` | ` | 按位或 | 任一位为 1 即为 1 |
^ | 按位异或 | 相同为 0,不同为 1 | 1100 ^ 1010 = 0110 |
~ | 按位取反 | 0 变 1,1 变 0 | ~1100 = 0011(补足位数) |
<< >> | 左移 / 右移 | 位向左 / 右移动 | 1100 << 1 = 11000 |
别把 &、| 和逻辑的 &&、|| 搞混:&& 是真值运算(结果只有 0/1),& 是逐位运算。3 && 4 是 1,3 & 4 是 0——完全不同的东西。
验证一段(用 8 位看):
c
unsigned char a = 0b1100; // 12
unsigned char b = 0b1010; // 10
printf("%d\n", a & b); // 8(0b1000)
printf("%d\n", a | b); // 14(0b1110)
printf("%d\n", a ^ b); // 6(0b0110)
printf("%d\n", a << 1); // 24(左移一位 = 乘 2)
printf("%d\n", a >> 1); // 6(右移一位 = 除 2)二、经典用法:位掩码
位运算最常见的场景是把"多个开关状态"塞进一个整数。假设一个字节 8 位代表 8 个开关:
c
#define FLAG_READ 0x01 // 00000001
#define FLAG_WRITE 0x02 // 00000010
#define FLAG_EXEC 0x04 // 00000100
unsigned char perm = 0;
perm |= FLAG_READ; // 置位:打开 READ
perm |= FLAG_WRITE; // 打开 WRITE,perm = 00000011
perm &= ~FLAG_WRITE; // 清除:关掉 WRITE,perm = 00000001
perm ^= FLAG_EXEC; // 翻转:EXEC 原来是 0 变 1
if (perm & FLAG_READ) { // 检测:READ 开着吗?
printf("可读\n");
}四个基本操作的模式,背下来:
| 操作 | 写法 |
|---|---|
| 置位(开) | `x |
| 清除(关) | x &= ~掩码; |
| 翻转 | x ^= 掩码; |
| 检测 | if (x & 掩码) |
三、移位:乘 2 和除 2 的快速通道
左移一位相当于乘 2,右移一位相当于除 2(正数):
c
int x = 3;
printf("%d\n", x << 2); // 12:左移 2 位 = 乘 4
printf("%d\n", x >> 1); // 1:右移 1 位 = 除 2 取整关键区别:无符号右移是逻辑移位(补 0),有符号右移是算术移位(补符号位):
c
unsigned int u = 0x80000000; // 最高位是 1
printf("%u\n", u >> 1); // 0x40000000:左边补 0(逻辑移位)
int i = -8; // 二进制最高位是 1(负数)
printf("%d\n", i >> 1); // -4:左边补 1(算术移位,保持负数)这解释了有符号与无符号里的建议:做位运算用无符号,行为可预测。有符号右移的结果在标准里甚至一度是实现定义(C99 后规定算术移位),没必要赌。
四、实战小技巧
| 技巧 | 写法 | 说明 |
|---|---|---|
| 判断奇偶 | if (n & 1) | 比 n % 2 快(编译器也会这么优化) |
| 取第 k 位 | (x >> k) & 1 | 右移 k 位再与 1 |
| 把第 k 位置 1 | `x | = (1 << k)` |
| 交换两个数(炫技,不建议) | a ^= b; b ^= a; a ^= b; | 实际工程用临时变量更清晰 |
| 2 的幂判断 | (n > 0) && ((n & (n - 1)) == 0) | n 是 2 的幂则成立 |
位运算还有个学习红利:它是理解内存布局、寄存器、以及 perf 火焰图里位操作指令的基础。
五、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
& 写成 && | 结果变成 0/1,逻辑错 | 看清单双符号 |
| 有符号右移 | 负数补 1,结果意外 | 用 unsigned |
| 移位超过类型位数 | 未定义行为 | 移位量小于类型位数 |
| 掩码类型不匹配 | 高位被截断 | 掩码和变量同类型 |
| 拿位运算结果当逻辑真值 | 非 0 值判断错 | 显式 != 0 |
六、与本站主线衔接
- 位运算是结构体内存对齐和位域(高手层)的基础;
- 编译器怎么把
n & 1优化成指令,见与汇编、性能剖析衔接; - 位运算在嵌入式 / 驱动(寄存器操作)里的实战,见位运算进阶与位域。
一句话总结
位运算是"逐位"运算:& 置位检测、| 打开、^ 翻转、~ 取反、移位乘除 2;做位运算用无符号,掩码和变量同类型,n & 1 判断奇偶——这五招学会,就能读懂大量系统级代码。