C++ 进阶(26):位操作——C++20 位库、bit_cast、bitmask 实战
更新时间:2026-09-01。本文是
languages/cpp/intermediate/进阶第 26 篇,接 span 与 string_view。C++20 标准化了位操作,<bit>头文件提供了countr_zero、bit_width、bit_cast等实用函数,不需要再用宏和 hack。
本文要回答的问题
std::bit_cast是什么?和 reinterpret_cast 有什么区别?- 怎么计算一个数的二进制中有多少个 1?怎么找第一个 1?
- 位掩码技巧:怎么设置位、清除位、翻转位、检查位?
- 位反转、字节序转换怎么做?
一、bit_cast:类型无损转换
cpp
#include <bit>
#include <cstdint>
// 把 float 无损转成 uint32_t,不改变比特
float f = 3.14f;
uint32_t bits = std::bit_cast<uint32_t>(f);
float f2 = std::bit_cast<float>(bits);
// C 风格是:
uint32_t bits;
memcpy(&bits, &f, sizeof f);
// bit_cast 比 memcpy 更干净和 reinterpret_cast 的区别:
cpp
// 安全:bit_cast 保证比特不变,类型大小必须相等
uint32_t bits = std::bit_cast<uint32_t>(f); // 大小都是 4 字节,ok
// reinterpret_cast:不一定安全,依赖于对齐和大小
uint32_t bits = reinterpret_cast<uint32_t&>(*reinterpret_cast<char*>(&f));
// UB 如果对齐不对二、std::bit 实用函数
cpp
#include <bit>
// 统计 1 的个数
uint32_t v = 0b101010;
int cnt = std::popcount(v); // 3 → 有三个 1
// 前导零个数:最高位 1 前面有几个零
int leading = std::countl_zero(0b00001010); // 28(对 32 位来说)
// 尾随零个数:最低位 1 后面有几个零
int trailing = std::countr_zero(0b101000); // 3
// 位宽:最高位 1 的位置 + 1
int width = std::bit_width(0b1010); // 4 → 位宽是 4
// 位反转
uint8_t rev = std::bit_reverse((uint8_t)0b10100000); // 0b00000101
// 字节序交换
uint16_t swap16 = std::byteswap((uint16_t)0x1234); // 0x3412三、经典位操作技巧
cpp
// 检查第 k 位是否为 1
bool has = (x & (1 << k)) != 0;
// 设置第 k 位为 1
x |= (1 << k);
// 清除第 k 位为 0
x &= ~(1 << k);
// 翻转第 k 位
x ^= (1 << k);
// 提取从 start 到 end 的位掩码(包含 start)
uint32_t mask = (~0U) << start & ~(~0U << (end + 1));
uint32_t bits = (x & mask) >> start;
// 找出最低位的 1
uint32_t lowest = x & -x; // 只有最低位 1 保留,其余是 0
// 清除最低位的 1
x &= x - 1;
// 是否只有一位是 1(2 的幂)
bool is_power = (x & (x - 1)) == 0;例子:提取 IP 地址的四个字节:
cpp
uint32_t ip = 0xc0a80101; // 192.168.1.1
uint8_t a = (ip >> 24) & 0xff;
uint8_t b = (ip >> 16) & 0xff;
uint8_t c = (ip >> 8) & 0xff;
uint8_t d = ip & 0xff;
// a=192, b=168, c=1, d=1四、位掩码枚举
cpp
enum Flags : uint32_t {
Read = 1 << 0,
Write = 1 << 1,
Exec = 1 << 2,
All = Read | Write | Exec,
};
Flags flags = Read | Write;
if (flags & Read) {
// 有 Read 权限
}
flags |= Exec; // 增加 Exec
flags &= ~Write; // 移除 Write五、大小端转换
cpp
// 主机字节序转网络字节序(大端)
uint16_t htons(uint16_t host) {
if constexpr (std::endian::native == std::endian::big) {
return host;
} else {
return std::byteswap(host);
}
}
uint32_t htonl(uint32_t host) {
// 类似,byteswap
}六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 移位超过位数 | UB | 对 32 位整数,移位不能 >= 32 |
| 符号位移位 | 算术移位,结果不对 | 用无符号类型移位 ~0U 而不是 ~0 |
| bit_cast 大小不匹配 | 编译错误 | 源类型和目标类型大小必须相等 |
| 掩码错误 | 提取错位 | 检查移位和掩码计算 |
相关与延伸
下一篇:协程实战——C++20 协程基础用法;C 位操作,见 C 位操作。
一句话总结
C++ 位操作:std::bit_cast 安全地重新解释比特,比 memcpy 和 reinterpret_cast 干净;<bit> 提供 popcount(统计 1)、countl_zero(前导零)、bit_width(位宽)、bit_reverse(位反转)、byteswap(字节交换);经典技巧:检查位 x & (1<<k),设置 x |= (1<<k),清除 x &= ~(1<<k),最低位 1 是 x & -x;枚举位掩码 Read=1<<0,组合用 |,检查用 &;std::endian 判断大小端。