首页 > 编程笔记 > C++笔记 阅读:12

C++ bitset容器的用法(非常详细)

对于 C++ 开发人员来说,使用位标志进行操作并不罕见。这可能是因为它们与操作系统 API(通常是用 C语言编写的)一起工作,这些 API 以位标志的形式接受各种类型的参数(如选项或样式),也可能是因为它们与做类似事情的库一起工作,或者只是因为某些类型的问题可以通过位标志自然地解决。

我们可以考虑使用位和位操作的替代方案,例如定义每个选项/标志都有一个元素的数组,或者定义一个包含成员和函数的结构来建模位标志,但这些通常更复杂。在需要将表示位标志的数值传递给函数的情况下,仍然需要将数组或结构转换为位序列。出于这个原因,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,那么你很可能编写过或至少见过操作位的代码。这通常涉及以下操作:
下面是定义控件边框样式的标志的简单示例,该控件的左侧、右侧、顶部或底部可以有边框,有无边框可以任意组合:
#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 工作风格,它使得我们能够编写鲁棒性更好、更安全的代码,因为它使用成员函数作风格,使得我们能够编写鲁棒性更好、更安全的代码,借助成员函数抽象了位操作,尽管我们仍然需要确定集合中的每个位代表什么:
除了这些操作之外,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());

相关文章