核心概念:
- 位运算直接操作的是变量在内存中的二进制位 (0 或 1)。
- 通常用于
unsigned
类型的整数,避免有符号数可能带来的复杂性 (尤其是右移)。 - 常与十六进制 (
0x...
) 或二进制 (0b...
,部分编译器支持) 表示法结合使用,更直观。
1. 按位非 (NOT) ~嵌入式
- 作用: 对操作数的每一个二进制位进行取反操作 (0 变成 1,1 变成 0)。
- 例子: C
unsigned char a = 0b01010101; // 十进制是 85 unsigned char result = ~a; // result 会变成 0b10101010 (十进制是 170)
- 注意:
~
是一元运算符,只需要一个操作数。
2. 按位与 (AND) &
- 作用: 对两个操作数的对应二进制位进行运算。只有当两个位都为 1 时,结果位才为 1,否则为 0。
- 规则:
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
- 例子: C
unsigned char a = 0b11001100; // 0xCC unsigned char b = 0b10101010; // 0xAA unsigned char result = a & b; // result 会变成 0b10001000 (0x88) // 11001100 // & 10101010 // ----------- // 10001000
- 常见用途 (非常重要!):
- 清零特定位 (Masking): 将某位或某几位强制变为 0,其他位保持不变。方法是与一个特定位为 0,其余位为 1 的数 (称为“掩码” mask) 进行 AND 运算。 C
unsigned char flags = 0b01101101; // 想要清除第 0 位和第 3 位 (从右往左数,从 0 开始) unsigned char mask = ~(0b00001001); // mask = 0b11110110 flags = flags & mask; // flags 变成 0b01100100
- 检查特定位是否为 1: 判断某位是否被设置。方法是与一个只有该位为 1,其余位为 0 的数进行 AND 运算,如果结果不为 0,则该位为 1。 C
unsigned char flags = 0b01101101; // 检查第 2 位是否为 1 unsigned char mask = 0b00000100; // 只有第 2 位是 1 if ((flags & mask) != 0) { // 第 2 位是 1 } else { // 第 2 位是 0 }
- 清零特定位 (Masking): 将某位或某几位强制变为 0,其他位保持不变。方法是与一个特定位为 0,其余位为 1 的数 (称为“掩码” mask) 进行 AND 运算。 C
3. 按位或 (OR) |
- 作用: 对两个操作数的对应二进制位进行运算。只要两个位中至少有一个为 1,结果位就为 1,否则为 0。
- 规则:
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1
- 例子: C
unsigned char a = 0b11001100; // 0xCC unsigned char b = 0b10101010; // 0xAA unsigned char result = a | b; // result 会变成 0b11101110 (0xEE) // 11001100 // | 10101010 // ----------- // 11101110
- 常见用途 (非常重要!):
- 设置特定位 (Setting): 将某位或某几位强制变为 1,其他位保持不变。方法是与一个特定位为 1,其余位为 0 的数进行 OR 运算。 C
unsigned char flags = 0b01100100; // 想要设置第 1 位和第 7 位 unsigned char mask = 0b10000010; flags = flags | mask; // flags 变成 0b11100110
- 设置特定位 (Setting): 将某位或某几位强制变为 1,其他位保持不变。方法是与一个特定位为 1,其余位为 0 的数进行 OR 运算。 C
4. 按位异或 (XOR) ^
- 作用: 对两个操作数的对应二进制位进行运算。当两个位不同时,结果位为 1,相同时为 0。
- 规则:
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
- 例子: C
unsigned char a = 0b11001100; // 0xCC unsigned char b = 0b10101010; // 0xAA unsigned char result = a ^ b; // result 会变成 0b01100110 (0x66) // 11001100 // ^ 10101010 // ----------- // 01100110
- 常见用途:
- 翻转特定位 (Toggling): 将某位或某几位的值翻转 (0 变 1,1 变 0),其他位保持不变。方法是与一个特定位为 1,其余位为 0 的数进行 XOR 运算。 C
unsigned char led_state = 0b00000001; // 假设表示 LED 亮 // 想要翻转第 0 位 (开关 LED) unsigned char mask = 0b00000001; led_state = led_state ^ mask; // 第一次变成 0b00000000 (灭) led_state = led_state ^ mask; // 第二次变回 0b00000001 (亮)
- 比较两个数是否相等:
a ^ b == 0
当且仅当a == b
。 - 不使用临时变量交换两个数:
a = a ^ b; b = a ^ b; a = a ^ b;
(了解即可,现代编译器优化可能使其不必要)。 - 简单的校验和或加密算法。
- 翻转特定位 (Toggling): 将某位或某几位的值翻转 (0 变 1,1 变 0),其他位保持不变。方法是与一个特定位为 1,其余位为 0 的数进行 XOR 运算。 C
5. 左移 (Left Shift) <<
- 作用: 将操作数的所有二进制位向左移动指定的位数。右边空出的位用 0 填充,左边移出的位被丢弃。
- 例子: C
unsigned char a = 0b00001101; // 十进制 13 unsigned char result = a << 2; // 左移 2 位, result 变成 0b00110100 (十进制 52) // 00001101 << 2 => 00110100
- 效果: 左移 N 位相当于乘以 2N (只要结果不溢出)。例如
x << 1
相当于x * 2
,x << 3
相当于x * 8
。这在嵌入式中通常比乘法指令更快。 - 常用于: 快速乘以 2 的幂,以及构造掩码 (例如
1 << n
可以生成一个只有第 n 位是 1 的数)。
6. 右移 (Right Shift) >>
- 作用: 将操作数的所有二进制位向右移动指定的位数。左边空出的位如何填充取决于操作数的类型:
- 逻辑右移 (Logical Shift): 对于
unsigned
无符号类型,左边空出的位总是用 0 填充。 - 算术右移 (Arithmetic Shift): 对于
signed
有符号类型,左边空出的位用符号位填充 (即,如果原数是负数,通常用 1 填充;如果是正数,用 0 填充)。这是为了保持数的正负性。
- 逻辑右移 (Logical Shift): 对于
- 例子 (Unsigned): C
unsigned char a = 0b11100100; // 十进制 228 unsigned char result = a >> 2; // 右移 2 位, result 变成 0b00111001 (十进制 57) // 11100100 >> 2 => 00111001 (左边补 0)
- 例子 (Signed - 依赖编译器实现,但通常如下): C
signed char b = -28; // 二进制补码表示通常为 0b11100100 (假设 8 位) signed char result_signed = b >> 2; // 右移 2 位, result 可能是 0b11111001 (十进制 -7) // 11100100 >> 2 => 11111001 (左边补符号位 1)
- 效果: 右移 N 位相当于整数除以 2N (对于正数和无符号数)。例如
x >> 1
相当于x / 2
,x >> 2
相当于x / 4
。比除法指令更快。 - 常用于: 快速除以 2 的幂,获取某个位的值 (配合
&
)。
总结与实践
掌握位运算的关键在于理解每个运算符的规则,并记住它们的常用场景:
&
用于 清零 或 检查 位。|
用于 设置 位。^
用于 翻转 位。<<
用于快速 乘 2 的幂,构造掩码。>>
用于快速 除 2 的幂,提取位。~
用于 取反 所有位。
小练习:
假设我们有一个 8 位的寄存器 CONTROL_REG
,它的定义如下:
- Bit 7: Enable (1 = Enabled, 0 = Disabled)
- Bit 6: Interrupt Mask (1 = Masked, 0 = Unmasked)
- Bit 5: Reserved
- Bit 4: Mode Select (1 = Mode A, 0 = Mode B)
- Bit 3-0: Clock Divider (0-15)
现在 CONTROL_REG
的值是 0b01011011
。请思考如何用位运算实现以下操作?
- Enable the device (设置 Bit 7 为 1)。
- Disable the device (清除 Bit 7 为 0)。
- Check if the device is currently enabled (检查 Bit 7 的值)。
- Set the mode to Mode A (设置 Bit 4 为 1)。
- Toggle the Interrupt Mask (翻转 Bit 6)。
- Get the current Clock Divider value (提取 Bit 3-0 的值)。
- Set the Clock Divider value to 5 (
0b0101
) (需要先清零再设置)。