【C++基础】第九十九课:[标准库特殊设施]bitset类型

bitset

Posted by x-jeff on June 21, 2024

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.bitset类型

标准库定义了bitset类,使得位运算的使用更为容易,并且能够处理超过最长整型类型大小的位集合。bitset类定义在头文件bitset中。

2.定义和初始化bitset

表17.2列出了bitset的构造函数。bitset类是一个类模板,它类似array类,具有固定的大小(参见:容器定义和初始化)。当我们定义一个bitset时,需要声明它包含多少个二进制位:

1
bitset<32> bitvec(1U); //32位;低位为1,其他位为0

参见:指定字面值的类型

大小必须是一个常量表达式。这条语句定义bitvec为一个包含32位的bitset。就像vector包含未命名的元素一样,bitset中的二进制位也是未命名的,我们通过位置来访问它们。二进制位的位置是从0开始编号的。因此,bitvec包含编号从0到31的32个二进制位。编号从0开始的二进制位被称为低位(low-order),编号到31结束的二进制位被称为高位(high-order)。

2.1.用unsigned值初始化bitset

当我们使用一个整型值来初始化bitset时,此值将被转换为unsigned long long类型并被当作位模式来处理。bitset中的二进制位将是此模式的一个副本。如果bitset的大小大于一个unsigned long long中的二进制位数,则剩余的高位被置为0。如果bitset的大小小于一个unsigned long long中的二进制位数,则只使用给定值中的低位,超出bitset大小的高位被丢弃:

1
2
3
4
5
6
//bitvec1比初始值小;初始值中的高位被丢弃
bitset<13> bitvec1(0xbeef); //二进制位序列为1111011101111
//bitvec2比初始值大;它的高位被置为0
bitset<20> bitvec2(0xbeef); //二进制位序列为00001011111011101111
//在64位机器中,long long 0ULL是64个0比特,因此~0ULL是64个1
bitset<128> bitvec3(~0ULL); //0~63位为1;63~127位为0

2.2.从一个string初始化bitset

我们可以从一个string或一个字符数组指针来初始化bitset。两种情况下,字符都直接表示位模式。与往常一样,当我们使用字符串表示数时,字符串中下标最小的字符对应高位,反之亦然:

1
bitset<32> bitvec4("1100"); //2、3两位为1,剩余两位为0

如果string包含的字符数比bitset少,则bitset的高位被置为0。

string的下标编号习惯与bitset恰好相反:string中下标最大的字符(最右字符)用来初始化bitset中的低位(下标为0的二进制位)。

我们不必使用整个string来作为bitset的初始值,可以只用一个子串作为初始值:

1
2
3
string str("1111111000000011001101");
bitset<32> bitvec5(str, 5, 4); //从str[5]开始的四个二进制位,1100
bitset<32> bitvec6(str, str.size()-4); //使用最后四个字符

3.bitset操作

bitset操作(参见表17.3)定义了多种检测或设置一个或多个二进制位的方法。bitset类还支持位运算符。这些运算符用于bitset对象的含义与内置运算符用于unsigned运算对象相同。

count、size、all、any和none等几个操作都不接受参数,返回整个bitset的状态。其他操作——set、reset和flip则改变bitset的状态。改变bitset状态的成员函数都是重载的。对每个函数,不接受参数的版本对整个集合执行给定的操作;接受一个位置参数的版本则对指定位执行操作:

1
2
3
4
5
6
7
8
9
bitset<32> bitvec(1U); //32位;低位为1,剩余位为0
bool is_set = bitvec.any(); //true,因为有1位置位
bool is_not_set = bitvec.none(); //false,因为有1位置位了
bool all_set = bitvec.all(); //false,因为只有1位置位
size_t onBits = bitvec.count(); //返回1
size_t sz = bitvec.size(); //返回32
bitvec.flip(); //翻转bitvec中的所有位
bitvec.reset(); //将所有位复位
bitvec.set(); //将所有位置位

当bitset对象的一个或多个位置位(即,等于1)时,操作any返回true。相反,当所有位复位时,none返回true。新标准引入了all操作,当所有位置位时返回true。操作count和size返回size_t类型的值,分别表示对象中置位的位数或总位数。函数size是一个constexpr函数,因此可以用在要求常量表达式的地方。

成员flip、set、reset及test允许我们读写指定位置的位:

1
2
3
4
5
bitvec.flip(0); //翻转第一位
bitvec.set(bitvec.size() - 1); //置位最后一位
bitvec.set(0, 0); //复位第一位
bitvec.reset(i); //复位第i位
bitvec.test(0); //返回false,因为第一位是复位的

下标运算符对const属性进行了重载。const版本的下标运算符在指定位置位时返回true,否则返回false。非const版本返回bitset定义的一个特殊类型,它允许我们操纵指定位的值:

1
2
3
4
5
bitvec[0] = 0; //将第一位复位
bitvec[31] = bitvec[0]; //将最后一位设置为与第一位一样
bitvec[0].flip(); //翻转第一位
~bitvec[0]; //等价操作,也是翻转第一位
bool b = bitvec[0]; //将bitvec[0]的值转换为bool类型

3.1.提取bitset的值

to_ulong和to_ullong操作都返回一个值,保存了与bitset对象相同的位模式。只有当bitset的大小小于等于对应的大小(to_ulong为unsigned long,to_ullong为unsigned long long)时,我们才能使用这两个操作:

1
2
unsigned long ulong = bitvec3.to_ulong();
cout << "ulong = " << ulong << endl;

如果bitset中的值不能放入给定类型中,则这两个操作会抛出一个overflow_error异常(参见:try语句块和异常处理)。

3.2.bitset的IO运算符

输入运算符从一个输入流读取字符,保存到一个临时的string对象中。直到读取的字符数达到对应bitset的大小时,或是遇到不是1或0的字符时,或是遇到文件尾或输入错误时,读取过程才停止。随即用临时string对象来初始化bitset。如果读取的字符数小于bitset的大小,则与往常一样,高位将被置为0。

输出运算符打印一个bitset对象中的位模式:

1
2
3
bitset<16> bits;
cin >> bits; //从cin读取最多16个0或1
cout << "bits: " << bits << endl; //打印刚刚读取的内容

3.3.使用bitset

为了说明如何使用bitset,我们重新实现了评分程序,用bitset代替unsigned long表示30个学生的测验结果——“通过/失败”:

1
2
3
4
5
6
7
8
9
10
11
bool status;
//使用位运算符的版本
unsigned long quizA = 0; //此值被当作位集合使用
quizA |= 1UL << 27; //指出第27个学生通过了测验
status = quizA & (1UL << 27); //检查第27个学生是否通过了测验
quizA &= ~(1UL << 27); //第27个学生未通过测验
//使用标准库类bitset完成等价的工作
bitset<30> quizB; //每个学生分配一位,所有位都被初始化为0
quizB.set(27); //指出第27个学生通过了测验
status = quizB[27]; //检查第27个学生是否通过了测验
quizB.reset(27); //第27个学生未通过测验