• 【C++】STL详解(十四)—— bitset(位图)的模拟实现


    在这里插入图片描述

    ​📝个人主页@Sherry的成长之路
    🏠学习社区:Sherry的成长之路(个人社区)
    📖专栏链接:C++学习
    🎯长路漫漫浩浩,万事皆有期待

    上一篇博客:【C++】STL详解(九)—— set、map、multiset、multimap的介绍及使用

    bitset类各函数接口总览

    namespace sherry
    {
    	//模拟实现位图
    	template<size_t N>
    	class bitset
    	{
    	public:
    		//构造函数
    		bitset();
    		//设置位
    		void set(size_t pos);
    		//清空位
    		void reset(size_t pos);
    		//反转位
    		void flip(size_t pos);
    		//获取位的状态
    		bool test(size_t pos);
    		//获取可以容纳的位的个数
    		size_t size();
    		//获取被设置位的个数
    		size_t count();
    		//判断位图中是否有位被设置
    		bool any();
    		//判断位图中是否全部位都没有被设置
    		bool none();
    		//判断位图中是否全部位都被设置
    		bool all();
    		//打印函数
    		void Print();
    	private:
    		vector<int> _bits; //位图
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    注意:为了防止与标准库当中的bitset类产生命名冲突,模拟实现时需放在自己的命名空间当中。

    bitset类的实现

    构造函数

    在构造位图时,我们需要根据所给位数N,创建一个N位的位图,并且将该位图中的所有位都初始化为0。

    一个整型有32个比特位,因此N个位的位图就需要用到N/32个整型,但是实际我们所需的整型个数是N/32+1,因为所给非类型模板参数N的值可能并不是32的整数倍。

    例如,当N为40时,我们需要用到两个整型,即40/32+1=2。

    代码如下:

    //构造函数
    bitset()
    {
    	_bits.resize(N / 32 + 1, 0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    set、reset、flip、test

    set成员函数用于设置位。

    设置位图中指定的位的方法如下:

    计算出该位位于第 i 个整数的第 j 个比特位。
    将1左移 j 位后与第 i 个整数进行或运算即可。

    代码如下:

    //设置位
    void set(size_t pos)
    {
    	assert(pos < N);
    
    	//算出pos映射的位在第i个整数的第j个位
    	int i = pos / 32;
    	int j = pos % 32;
    	_bits[i] |= (1 << j); //将该位设置为1(不影响其他位)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    reset成员函数用于清空位。

    清空位图中指定的位的方法如下:

    计算出该位位于第 i 个整数的第 j 个比特位。
    将1左移 j 位再整体反转后与第 i 个整数进行与运算即可。

    代码如下:

    //清空位
    void reset(size_t pos)
    {
    	assert(pos < N);
    
    	//算出pos映射的位在第i个整数的第j个位
    	int i = pos / 32;
    	int j = pos % 32;
    	_bits[i] &= (~(1 << j)); //将该位设置为0(不影响其他位)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    flip成员函数用于反转位。

    反转位图中指定的位的方法如下:

    计算出该位位于第 i 个整数的第 j 个比特位。
    将1左移 j 位后与第 i 个整数进行异或运算即可。

    代码如下:

    //反转位
    void flip(size_t pos)
    {
    	assert(pos < N);
    
    	//算出pos映射的位在第i个整数的第j个位
    	int i = pos / 32;
    	int j = pos % 32;
    	_bits[i] ^= (1 << j); //将该进行反转(不影响其他位)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    test成员函数用于获取位的状态。

    获取位图中指定的位的状态的方法如下:

    计算出该位位于第 i 个整数的第 j 个比特位。
    将1左移 j 位后与第 i 个整数进行与运算得出结果。
    若结果非0,则该位被设置,否则该位未被设置。

    代码如下:

    //获取位的状态
    bool test(size_t pos)
    {
    	assert(pos < N);
    
    	//算出pos映射的位在第i个整数的第j个位
    	int i = pos / 32;
    	int j = pos % 32;
    	if (_bits[i] & (1 << j)) //该比特位被设置
    		return true;
    	else //该比特位未被设置
    		return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    size、count

    size成员函数用于获取位图中可以容纳的位的个数。

    我们直接将所给非类型模板参数进行返回即可。

    //获取可以容纳的位的个数
    size_t size()
    {
    	return N;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    count成员函数用于获取位图中被设置的位的个数。

    获取位图中被设置的位的个数,也就是统计位图中1的个数,我们只需要依次统计每个整数二进制中1的个数,然后将其相加即可得到位图中1的个数。

    统计二进制中1的个数的方法如下:

    将原数 n 与 n - 1 进行与运算得到新的 n 。
    判断 n 是否为0,若 n 不为0则继续进行第一步。
    如此进行下去,直到 n 最终为0,此时该操作进行了几次就说明二进制中有多少个1。

    因为该操作每进行一次就会消去二进制中最右边的1,例图如下:

    代码如下:

    //获取被设置位的个数
    size_t count()
    {
    	size_t count = 0;
    	//将每个整数中1的个数累加起来
    	for (auto e : _bits)
    	{
    		int num = e;
    		//计算整数num中1的个数
    		while (num)
    		{
    			num = num&(num - 1);
    			count++;
    		}
    	}
    	return count; //位图中1的个数,即被设置位的个数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    any、none、all

    any成员函数用于判断位图中是否有位被设置。

    我们只需遍历每一个整数,若这些整数全部都为0,则说明位图中没有位被设置过。
    虽然位图可能并没有包含最后一个整数的全部比特位,但由于我们构造位图时是将整数的全部比特位都初始化成了0,因此不会对此处判断造成影响。

    代码如下:

    //判断位图中是否有位被设置
    bool any()
    {
    	//遍历每个整数
    	for (auto e : _bits)
    	{
    		if (e != 0) //该整数中有位被设置
    			return true;
    	}
    	return false; //全部整数都是0,则没有位被设置过
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    none成员函数用于判断位图中是否全部位都没有被设置。

    位图中是否全部位都没有被设置,实际上就是位图中有位被设置的反面,因此none成员函数直接调用any成员函数,然后将返回值取反后再进行返回即可。

    //判断位图中是否全部位都没有被设置
    bool none()
    {
    	return !any();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    all成员函数用于判断位图中是否全部位都被设置。

    判断过程分为两步:

    先检查前n-1个整数的二进制是否为全1。
    再检查最后一个整数的前N%32个比特位是否为全1。

    需要注意的是,如果位图没有包含最后一个整数的全部比特位,那么最后一个整数的二进制无论如何都不会为全1,所以在判断最后一个整数时应该只判断位图所包含的比特位。

    代码如下:

    //判断位图中是否全部位都被设置
    bool all()
    {
    	size_t n = _bits.size();
    	//先检查前n-1个整数
    	for (size_t i = 0; i < n - 1; i++)
    	{
    		if (~_bits[i] != 0) //取反后不为全0,说明取反前不为全1
    			return false;
    	}
    	//再检查最后一个整数的前N%32位
    	for (size_t j = 0; j < N % 32; j++)
    	{
    		if ((_bits[n - 1] & (1 << j)) == 0) //该位未被设置
    			return false;
    	}
    	return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    打印函数

    可以实现一个打印函数,便于检查我们上述代码的正确性,打印位图时遍历位图所包含的比特位进行打印即可,在打印位图的过程中可以顺便统计位图中位的个数count,将count与我们传入的非类型模板参数N进行比较,可以判断位图大小是否是符合我们的预期。

    //打印函数
    void Print()
    {
    	int count = 0;
    	size_t n = _bits.size();
    	//先打印前n-1个整数
    	for (size_t i = 0; i < n - 1; i++)
    	{
    		for (size_t j = 0; j < 32; j++)
    		{
    			if (_bits[i] & (1 << j)) //该位被设置
    				cout << "1";
    			else //该位未被设置
    				cout << "0";
    			count++;
    		}
    	}
    	//再打印最后一个整数的前N%32位
    	for (size_t j = 0; j < N % 32; j++)
    	{
    		if (_bits[n - 1] & (1 << j)) //该位被设置
    			cout << "1";
    		else //该位未被设置
    			cout << "0";
    		count++;
    	}
    	cout << " " << count << endl; //打印总共打印的位的个数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    总结:

    今天我们比较详细地完成了bitset(位图)的模拟实现,了解了一些有关的底层原理。接下来,我们将进行布隆过滤器的学习。希望我的文章和讲解能对大家的学习提供一些帮助。

    当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

    在这里插入图片描述

  • 相关阅读:
    linux如何使 CPU使用率 保持在指定百分比?
    前端学习1
    基于SSM的医用物理学实验考核系统设计与实现
    6.1_4 Python3.x入门 P4 【基础】可变序列(列表list、字典dict、集合set)
    零基础看深度学习python代码的基本方法
    Android学习笔记 29. Activity组件
    ASP.NET 6 使用工作单元操作 MongoDB
    关于pycharm怎么设置中文、背景图、快捷键的一些小Tips
    C语言 指针
    人傻了,在学校也没人跟我说微服务这么重要啊!惨遭工作毒打的我只能说这份微服务笔记真是我的救星!
  • 原文地址:https://blog.csdn.net/m0_73258399/article/details/133562539