目录
1、框架
- bitset能实现对数字的位的操作,同时也能通过类似于数组的下标来访问各个位的数值,以执行相应的操作。模拟bitset就是用一个普通的数组来存储数据以达到模拟的目的。
- 如果我们以一个整型作为比特位的容器,那么如果要求0~N范围的比特位,就需要有
N/32+1
个整型来容纳这些比特位,同理如果以char为容器,则需要N/8+1
个char来容纳N个比特位。这里我们用vector数组作为底层容纳比特位的容器,且其存储的数据类型为char。namespace cpp { //N个比特位的位图 template<size_t N> class bitset { public: //构造函数 bitset(); //把x映射的位标记成1 void set(size_t x); //把x映射的位标记成0 void reset(size_t x); //判断指定比特位x的状态是否为1 bool test(size_t x); //翻转指定pos void flip(size_t x); //获取位图中可以容纳位N的个数 size_t size() //统计set中1的位数 size_t count(); //判断所有比特位若无置为1,返回true bool none(); //判断位图中是否有位被置为1,若有则返回true bool any(); //全部NUM个bit位被set返回true bool all(); private: vector<char> _bits;//位图 }; }
2、成员函数
构造函数
- 一个char类型有8个bit位,所以理想状态下N个比特位的位图就需要用到N / 8个字节,但仅限于N是8的整数倍,如果N位10,那么计算下来就会少2个比特位,因此综合考虑,我们给出N / 8 + 1个字节,这样算下来,所需的N个比特位绝对都能访问到,最多可以整除的情况下浪费了8个比特位(1字节)
而构造函数,我们只需要对这所有的比特位(N / 8 + 1)个字节的大小初始化为0即可。
//构造函数 bitset() { //+1保证足够比特位,最多浪费8个比特位 _bits.resize(N / 8 + 1, 0); }
set
set的作用是把x映射的位置标记成1,实现规则如下:
- 通过x / 8计算x在第i个char类型
- 通过x % 8计算x在char第j个比特位
- 利用按位或 | 把第i个char中的第j个比特位置为1
//把x映射的位标记成1 void set(size_t x) { //x映射的比特位在第几个char对象 size_t i = x / 8; //x在char第几个比特位 size_t j = x % 8; //利用按位或|把第j位标记成1 _bits[i] |= (1 << j); }
调试窗口如下:
reset
reset的作用是把把x映射的位标记成0,实现规则如下:
- 通过x / 8计算x在第i个char类型
- 通过x % 8计算x在char第j个比特位
- 将1左移 j 位再整体反转后与第 i 个char类型进行与运算即可。
//把x映射的位标记成0 void reset(size_t x) { //x映射的比特位在第几个char对象 size_t i = x / 8; //x在char第几个比特位 size_t j = x % 8; //将1左移 j 位再整体反转后与第 i 个char进行与运算 _bits[i] &= (~(1 << j)); }
test
test的作用是判断指定比特位x的状态是否为1,实现规则如下:
- 通过x / 8计算x在第i个char类型
- 通过x % 8计算x在char第j个比特位
- 将1左移 j 位后与第 i 个char类型进行与运算得出结果
- 若结果非0,则该位被设置,否则该位未被设置
//判断指定比特位x的状态是否为1 bool test(size_t x) { //x映射的比特位在第几个char对象 size_t i = x / 8; //x在char第几个比特位 size_t j = x % 8; //将1左移 j 位后与第 i 个char类型进行与运算得出结果 return _bits[i] & (1 << j); }
flip
flip的作用是用于翻转指定位,若指定位为0,翻转后为1,若指定位为1,反转后为0,实现规则如下:
- 通过x / 8计算x在第i个char类型
- 通过x % 8计算x在char第j个比特位
- 将1左移 j 位后与第 i 个char进行按位异或运算^即可。
//翻转指定位x void flip(size_t x) { //x映射的比特位在第几个char对象 size_t i = x / 8; //x在char第几个比特位 size_t j = x % 8; //将1左移 j 位后与第 i 个char进行按位异或运算^即可。 _bits[i] ^= (1 << j); }
count
count的作用是统计位图中被设计为1的个数,实现规则如下:
- n = n & (n-1) => 消去n的二进制数中最右边的1
- n不为零继续执行第一步
- 执行了几次说明n中有多少个1
//统计set中1的个数 size_t count() { size_t count = 0; for (auto e : _bits) { int n = e; while (n) { n = n & (n - 1); count++; } } return count; }
size
size的作用是获取位图中可以容纳位N的个数
//获取位图中可以容纳位N的个数 size_t size() { return N; }
none any all
1、none
- none的作用是遍历每一个char,如果全为0,则none返回true
//判断所有比特位若无置为1,返回true bool none() { //遍历每个char for (auto e : _bits) { if (e != 0)//说明有位被置为1,返回false return false; } return true;//说明全为0,返回true }
2、any
- any的作用判断位图中是否有位被置为1,若有则返回true,这其实和none的作用刚好相反,因此我们直接复用none即可。
//判断位图中是否有位被置为1,若有则返回true bool any() { return !none(); }
3、all
all的作用是判断位图中是否所有的位都被置为1,注意这里的特殊性,先前开辟空间时我们为了避免出现N/8不能整除的情况,特地在resize时多开了一个char类型(8比特)的空间,这8比特里面只有前N%8个比特位才是有效的(剩下的都是多开的空间),因此all函数需要分情况讨论。
- 先检查前n-1个char的二进制是否为全1。
- 再检查最后一个char的前N%8个比特位是否为全1。
//全部NUM个bit位被set返回true bool all() { size_t size = _bits.size(); //先检查前N-1个char for (size_t i = 0; i < size - 1; i++) { if (~_bits[i] != 0)//取反应该为0,否则取反之前不全为1,返回false return false; } //再检查最后一个char的前 N%8 个位 for (size_t j = 0; j < N % 8; j++) { if ((_bits[size - 1] & (1 << j)) == 0)//和test的原理一致 return false; } return true; }