基于C++ bitset常用函数及运算符(详解)

发布时间:2026/10/26 19:44:24
基于C++ bitset常用函数及运算符(详解) 前言std::bitset是标准库里的定长位集合长度在编译期由模板参数确定每个元素只占一位支持按位运算、单比特读写、与字符串和整数互转。它适合表示「一组开关」「权限位」「素数筛」这类长度固定、只需要 0/1 两种状态的集合。相比std::vectorbool它的长度是类型的一部分编译器可以做更强的优化相比手写std::uint64_t位运算它的接口更完整越界检查也更明确。常见误解有三个以为bitset的长度可以运行时指定不能模板参数必须是常量表达式以为operator[]会做边界检查不会越界是未定义行为以为bitset能像容器一样用迭代器和算法它没有begin()/end()。这些差异直接影响代码能否编译、出错时是抛异常还是静默越界。本文按构造、成员函数、运算符、互转四部分梳理std::bitset并给出一个用位图做素数筛的完整示例。内容以 C17 为准to_ullong、all()等成员是 C11 加入的二进制字面量是 C14 加入的文中会分别标注。一、长度与构造std::bitsetN的N必须是编译期常量通常写成字面量或constexpr变量。默认构造把所有位清零也可以用unsigned long long初始化超出N的高位被截断// 适用于 C14使用了二进制字面量#include bitset#include stringstd::bitset8 zero; // 00000000std::bitset8 a(0b10101010); // 10101010std::bitset8 b(0xFF); // 11111111std::bitset8 c(0x1FF); // 11111111高位被截断std::bitset8 d(std::string(1100)); // 00001100用字符串构造时最右边的字符对应最低位。也就是说1100得到的是 12而不是把1放在最高位后剩下的位全零之外的什么特殊布局——字符串的最后一个字符写入 bit 0依次向左。这个方向如果记反解析二进制串时会得到完全不同的值。字符串构造函数还有三个可选参数起始位置pos、取用长度n、代表 0 和 1 的字符。默认从位置 0 开始取到末尾0表示 0、1表示 1。如果pos超过字符串长度抛std::out_of_range如果取到的字符既不是zero也不是one抛std::invalid_argument。注意构造时只取n个字符且从str[pos]开始向右取右侧仍然是低位。还有一个接受const char*的构造函数C11 起语义与字符串版本类似但传空指针是未定义行为undefined behavior, UB。二、常用成员函数成员作用越界/异常set()全部置 1无set(pos)第 pos 位置 1pos 越界抛out_of_rangereset()全部置 0无reset(pos)第 pos 位置 0越界抛out_of_rangeflip()全部取反无flip(pos)第 pos 位取反越界抛out_of_rangetest(pos)读取第 pos 位越界抛out_of_rangeoperator[](pos)读写第 pos 位不检查越界是 UBcount()1 的个数无size()位数 N无all()是否全为 1C11无any()是否有 1无none()是否全为 0无to_string()转成std::string无to_ulong()转unsigned long放不下抛overflow_errorto_ullong()转unsigned long longC11放不下抛overflow_errorset(pos)和operator[]的差别是初学最容易踩的前者带检查、会抛异常后者不检查、越界直接 UB。只读场景建议用test(pos)需要读写且能保证下标合法时才用operator[]。非 const 的operator[]返回一个代理对象proxy不是bool。这个代理支持赋值、flip()、operator~和到bool的转换所以b[0] true;、b[0].flip();都能写。但代理对象的寿命依赖原bitset不要把auto x b[0];的x保存到b销毁之后使用。count()的返回类型是std::size_t不是int和int比较时注意符号。C20 起count()是constexpr可以在编译期求值C17 及以前不能。三、运算符bitset重载了完整的按位运算符、|、^、~、、以及、!。两个操作数的长度必须相同类型相同结果仍是同长度的bitset。运算符语义注意a b按位与长度必须一致a \b按位或a ^ b按位异或同上~a全部取反包含全部 N 位a k左移 k 位移出的位丢弃空出的低位补 0a k右移 k 位移出的位丢弃空出的高位补 0a b逐位相等长度相同才有意义移位和内置整数不同当k N时结果是全零这是良定义的而内置整数左移超过位宽是 UB。移位的返回类型是bitset不会发生整型提升因此~std::bitset8(0)得到 8 个 1而不是像~0那样得到 -1。流插入operator输出的是从高位到低位的字符序列长度恰好为 N。std::cout std::bitset8(3)输出00000011。流提取按同样的格式读入遇到非 0/1 字符会设置失败状态。bitset没有迭代器也没有data()。想把它当容器传给算法只能先to_string()或用下标循环。这是定长位集合的设计取舍接口围绕位运算而不是围绕泛型遍历。四、完整示例素数筛下面的程序用bitset做 1000 以内的素数筛选。先把所有位设为 10 和 1 手动清零然后从 2 开始若当前数仍是素数就把它的所有倍数清零。// 适用于 C17#include bitset#include cstddef#include iostream#include stringint main() {constexpr std::size_t kLimit 1000;std::bitsetkLimit is_prime;is_prime.set(); // 全部置 1is_prime.reset(0);is_prime.reset(1);for (std::size_t i 2; i * i is_prime.size(); i) {if (is_prime.test(i)) {for (std::size_t j i * i; j is_prime.size(); j i) {is_prime.reset(j); // j 一定是合数}}}std::cout count is_prime.count() \n;std::string bits is_prime.to_string();std::cout highest bit bits.front() \n; // 最高位对应 999std::bitset8 flags(std::string(1010));flags.flip(1); // 00001010 - 00001000flags.set(0);std::cout flags flags.to_ulong() \n;return 0;}按标准语义筛完后count()统计的是 1000 以内的素数个数含 2不含 0 和 1to_string()的第 0 个字符对应最高位 999所以front()输出的是 999 是否为素数的标志。flags初始为 8翻转 bit 1 后变成 8再置 bit 0 得到 9因此最后一行输出00001001 9。这个例子体现了bitset的两个优点内存紧凑1000 位约 125 字节、按位操作语义清晰。缺点也明显长度固定无法在运行时根据输入扩展如果上限来自输入就得用std::vectorbool或第三方的动态位集。常见坑点1. 用运行时变量做模板参数❌int n; std::cin n; std::bitsetn b;—— 模板参数必须是编译期常量编译失败。✅ 用constexpr std::size_t N 1000;长度需要运行时决定时改用std::vectorbool。2. 用operator[]越界访问❌b[b.size()]—— 不做检查是 UB。✅ 用b.test(pos)越界时抛std::out_of_range或先判断pos b.size()。3. 字符串方向记反❌ 以为std::bitset8(1000)的 1 在最高位 —— 实际最右字符才是 bit 0。✅ 记住「字符串末尾对应低位」需要高位在前时自行反转字符串。4.to_ulong溢出❌std::bitset64(~0ULL).to_ulong()在 32 位unsigned long平台上溢出。✅ 用to_ullong()C11并注意unsigned long宽度是平台相关的。5. 以为~只作用于有效位之外❌ 把~b当成「只翻转已设置的位」—— 它翻转全部 N 位包括前导零。✅ 需要只翻转某几位时逐位flip(pos)或先与掩码相与。6. 把bitset当容器传给算法❌std::find(b.begin(), b.end(), true)——bitset没有迭代器编译失败。✅ 用下标循环或先to_string()再处理。7. 移位位数超过长度❌ 以为b 100是 UB 或结果不变。✅ 标准规定结果全零是良定义的但业务上通常说明移位量算错了。8. 保存operator[]的代理对象❌auto bit std::bitset8(1)[0];临时bitset销毁后bit悬垂。✅ 需要值时直接转boolbool bit std::bitset8(1)[0];总结主题结论长度模板参数编译期常量不可运行时改变构造默认全 0整数截断高位字符串最右字符是 bit 0读写test/set(pos)检查越界operator[]不检查位运算、转换to_string、to_ulong、to_ullong溢出抛异常遍历无迭代器用下标或转字符串适用定长开关集合、筛法、状态压缩std::bitset把「定长位集合」这件事做得既紧凑又类型安全代价是长度必须编译期确定、接口不兼容泛型算法。用它时记住三条模板参数不能是运行时值、下标访问分带检查和不带检查两套、字符串的最右字符对应最低位。剩下的成员函数和运算符查表即可。