
C++标准库中的bitset是一个功能强大且高效的位操作工具,它提供了一种轻量级的方式,用来管理和操作二进制位(bit)的集合。与传统的位操作(如位掩码和移位操作)相比,std::bitset更加直观、易用,且提供了丰富的接口用于高效处理位数据。这种工具在需要优化内存占用和加速位级别操作的场景中非常有用,例如图算法、布隆过滤器、集合运算、网络路由表维护等。
bitset 基础概念bitsetbitset是一个C++标准库中的位集合容器,它提供了一种方便操作和存储位级数据的机制。bitset在C++标准库头文件<bitset>中声明,可以创建固定大小的位集合,并对其进行位级操作和访问。
bitset是一个固定大小的位集合容器,它的大小在编译时确定,不能改变。bitset的大小可以是任意的,甚至可以是零。每个bitset对象都存储一个n位的二进制位序列,其中n是bitset的大小。bitset中的位可以使用整数索引进行访问,从0开始,直到n-1。
①bitset的模板定义如下:
template<std::size_t N> class bitset;其中,N表示bitset的大小(即位的数量)。N是一个编译时常量,所以bitset的大小在运行时不可更改。例如,std::bitset<8>表示一个包含8位的位集合,适合表示字节级数据;std::bitset<32>表示一个包含32位的位集合,适合表示标准的32位整数;std::bitset<1024>可以用来处理更大范围的位数据。
②核心价值:
③典型应用场景:
④核心成员函数:
方法 | 作用 | 时间复杂度 |
|---|---|---|
set(pos) | 置位指定位置 | O(1) |
reset(pos) | 复位指定位置 | O(1) |
flip(pos) | 翻转指定位 | O(1) |
test(pos) | 安全访问(带边界检查) | O(1) |
count() | 统计置位数量 | O(N/bits) |
any() | 判断是否有置位位 | O(N/bits) |
none() | 判断是否全零 | O(N/bits) |
all() | 判断是否全一(C++11) | O(N/bits) |
①默认初始化
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> bits1; // 定义一个包含 8 位的 bitset,所有位初始化为 0
std::cout << "bits1: " << bits1 << std::endl;
return 0;
}
定义了一个包含 8 位的 bitset 对象 bits1。由于没有提供显式的初始化值,所有位都被初始化为 0。
②用整数初始化
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> bits2(10); // 用整数 10 的二进制表示初始化,即 00001010
std::cout << "bits2: " << bits2 << std::endl;
return 0;
}
用整数 10 来初始化 bitset 对象 bits2。整数 10 的二进制表示是 00001010,因此 bits2 的值就是这个二进制序列。
③ 用二进制字符串初始化
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> bits3("10101010"); // 用二进制字符串初始化
std::cout << "bits3: " << bits3 << std::endl;
return 0;
}
使用二进制字符串 "10101010" 来初始化 bitset 对象 bits3。字符串中的每个字符对应 bitset 中的一个位,'1' 表示该位为 1,'0' 表示该位为 0。
①访问单个位:可以使用下标运算符 [] 来访问 bitset 中的单个位。需要注意的是,下标从 0 开始,最右边的位是第 0 位。
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> bits("10101010");
std::cout << "第 3 位的值: " << bits[2] << std::endl; // 注意索引从 0 开始
return 0;
}访问了 bits 对象的第 3 位(索引为 2),并将其值输出。

②置位操作:set 函数用于将指定位置的位设置为 1。如果不指定位置,则将所有位都设置为 1。
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> bits("10101010");
bits.set(3); // 将第 3 位置为 1
std::cout << "置位后: " << bits << std::endl;
return 0;
}
将 bits 对象的第 3 位置为 1,并输出置位后的 bitset 值。
③取反操作:flip 函数用于将指定位置的位取反。如果不指定位置,则将所有位都取反。
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> bits("10101010");
bits.flip(); // 所有位取反
std::cout << "取反后: " << bits << std::endl;
return 0;
}
将 bits 对象的所有位取反,并输出取反后的 bitset 值。
④复位操作:reset 函数用于将指定位置的位设置为 0。如果不指定位置,则将所有位都设置为 0。
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> bits("10101010");
bits.reset(5); // 将第 5 位置为 0
std::cout << "复位后: " << bits << std::endl;
return 0;
}
将 bits 对象的第 5 位置为 0,并输出复位后的 bitset 值。
⑤测试位:test 函数用于检查指定位置的位是否为 1。如果是,则返回 true;否则返回 false。
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> bits("10101010");
if (bits.test(2)) {
std::cout << "第 3 位是 1" << std::endl;
} else {
std::cout << "第 3 位是 0" << std::endl;
}
return 0;
}
使用 test 函数检查 bits 对象的第 3 位是否为 1,并根据结果输出相应的信息。
⑥统计置位的位数:count 函数用于统计 bitset 中值为 1 的位的数量。
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> bits("10101010");
std::cout << "置位的位数: " << bits.count() << std::endl;
return 0;
}
输出 bits 对象中值为 1 的位的数量。
⑦判断是否所有位都为 0:none 函数用于判断 bitset 中是否所有位都为 0。如果是,则返回 true;否则返回 false。
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> bits("00000000");
if (bits.none()) {
std::cout << "所有位都为 0" << std::endl;
} else {
std::cout << "存在值为 1 的位" << std::endl;
}
return 0;
}
使用 none 函数判断 bits 对象是否所有位都为 0,并输出相应的信息。
⑨判断是否所有位都为 1:all 函数用于判断 bitset 中是否所有位都为 1。如果是,则返回 true;否则返回 false。
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> bits("11111111");
if (bits.all()) {
std::cout << "所有位都为 1" << std::endl;
} else {
std::cout << "存在值为 0 的位" << std::endl;
}
return 0;
}
使用 all 函数判断 bits 对象是否所有位都为 1,并输出相应的信息。
template<size_t N>
class bitset {
private:
typedef unsigned long base_type;
static const size_t bits_per_block = sizeof(base_type) * CHAR_BIT;
static const size_t block_count = (N + bits_per_block - 1) / bits_per_block;
base_type data[block_count]; // 紧凑存储
};constexpr bitset<8> mask = 0b11100000;
static_assert(mask.count() == 3, "Mask error");unsigned long保证可移植性
bitset 的应用场景在很多系统中,需要管理一组标志位来表示不同的状态或选项。bitset 可以很好地满足这个需求,因为它可以高效地存储和操作多个标志位。
#include <iostream>
#include <bitset>
enum Feature {
FEATURE_1 = 0,
FEATURE_2 = 1,
FEATURE_3 = 2,
FEATURE_4 = 3,
FEATURE_5 = 4,
FEATURE_6 = 5,
FEATURE_7 = 6,
FEATURE_8 = 7
};
int main() {
std::bitset<8> featureFlags;
// 启用 FEATURE_3
featureFlags.set(FEATURE_3);
// 检查 FEATURE_3 是否启用
if (featureFlags.test(FEATURE_3)) {
std::cout << "FEATURE_3 已启用" << std::endl;
}
return 0;
}
定义了一个包含 8 个功能标志的 bitset 对象 featureFlags。通过 set 函数启用了 FEATURE_3,并使用 test 函数检查该功能是否已启用。
在某些算法中,需要存储大量的状态信息。使用 bitset 可以将这些状态信息进行压缩,减少内存开销。
#include <iostream>
#include <bitset>
#include <vector>
const int MAZE_SIZE = 10;
int main() {
std::bitset<MAZE_SIZE * MAZE_SIZE> visited;
// 标记 (2, 3) 位置已访问
int index = 2 * MAZE_SIZE + 3;
visited.set(index);
// 检查 (2, 3) 位置是否已访问
if (visited.test(index)) {
std::cout << "(2, 3) 位置已访问" << std::endl;
}
return 0;
}
在这个迷宫问题的示例中,使用一个 bitset 来表示迷宫中每个格子的访问状态。通过将二维坐标转换为一维索引,可以方便地访问和修改 bitset 中的位。
位掩码是一种常见的二进制操作技术,用于提取或设置特定的位。bitset 可以很方便地进行位掩码操作。
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> data("10101010");
std::bitset<8> mask("00001111");
// 提取低 4 位
std::bitset<8> result = data & mask;
std::cout << "低 4 位的值: " << result << std::endl;
return 0;
}
使用一个位掩码 mask 来提取 data 的低 4 位。通过按位与操作 &,可以得到只包含低 4 位的结果。
bitset 可以用于表示集合,并且支持集合的基本运算,如并集、交集和差集。
#include <iostream>
#include <bitset>
int main() {
std::bitset<8> set1("10101010");
std::bitset<8> set2("01010101");
// 并集
std::bitset<8> unionSet = set1 | set2;
std::cout << "并集: " << unionSet << std::endl;
// 交集
std::bitset<8> intersectionSet = set1 & set2;
std::cout << "交集: " << intersectionSet << std::endl;
// 差集
std::bitset<8> differenceSet = set1 & (~set2);
std::cout << "差集: " << differenceSet << std::endl;
return 0;
}
使用 bitset 表示两个集合 set1 和 set2,并进行了并集、交集和差集的运算。通过按位或操作 | 实现并集,按位与操作 & 实现交集,按位取反操作 ~ 和按位与操作实现差集。
bitset<32> bs(0xDEADBEEF);
// 转换为数值类型
unsigned long val1 = bs.to_ulong();
unsigned long long val2 = bs.to_ullong(); // C++11
// 字符串表示
string s1 = bs.to_string(); // "11011110..."
string s2 = bs.to_string('*', '-'); // 替换0/1为指定字符
// 流操作
cout << hex << bs << endl; // 输出十六进制形式template<size_t N>
void print_bitset(const bitset<N>& bs) {
for(int i=bs.size()-1; i>=0; --i) { // MSB优先
cout << bs.test(i);
if(i%8 == 0) cout << ' ';
}
}template<size_t N>
constexpr auto generate_check_mask() {
bitset<N> mask;
for(size_t i=0; i<N; i+=2) {
mask.set(i);
}
return mask;
}
static_assert(generate_check_mask<8>().to_ulong() == 0xAA, "");struct EthernetFrame {
uint8_t dest[6];
uint8_t src[6];
uint16_t type;
// ... 其他字段
};
void process_frame(const EthernetFrame& frame) {
bitset<16> type_field(ntohs(frame.type));
if(type_field[15] == 1) { // 最高位表示QTag
// 处理VLAN标签
}
}struct RGBA {
uint8_t r, g, b, a;
};
bitset<32> encode_pixel(const RGBA& p) {
return bitset<32>(p.r) << 24 |
bitset<32>(p.g) << 16 |
bitset<32>(p.b) << 8 |
bitset<32>(p.a);
}class DeviceController {
bitset<8> status;
enum Flags {
POWER_ON = 0,
OVERHEAT = 1,
FAULT = 2,
// ... 其他标志位
};
public:
bool is_safe() const {
return !status.test(OVERHEAT) &&
!status.test(FAULT);
}
};①constexpr应用
constexpr bitset<64> create_mask(size_t start, size_t len) {
bitset<64> mask;
for(size_t i=start; i<start+len; ++i) {
mask.set(i);
}
return mask;
}
static_assert(create_mask(4,8).to_ullong() == 0xFF0, "");② 范围视图适配
bitset<256> data;
auto first_quad = data.to_string().substr(0,64);
auto reversed = views::reverse(data.to_string());③SIMD加速
#if defined(__AVX2__)
void fast_xor(bitset<256>& a, const bitset<256>& b) {
auto* pa = reinterpret_cast<__m256i*>(&a);
const auto* pb = reinterpret_cast<const __m256i*>(&b);
_mm256_store_si256(pa, _mm256_xor_si256(*pa, *pb));
}
#endif问题1:动态位操作需求
template<size_t N>
class BitWindow {
bitset<N>& ref;
size_t start;
size_t length;
public:
class Proxy {
BitWindow& window;
size_t pos;
public:
Proxy& operator=(bool val) {
window.ref.set(window.start + pos, val);
return *this;
}
operator bool() const { /* ... */ }
};
Proxy operator[](size_t pos) {
return {*this, pos};
}
};问题2:大端序处理
bitset<32> read_big_endian(istream& is) {
uint32_t temp;
is.read(reinterpret_cast<char*>(&temp), sizeof(temp));
temp = ntohl(temp); // 网络序转主机序
return bitset<32>(temp);
}问题3:安全类型转换
template<size_t N>
optional<unsigned long> safe_convert(const bitset<N>& bs) {
if(bs.size() > numeric_limits<unsigned long>::digits ||
bs.to_ulong() > numeric_limits<unsigned long>::max()) {
return nullopt;
}
return bs.to_ulong();
}由于 bitset 的大小在编译时就已经确定,因此在使用 bitset 时需要确保已经明确知道所需的位序列长度。如果需要在运行时动态调整大小,可以考虑使用 std::vector<bool>。
在访问 bitset 中的位时,要确保索引在合法范围内,否则会导致未定义行为。例如,对于一个包含 8 位的 bitset,索引的合法范围是 0 到 7。
在使用二进制字符串初始化 bitset 时,要确保字符串只包含 '0' 和 '1' 字符,否则会抛出 std::invalid_argument 异常。
#include <iostream>
#include <bitset>
int main() {
try {
std::bitset<8> bits("10201010"); // 会抛出异常
} catch (const std::invalid_argument& e) {
std::cout << "输入的字符串包含非法字符: " << e.what() << std::endl;
}
return 0;
}由于输入的字符串包含非法字符 '2',bitset 的构造函数会抛出 std::invalid_argument 异常。使用 try-catch 块来捕获并处理这个异常。
bitset 是 C++ 标准库中一个非常实用的类模板,它为我们处理二进制位序列提供了高效、便捷的方式。在实际编程中,当需要处理固定长度的二进制数据时,bitset 是一个不错的选择。但同时也要注意其大小固定性、越界访问和输入验证等问题。
希望本文能够帮助你更好地理解和使用 bitset 类型,提升你的 C++ 编程技能。
using声明在模板编程中有着重要应用,如定义模板类型别名等。