C++ bitset容器的用法(非常详细)
对于 C++ 开发人员来说,使用位标志进行操作并不罕见。这可能是因为它们与操作系统 API(通常是用 C语言编写的)一起工作,这些 API 以位标志的形式接受各种类型的参数(如选项或样式),也可能是因为它们与做类似事情的库一起工作,或者只是因为某些类型的问题可以通过位标志自然地解决。
我们可以考虑使用位和位操作的替代方案,例如定义每个选项/标志都有一个元素的数组,或者定义一个包含成员和函数的结构来建模位标志,但这些通常更复杂。在需要将表示位标志的数值传递给函数的情况下,仍然需要将数组或结构转换为位序列。出于这个原因,C++ 标准提供了一种名为 std::bitset 的容器,它主要用于存储固定长度的位序列。
bitset 类在头文件
1) 所有位均设置为 0 的空 bitset:
2) 数值 bitset:
3) 字符串 '0' 和 '1' 的 bitset:
4) 字符串的 bitset,该字符串包含表示 '0' 和 '1' 的任意两个字符。在这种情况下,我们必须指定哪个字符表示 0(第四个参数 'o'),哪个字符表示 1(第五个参数 'x'):
要测试集合中的单个位或整个集合中的特定值,请使用以下方法:
1) 使用 count() 获取设置为 1 的位数:
2) 使用 any() 检查是否至少有一个位设置为 1:
3) 使用 all() 检查是否所有位都设置为 1:
4) 使用 none() 检查是否所有位都设置为 0:
5) 使用 test() 检查单个位的值(其位置是函数的唯一参数):
6) 使用 operator[] 访问和测试单个位:
要修改 bitset 的内容,可以使用以下方法:
1) 成员操作符 |=、&=、^= 和 ~ 分别执行二进制运算 OR、AND、XOR 和 NOT。此外,也可以使用非成员操作符 |、& 和 ^:
2) 成员操作符 <<=、<<、>>= 和 >> 执行移位操作:
3) flip() 将整个集合或单个位从 0 切换到 1 或从 1 切换到 0:
4) set() 将整个集合或单个位更改为 true 或指定值:
5) reset() 将整个集合或单个位更改为 false:
要将 bitset 转换为数值或字符串值,请使用以下方法:
1) to_ulong() 和 to_ullong() 将值转换为 unsigned long 或 unsigned long long。如果值无法在输出类型中表示,这些操作将抛出 std::overflow_error 异常。请参考以下示例:
2) to_string() 将值转换为 std::basic_string。默认情况下,结果是一个包含' 0' 和 '1' 的字符串,但你可以为这两个值指定不同的字符:
下面是定义控件边框样式的标志的简单示例,该控件的左侧、右侧、顶部或底部可以有边框,有无边框可以任意组合:
除了这些操作之外,bitset 类还有其他方法,用于执行按位操作、移位、测试等。
从概念上讲,std::bitset 是数值的表示形式,它使你能够访问和修改单个位。然而,bitset 内部有一个整数值数组,在其上可以执行按位操作。bitset 的大小不限于数值类型的大小,它可以是任何东西,除了它是一个编译时常数。
控件边框样式的示例可以使用 std::bitset 按以下方式编写:
我们可以考虑使用位和位操作的替代方案,例如定义每个选项/标志都有一个元素的数组,或者定义一个包含成员和函数的结构来建模位标志,但这些通常更复杂。在需要将表示位标志的数值传递给函数的情况下,仍然需要将数组或结构转换为位序列。出于这个原因,C++ 标准提供了一种名为 std::bitset 的容器,它主要用于存储固定长度的位序列。
bitset 类在头文件
<bitset>
的 std 命名空间中可用,bitset 表示固定大小的位序列,其大小在编译时定义。为了方便起见,本节大多数示例将使用 8 位的 bitset。C++ bitset的使用方式
要构造 std::bitset 对象,请使用以下可用的构造函数:1) 所有位均设置为 0 的空 bitset:
std::bitset<8> b1; // [0,0,0,0,0,0,0,0]
2) 数值 bitset:
std::bitset<8> b2{ 10 }; // [0,0,0,0,1,0,1,0]
3) 字符串 '0' 和 '1' 的 bitset:
std::bitset<8> b3{ "1010"s }; // [0,0,0,0,1,0,1,0]
4) 字符串的 bitset,该字符串包含表示 '0' 和 '1' 的任意两个字符。在这种情况下,我们必须指定哪个字符表示 0(第四个参数 'o'),哪个字符表示 1(第五个参数 'x'):
std::bitset<8> b4 { "ooooxoxo"s, 0, std::string::npos, 'o', 'x' }; // [0,0,0,1,0,1,0]
要测试集合中的单个位或整个集合中的特定值,请使用以下方法:
1) 使用 count() 获取设置为 1 的位数:
std::bitset<8> bs{ 10 }; std::cout << "has " << bs.count() << " 1s" << '\n';
2) 使用 any() 检查是否至少有一个位设置为 1:
if (bs.any()) std::cout << "has some 1s" << '\n';
3) 使用 all() 检查是否所有位都设置为 1:
if (bs.all()) std::cout << "has only 1s" << '\n';
4) 使用 none() 检查是否所有位都设置为 0:
if (bs.none()) std::cout << "has no 1s" << '\n';
5) 使用 test() 检查单个位的值(其位置是函数的唯一参数):
if (!bs.test(0)) std::cout << "even" << '\n';
6) 使用 operator[] 访问和测试单个位:
if(!bs[0]) std::cout << "even" << '\n';
要修改 bitset 的内容,可以使用以下方法:
1) 成员操作符 |=、&=、^= 和 ~ 分别执行二进制运算 OR、AND、XOR 和 NOT。此外,也可以使用非成员操作符 |、& 和 ^:
std::bitset<8> b1{ 42 }; // [0,0,1,0,1,0,1,0] std::bitset<8> b2{ 11 }; // [0,0,0,0,1,0,1,1] auto b3 = b1 | b2; // [0,0,1,0,1,1,1,1] auto b4 = b1 & b2; // [0,0,0,0,1,0,1,0] auto b5 = b1 ^ b2; // [1,1,0,1,1,1,1,1] auto b6 = ~b1; // [1,1,0,1,0,1,0,1]
2) 成员操作符 <<=、<<、>>= 和 >> 执行移位操作:
auto b7 = b1 << 2; // [1,0,1,0,1,0,0,0] auto b8 = b1 >> 2; // [0,0,0,0,1,0,1,0]
3) flip() 将整个集合或单个位从 0 切换到 1 或从 1 切换到 0:
b1.flip(); // [1,1,0,1,0,1,0,1] b1.flip(0); // [1,1,0,1,0,1,0,0]
4) set() 将整个集合或单个位更改为 true 或指定值:
b1.set(0, true); // [1,1,0,1,0,1,0,1] b1.set(0, false); // [1,1,0,1,0,1,0,0]
5) reset() 将整个集合或单个位更改为 false:
b1.reset(2); // [1,1,0,1,0,0,0,0]
要将 bitset 转换为数值或字符串值,请使用以下方法:
1) to_ulong() 和 to_ullong() 将值转换为 unsigned long 或 unsigned long long。如果值无法在输出类型中表示,这些操作将抛出 std::overflow_error 异常。请参考以下示例:
std::bitset<8> bs{ 42 }; auto n1 = bs.to_ulong(); // n1 = 42UL auto n2 = bs.to_ullong(); // n2 = 42ULL
2) to_string() 将值转换为 std::basic_string。默认情况下,结果是一个包含' 0' 和 '1' 的字符串,但你可以为这两个值指定不同的字符:
auto s1 = bs.to_string(); // s1 = "00101010" auto s2 = bs.to_string('o', 'x'); // s2 = "ooxoxoxo"
深度解读C++ bitset
如果你曾经使用过 C API 或类 C API,那么你很可能编写过或至少见过操作位的代码。这通常涉及以下操作:- 定义位标志:这些可以是枚举、类中的静态常量,也可以是 C 风格中使用 #define 引入的宏。通常,有一个标志表示没有值(如样式、选项等)。因为这些应该是位标志,所以它们的值是 2 的幂;
- 在集合(即数值)中添加和删除标志:添加位标志通过位或操作符(value|=FLAG)完成,删除位标志通过位与操作符完成,并使用反标志(value&=~FLAG);
- 测试标志是否已添加到集合中(value & FLAG==FLAG);
- 将标志作为参数调用函数。
下面是定义控件边框样式的标志的简单示例,该控件的左侧、右侧、顶部或底部可以有边框,有无边框可以任意组合:
#define BORDER_NONE 0x00 #define BORDER_LEFT 0x01 #define BORDER_TOP 0x02 #define BORDER_RIGHT 0x04 #define BORDER_BOTTOM 0x08 void apply_style(unsigned int const style) { if (style & BORDER_BOTTOM) { /* do something */ } } // initialize with no flags unsigned int style = BORDER_NONE; // set a flag style = BORDER_BOTTOM; // add more flags style |= BORDER_LEFT | BORDER_RIGHT | BORDER_TOP; // remove some flags style &= ~BORDER_LEFT; style &= ~BORDER_RIGHT; // test if a flag is set if ((style & BORDER_BOTTOM) == BORDER_BOTTOM) { // pass the flags as argument to a function apply_style(style); }标准的 std::bitset 类旨在作为 C++ 替代方案,来取代这种具有位集合的类 C 工作风格,它使得我们能够编写鲁棒性更好、更安全的代码,因为它使用成员函数作风格,使得我们能够编写鲁棒性更好、更安全的代码,借助成员函数抽象了位操作,尽管我们仍然需要确定集合中的每个位代表什么:
- 添加和删除标志分别是通过 set() 和 reset() 方法完成的,它们将由位置指示的位的值设置为 1 或 0(或 true 和 false)。另外,我们也可以使用索引操作符来达到同样的目的;
- 测试是否用 test() 方法设置了位;
- 整数或字符串的转换是通过构造函数完成的,而转换成整数或字符串的转换是通过成员函数完成的,因此 bitset 中的值可以用在需要整数的地方(例如函数的参数)。
除了这些操作之外,bitset 类还有其他方法,用于执行按位操作、移位、测试等。
从概念上讲,std::bitset 是数值的表示形式,它使你能够访问和修改单个位。然而,bitset 内部有一个整数值数组,在其上可以执行按位操作。bitset 的大小不限于数值类型的大小,它可以是任何东西,除了它是一个编译时常数。
控件边框样式的示例可以使用 std::bitset 按以下方式编写:
struct border_flags { static const int left = 0; static const int top = 1; static const int right = 2; static const int bottom = 3; }; // initialize with no flags std::bitset<4> style; // set a flag style.set(border_flags::bottom); // set more flags style.set(border_flags::left) .set(border_flags::top) .set(border_flags::right); // remove some flags style[border_flags::left] = 0; style.reset(border_flags::right); // test if a flag is set if (style.test(border_flags::bottom)) {} // pass the flags as argument to a function apply_style(style.to_ulong());