理解 C++ 编程中的“位与“运算符

我们以位掩码 & 0xFF 为例说明计算机中位与运算的完整原理。

1. 语法解析

& 运算符

  • 名称:按位与(bitwise AND)运算符
  • 作用:对两个操作数的每个对应位执行逻辑与操作
  • 语法操作数1 & 操作数2

0xFF 的含义

  • **0x**:十六进制前缀
  • **FF**:十六进制的 255
  • 二进制11111111(8个1)

2. 按位与的真值表

位A 位B A & B
0 0 0
0 1 0
1 0 0
1 1 1

关键规律:只有当两个对应位都为1时,结果位才为1

3. 计算过程详解

示例:保留 16位数的低8位

假设我们有一个16位的和:sum = 0x0123(十进制291)

1
2
uint16_t sum = 0x0123;  // 二进制: 00000001 00100011
uint8_t low_byte = sum & 0xFF;

逐步计算

1
2
3
4
sum      = 00000001 00100011  (0x0123 = 291)
0xFF = 00000000 11111111 (只保留低8位为1)
-------------------------------- & (按位与)
结果 = 00000000 00100011 (0x0023 = 35)

转换为 uint8_t 后:0x23(丢弃高8位)

更复杂的例子

1
2
uint16_t large_sum = 0xABCD;  // 43981
uint8_t result = large_sum & 0xFF;

计算过程

1
2
3
4
5
large_sum = 10101011 11001101  (0xABCD)
0xFF = 00000000 11111111
-------------------------------- &
result = 00000000 11001101 (0x00CD)
转换为uint8_t: 0xCD

4. 代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <iomanip>
#include <bitset>
#include <cstdint>

void demonstrate_bitmask() {
// 示例1:16位数保留低8位
uint16_t number = 0xABCD;
uint8_t masked = number & 0xFF;

std::cout << "原始数: 0x" << std::hex << std::uppercase << number << std::endl;
std::cout << "二进制: " << std::bitset<16>(number) << std::endl;
std::cout << "0xFF: " << std::bitset<16>(0x00FF) << std::endl;
std::cout << "结果: " << std::bitset<16>(number & 0x00FF) << std::endl;
std::cout << "提取的低字节: 0x" << std::hex << std::uppercase << static_cast<int>(masked) << std::endl;
std::cout << std::endl;

// 示例2:三字节求和场景
uint8_t bytes[] = {200, 100, 50}; // 总和 = 350 = 0x015E
uint16_t sum = bytes[0] + bytes[1] + bytes[2];
uint8_t checksum = sum & 0xFF;

std::cout << "三个字节: " << static_cast<int>(bytes[0]) << ", "
<< static_cast<int>(bytes[1]) << ", " << static_cast<int>(bytes[2]) << std::endl;
std::cout << "求和: " << std::dec << sum << " = 0x" << std::hex << sum << std::endl;
std::cout << "二进制: " << std::bitset<16>(sum) << std::endl;
std::cout << "保留低字节: 0x" << std::hex << static_cast<int>(checksum) << std::endl;
}

// 通用位掩码函数
template<typename T>
T get_low_bytes(T value, int byte_count) {
T mask = (1 << (byte_count * 8)) - 1;
return value & mask;
}

int main() {
demonstrate_bitmask();

// 演示通用掩码
uint32_t value = 0x12345678;
std::cout << "\n通用掩码示例:" << std::endl;
std::cout << "原始: 0x" << std::hex << value << std::endl;
std::cout << "低1字节: 0x" << get_low_bytes(value, 1) << std::endl;
std::cout << "低2字节: 0x" << get_low_bytes(value, 2) << std::endl;
std::cout << "低3字节: 0x" << get_low_bytes(value, 3) << std::endl;

return 0;
}

5. 为什么 & 0xFF 能保留低8位?

数学原理

  • 0xFF 的二进制是 11111111
  • 任何数与 11111111 进行按位与,结果等于该数本身(只限于低8位)
  • 高位的0与任何数相与都是0,所以高位被清零

可视化理解

1
2
3
任意16位数: ABCDEFGH IJKLMNOP  (A-P代表8个位)
掩码 0xFF: 00000000 11111111
结果: 00000000 IJKLMNOP ← 只保留低8

6. 其他常用的位掩码

1
2
3
4
5
// 常用掩码模式
uint8_t keep_low_4_bits = value & 0x0F; // 00001111
uint8_t keep_high_4_bits = value & 0xF0; // 11110000
uint16_t keep_low_12_bits = value & 0x0FFF; // 00001111 11111111
uint32_t keep_low_24_bits = value & 0xFFFFFF; // 低24位全1

7. 性能优势和小结

位掩码 & 0xFF 比模运算 % 256 更高效:

  • 位运算:CPU直接支持,通常1个时钟周期
  • 模运算:需要除法器,多个时钟周期

小结

& 0xFF 是一个经典的位操作技巧:

  • 语法:按位与运算符 + 十六进制常量
  • 原理:利用 11111111 掩码清零高位移位,保留低位
  • 应用:截取数据的特定字节、哈希函数、校验和计算等
  • 优势:高效、简洁、明确表达意图

8. 应用:保留低字节位

假设我们将三个字节的值做求和运算,得到的结果大于255,需要占用两个字节,我们如何只保留低位的那一个字节?

你可以使用方法3来运算,这个在数学上很容易理解,但是如果你理解了上面的位与运算,那么通过位掩码 & 0xFF 来实现,即方法1,计算的执行效率最高。

方法1:位掩码(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <cstdint>
#include <iostream>
#include <iomanip>

int main() {
uint8_t byte1 = 200;
uint8_t byte2 = 100;
uint8_t byte3 = 50;

// 方法1:位掩码 (最常用)
uint16_t sum = byte1 + byte2 + byte3; // 可能超过255
uint8_t low_byte = sum & 0xFF; // 保留低8位

std::cout << "三个字节: " << static_cast<int>(byte1) << ", "
<< static_cast<int>(byte2) << ", " << static_cast<int>(byte3) << std::endl;
std::cout << "求和结果: " << sum << std::endl;
std::cout << "低字节: 0x" << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
<< static_cast<int>(low_byte) << " (" << std::dec << static_cast<int>(low_byte) << ")" << std::endl;

return 0;
}

方法2:直接使用 uint8_t 溢出

1
2
// 方法2:利用 uint8_t 的自然溢出(简洁但可读性差)
uint8_t result = byte1 + byte2 + byte3; // 自动截断到8位

方法3:模运算

1
2
// 方法3:模256运算(数学上等价于位掩码)
uint8_t result = (byte1 + byte2 + byte3) % 256;

完整示例和验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <cstdint>
#include <iostream>
#include <iomanip>

// 三种方法的比较
void demonstrate_methods() {
uint8_t bytes[] = {200, 100, 50}; // 总和 = 350 > 255

// 方法1:位掩码
uint16_t sum1 = bytes[0] + bytes[1] + bytes[2];
uint8_t result1 = sum1 & 0xFF;

// 方法2:自然溢出
uint8_t result2 = bytes[0] + bytes[1] + bytes[2];

// 方法3:模运算
uint8_t result3 = (bytes[0] + bytes[1] + bytes[2]) % 256;

std::cout << "三个字节: ";
for (int i = 0; i < 3; ++i) {
std::cout << "0x" << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
<< static_cast<int>(bytes[i]) << " ";
}
std::cout << std::endl;

std::cout << "总和: " << std::dec << (bytes[0] + bytes[1] + bytes[2]) << std::endl;
std::cout << "方法1 (位掩码): 0x" << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
<< static_cast<int>(result1) << std::endl;
std::cout << "方法2 (自然溢出): 0x" << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
<< static_cast<int>(result2) << std::endl;
std::cout << "方法3 (模运算): 0x" << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
<< static_cast<int>(result3) << std::endl;
}

int main() {
demonstrate_methods();
return 0;
}

输出结果

1
2
3
4
5
三个字节: 0xC8 0x64 0x32 
总和: 350
方法1 (位掩码): 0x5E
方法2 (自然溢出): 0x5E
方法3 (模运算): 0x5E

关键点总结

  1. **std::**:访问标准库组件的命名空间限定符
  2. **static_cast<int>()**:安全的显式类型转换,避免意外的类型解释
  3. 保留低字节:推荐使用 & 0xFF 位掩码,清晰且高效
  4. 溢出处理:C++ 中无符号整数的溢出是明确定义的(模运算)

理解 C++ 编程中的“位与“运算符
https://jycpp.github.io/26-01-03-理解CPP编程中的位与运算符.html
作者
Jet Yan
发布于
2026年1月3日
许可协议