在c++程序开发中,位运算是优化性能、简化逻辑的重要技巧,尤其是在处理状态标记、权限控制、数据压缩等场景时,直接操作二进制位能大幅减少内存占用和计算开销。c++标准库提供的bitset容器封装了完整的位操作能力,既保留了位运算的高效性,又避免了手动操作位运算符可能出现的逻辑错误,是处理固定长度位序列的常用工具。

bitset基础概念与初始化
bitset是c++ STL中的模板类,位于<bitset>头文件中,它的模板参数指定了位序列的长度,这个长度在编译期就必须确定,因此bitset适合处理长度固定的位操作场景。
常见的初始化方式有以下几种:
- 默认初始化:所有位都设为0
- 用整数初始化:将整数的二进制表示存入bitset
- 用字符串初始化:字符串中的0和1对应bitset的位
下面是初始化示例:
#include <bitset>
#include <iostream>
#include <string>
int main() {
// 默认初始化,长度为8,所有位为0
std::bitset<8> bs1;
// 用整数初始化,整数5的二进制是00000101
std::bitset<8> bs2(5);
// 用字符串初始化,字符串从左到右对应高位到低位
std::bitset<8> bs3("10110001");
std::cout << "bs1: " << bs1 << std::endl;
std::cout << "bs2: " << bs2 << std::endl;
std::cout << "bs3: " << bs3 << std::endl;
return 0;
}
bitset常用成员函数
bitset提供了丰富的成员函数,覆盖了大部分位操作需求,下面介绍最常用的几个:
| 成员函数 | 功能说明 |
|---|---|
| size() | 返回bitset的位长度 |
| count() | 返回bitset中值为1的位的数量 |
| any() | 判断是否存在值为1的位,存在返回true |
| none() | 判断是否所有位都为0,是返回true |
| test(pos) | 判断pos位置的位是否为1,是返回true |
| set(pos, val) | 将pos位置的位设为val,val默认是1 |
| reset(pos) | 将pos位置的位设为0 |
| flip(pos) | 翻转pos位置的位,0变1,1变0 |
| to_ulong() | 将bitset转换为unsigned long整数 |
| to_string() | 将bitset转换为字符串 |
下面是成员函数的使用示例:
#include <bitset>
#include <iostream>
int main() {
std::bitset<8> bs("10110001");
std::cout << "位长度: " << bs.size() << std::endl;
std::cout << "值为1的位数量: " << bs.count() << std::endl;
std::cout << "是否存在值为1的位: " << bs.any() << std::endl;
std::cout << "是否所有位都为0: " << bs.none() << std::endl;
std::cout << "第3位是否为1: " << bs.test(3) << std::endl; // 位从0开始计数,右起第一位为0位
bs.set(2); // 将第2位设为1
std::cout << "set后: " << bs << std::endl;
bs.reset(0); // 将第0位设为0
std::cout << "reset后: " << bs << std::endl;
bs.flip(7); // 翻转第7位
std::cout << "flip后: " << bs << std::endl;
std::cout << "转换为整数: " << bs.to_ulong() << std::endl;
std::cout << "转换为字符串: " << bs.to_string() << std::endl;
return 0;
}
bitset结合位运算符实现高效运算
bitset支持所有常用的位运算符,包括按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移(<<)、右移(>>),这些运算符可以直接作用于两个相同长度的bitset对象,运算效率非常高。
下面是位运算符结合bitset的使用示例:
#include <bitset>
#include <iostream>
int main() {
std::bitset<8> bs1("10110001");
std::bitset<8> bs2("01001010");
// 按位与运算
std::bitset<8> and_result = bs1 & bs2;
std::cout << "按位与结果: " << and_result << std::endl;
// 按位或运算
std::bitset<8> or_result = bs1 | bs2;
std::cout << "按位或结果: " << or_result << std::endl;
// 按位异或运算
std::bitset<8> xor_result = bs1 ^ bs2;
std::cout << "按位异或结果: " << xor_result << std::endl;
// 按位取反运算
std::bitset<8> not_result = ~bs1;
std::cout << "按位取反结果: " << not_result << std::endl;
// 左移运算
std::bitset<8> left_shift = bs1 << 2;
std::cout << "左移2位结果: " << left_shift << std::endl;
// 右移运算
std::bitset<8> right_shift = bs1 >> 3;
std::cout << "右移3位结果: " << right_shift << std::endl;
return 0;
}
bitset与原生位运算的适用场景对比
虽然bitset封装了位操作,但原生位运算(直接对整数进行位操作)仍然有适用场景,两者的选择可以参考以下原则:
- 如果位序列长度固定,且需要频繁进行位的状态查询、修改、统计,优先选择
bitset,代码更简洁,可读性更高 - 如果位序列长度不固定,或者需要处理的位长度超过
bitset的最大支持长度(通常和unsigned long的长度相关),可以使用vector<bool>或者手动用整数数组模拟位运算 - 如果是对单个整数进行简单的位操作,比如判断某个标志位是否置位,原生位运算的效率更高,不需要额外的容器开销
实战示例:用bitset实现权限判断
权限判断是位运算的典型应用场景,每个权限对应一个二进制位,1表示拥有该权限,0表示没有。下面用bitset实现一个简单的权限判断逻辑:
#include <bitset>
#include <iostream>
#include <string>
// 定义权限对应的位位置
const int READ_PERMISSION = 0; // 读权限
const int WRITE_PERMISSION = 1; // 写权限
const int EXECUTE_PERMISSION = 2;// 执行权限
int main() {
// 用户权限bitset,初始拥有读和执行权限
std::bitset<3> user_permission;
user_permission.set(READ_PERMISSION);
user_permission.set(EXECUTE_PERMISSION);
// 判断是否有写权限
if (user_permission.test(WRITE_PERMISSION)) {
std::cout << "用户拥有写权限" << std::endl;
} else {
std::cout << "用户没有写权限" << std::endl;
}
// 新增写权限
user_permission.set(WRITE_PERMISSION);
std::cout << "新增写权限后,用户权限: " << user_permission << std::endl;
// 移除执行权限
user_permission.reset(EXECUTE_PERMISSION);
std::cout << "移除执行权限后,用户权限: " << user_permission << std::endl;
return 0;
}
通过上面的示例可以看出,bitset让权限判断的逻辑非常清晰,不需要手动计算位移和掩码,大大降低了出错概率。
注意事项
bitset的长度参数是编译期常量,不能在运行时动态修改,如果需要动态长度的位序列,可以考虑vector<bool>,但vector<bool>的性能和接口不如bitset友好- 访问
bitset的位时,下标从0开始,0对应最低位(最右边的位) - 用字符串初始化
bitset时,字符串的长度不能超过bitset的长度,否则会截断或者报错 to_ulong()转换时,如果bitset的值超过unsigned long的表示范围,会抛出std::overflow_error异常,使用时需要注意