std::vector 是位压缩特化容器,不支持标准算法和取地址操作;代理对象赋值可能失效;越界访问未定义;大数据范围应选布隆过滤器;手动实现推荐 uint64_t 存储单元。

std::vector 不能直接当去重容器用
它底层是位压缩特化实现,迭代器解引用返回的是 proxy 对象,不是 bool&,所以没法传给 std::find、std::count 这类算法,也不能取地址做指针操作。常见错误是写 auto& b = bitmap[i]; b = true; —— 看似生效,但 b 是临时代理,后续读可能失效,尤其开 -O2 后行为更不可靠。
- 别把它当普通容器塞进
std::set或泛型算法 - 映射前必须确保所有输入都在
[0, N)范围内,负数或越界访问是未定义行为 - 若数据范围不确定(比如含负数、跨度大),优先考虑布隆过滤器或分段哈希,而不是硬套
std::vector<bool></bool>
手动实现 Bitmap:用 std::vector<uint64_t></uint64_t> 替代 char 或 int
用 uint64_t 当存储单元,单次操作可覆盖 64 个 bit,比 char(8 bit)快 8 倍,比 int(32 bit)快 2 倍;现代 CPU 对 64 位寄存器的位操作(如 bts、bt)原生支持,编译器也更容易向量化。
- 计算字索引:
word_idx = index / 64,位偏移:bit_offset = index % 64 - 置位:
data[word_idx] |= (1ULL (注意 <code>ULL防止左移溢出) - 查位:
(data[word_idx] & (1ULL - 分配时建议用
aligned_alloc(64, size)或posix_memalign,对齐到 64 字节可避免某些 SIMD 指令异常
如何安全映射任意整数到位图索引
位图只认非负整数索引,但现实数据常含负数或稀疏大值。不能简单用 abs(x) % size,那会引发冲突和漏判。
- 正负分离:为正数和负数各建一个
Bitmap,负数统一转为unsigned long long(-x)再映射 - 偏移归一化:若已知全集在
[min_val, max_val],则用index = x - min_val,要求max_val - min_val可接受 - 超大值兜底:若
x >= 1ULL ,且位图仅分配几 GB,应提前丢弃或走 fallback 路径(如哈希表) - 永远检查边界:
if (index >= num_bits) return false;,不依赖调用方保证
遍历“已出现”的值比你想的慢得多
位图不存值,只存状态。想拿到所有去重后的数,本质是扫描整个位空间并解码——哪怕只有 100 个数被设为 1,也要扫完全部 N 位。
- 避免写
for (size_t i = 0; i ,这是纯 O(N),不是 O(活跃数) - 真要高频遍历,得额外维护一个
std::vector<size_t></size_t>记录插入顺序,或改用roaring bitmap这类带索引结构 - 若只需统计数量,用内置 popcnt 指令(
__builtin_popcountll)批量算每 word 的 1 的个数,比逐 bit 查快两个数量级
位图的威力在于单次 test 是真 O(1),但“拉出全部结果”这一步,从来就不是它的设计目标。别把它当集合容器用,它只是存在性开关。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











