在 C++ 中,位集(bitset)通常用于存储一系列布尔值。标准库提供了 std::bitset,但它的大小必须在编译时确定。对于需要动态大小的场景(如图形学中的像素掩码、数据库中的标记位、或网络协议的位域),我们需要一个在运行时可调整大小的位集。下面我们用 std::vector<uint64_t> 实现一个可变长度位集,并讨论其实现细节、性能以及典型用例。
1. 设计目标
| 特性 | 说明 |
|---|---|
| 动态大小 | 位数可以随时增长或缩小 |
| 高效访问 | 获取、设置、清除单个位操作均为 O(1) |
| 内存紧凑 | 只占用必要的 64 位块 |
| 易用接口 | 类似 std::bitset 的 API(set, reset, test 等) |
| 可扩展 | 支持按位或(OR)、与(AND)、异或(XOR)等批量运算 |
2. 基本实现
#include <vector>
#include <cstdint>
#include <stdexcept>
#include <iostream>
#include <iomanip>
class DynamicBitset {
std::vector <uint64_t> data_;
size_t bit_count_; // 实际位数
static constexpr size_t BITS_PER_BLOCK = 64;
static constexpr uint64_t BLOCK_MASK = 0xFFFFFFFFFFFFFFFFULL;
// 确保内部 vector 至少能存放 n 位
void ensure_size(size_t n) {
size_t needed_blocks = (n + BITS_PER_BLOCK - 1) / BITS_PER_BLOCK;
if (data_.size() < needed_blocks)
data_.resize(needed_blocks, 0);
}
// 对给定索引进行边界检查
void check_index(size_t idx) const {
if (idx >= bit_count_)
throw std::out_of_range("DynamicBitset: index out of range");
}
public:
DynamicBitset() : bit_count_(0) {}
explicit DynamicBitset(size_t n, bool init = false) : bit_count_(n) {
ensure_size(n);
if (!init) std::fill(data_.begin(), data_.end(), 0);
}
size_t size() const noexcept { return bit_count_; }
void resize(size_t n, bool init = false) {
if (n < bit_count_) {
// 需要清除超出的位
for (size_t i = n; i < bit_count_; ++i)
reset(i);
}
bit_count_ = n;
ensure_size(n);
if (init) std::fill(data_.begin(), data_.end(), 0);
}
// 单个位操作
bool test(size_t idx) const {
check_index(idx);
size_t block = idx / BITS_PER_BLOCK;
size_t offset = idx % BITS_PER_BLOCK;
return (data_[block] >> offset) & 1ULL;
}
void set(size_t idx, bool value = true) {
check_index(idx);
size_t block = idx / BITS_PER_BLOCK;
size_t offset = idx % BITS_PER_BLOCK;
if (value)
data_[block] |= (1ULL << offset);
else
data_[block] &= ~(1ULL << offset);
}
void reset(size_t idx) { set(idx, false); }
void flip(size_t idx) {
check_index(idx);
size_t block = idx / BITS_PER_BLOCK;
size_t offset = idx % BITS_PER_BLOCK;
data_[block] ^= (1ULL << offset);
}
// 批量操作
void setAll(bool value = true) {
std::fill(data_.begin(), data_.end(), value ? BLOCK_MASK : 0);
}
void resetAll() { setAll(false); }
void flipAll() {
for (auto &block : data_) block ^= BLOCK_MASK;
}
// 位运算
DynamicBitset operator|(const DynamicBitset& rhs) const {
DynamicBitset result(std::max(bit_count_, rhs.bit_count_), false);
for (size_t i = 0; i < result.data_.size(); ++i) {
uint64_t a = (i < data_.size()) ? data_[i] : 0;
uint64_t b = (i < rhs.data_.size()) ? rhs.data_[i] : 0;
result.data_[i] = a | b;
}
return result;
}
DynamicBitset operator&(const DynamicBitset& rhs) const {
DynamicBitset result(std::max(bit_count_, rhs.bit_count_), false);
for (size_t i = 0; i < result.data_.size(); ++i) {
uint64_t a = (i < data_.size()) ? data_[i] : 0;
uint64_t b = (i < rhs.data_.size()) ? rhs.data_[i] : 0;
result.data_[i] = a & b;
}
return result;
}
DynamicBitset operator^(const DynamicBitset& rhs) const {
DynamicBitset result(std::max(bit_count_, rhs.bit_count_), false);
for (size_t i = 0; i < result.data_.size(); ++i) {
uint64_t a = (i < data_.size()) ? data_[i] : 0;
uint64_t b = (i < rhs.data_.size()) ? rhs.data_[i] : 0;
result.data_[i] = a ^ b;
}
return result;
}
// 迭代器支持(仅遍历 1 位)
class iterator {
const DynamicBitset &bs_;
size_t pos_;
public:
using iterator_category = std::forward_iterator_tag;
using value_type = bool;
using difference_type = std::ptrdiff_t;
using pointer = bool*;
using reference = bool;
iterator(const DynamicBitset &bs, size_t pos) : bs_(bs), pos_(pos) {}
bool operator*() const { return bs_.test(pos_); }
iterator &operator++() { ++pos_; return *this; }
bool operator==(const iterator &other) const { return pos_ == other.pos_; }
bool operator!=(const iterator &other) const { return !(*this == other); }
};
iterator begin() const { return iterator(*this, 0); }
iterator end() const { return iterator(*this, bit_count_); }
// 打印为二进制字符串(最高位在左侧)
std::string to_string() const {
std::string s;
s.reserve(bit_count_);
for (size_t i = bit_count_; i > 0; --i)
s += test(i - 1) ? '1' : '0';
return s;
}
};
3. 性能考量
-
内存布局
` 的连续内存提供了缓存友好性。对 64 位块进行掩码运算是 SIMD 友好的,现代 CPU 对此非常优化。
`std::vector -
单个位操作
通过位运算(>>,&,|,^)实现 O(1) 复杂度。与std::bitset的实现相同,唯一差别是我们在运行时需要除以 64 来定位块。 -
扩容
ensure_size会在需要时重新分配向量。若频繁增大位数,最好预估最大大小并一次性分配,以减少重分配成本。 -
批量运算
对整个vector进行按位操作(OR/AND/XOR)在块级别完成,时间复杂度为 O(n_blocks)。如果需要大量此类运算,建议使用 SIMD 指令集(AVX/AVX2/AVX512)进一步优化。
4. 示例用法
int main() {
DynamicBitset bs1(130); // 130 位,全部 0
bs1.set(0); // 位置 0 置 1
bs1.set(65); // 位置 65 置 1
bs1.set(129); // 位置 129 置 1
std::cout << "bs1: " << bs1.to_string() << '\n';
DynamicBitset bs2(130, true); // 全部 1
bs2.reset(65); // 位置 65 清 0
auto bs_or = bs1 | bs2;
std::cout << "bs1 | bs2: " << bs_or.to_string() << '\n';
auto bs_and = bs1 & bs2;
std::cout << "bs1 & bs2: " << bs_and.to_string() << '\n';
// 逐位打印
std::cout << "Bits set in bs1:\n";
for (size_t i = 0; i < bs1.size(); ++i)
if (bs1.test(i)) std::cout << i << ' ';
std::cout << '\n';
}
运行结果示例(仅演示部分):
bs1: 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000