C++学习进阶:二进制与位运算

目录

1.进制与原反补码

2.位运算

 2.1.按位与

 2.2.按位或

2.3.异或 

2.4.取反

2.5.移位

3.部分面试题

3.1.不创建新的变量,实现两个变量的交换

3.2.求一个整数存储在内存中二进制中1的个数 

 3.3.有40亿个无序且不重复的无符号的整数,如何快速判断这个数是否在那40亿个数当中


这一部分本来是C语言的内容,当学习位图时,发现这一部分的只是有点欠缺,所以恶补了一下,望周知!!!

1.进制与原反补码

我们先用几个例子来进行学习:

  • 123(10) = 1*10^2 + 2*10^1 + 3*10^0 
  • 111(2) = 7 (10)

这就是进制

我们知道在C语言中,一个整型的大小是4个字节,一个字节是8个比特位,一个bit对应0或者1

所以对于数字7 :

// 7
// 00000000 00000000 00000111 -原码
// 00000000 00000000 00000111 -反码
// 00000000 00000000 00000111 -补码

对于正整数来说,我们通过10进制来找到二进制,写成32位这时就是原码,而正整数的原反补码是一样的。

(注意32位的整型,第一位的0代表正数,1代表负数,这个位置叫做符号位)

那么数字-7呢?

// -7
// 10000000 00000000 00000111 -原码		因为是负数第一位符号位为1
// 11111111 11111111 11111000 -反码		反码通过原码符号位不变,其他位按位取反
// 11111111 11111111 11111001 -补码		补码等于反码加1

 这里我们发现跟正整数有较大的区别


整数在内存中以二进制的补码形式存储 ,操作也是通过二进制的补码形式,最终转换为10进制

2.位运算

基本规则:

接下来我们按顺序来研究这几个位运算操作!!!

 2.1.按位与

当 位运算 对应位置 均为1时 才是1。

// 按位与

0 & 0 = 0;
1 & 0 = 0;
0 & 1 = 0;
1 & 1 = 1;

0111 & 0101 = 0101

 2.2.按位或

只要 对应位为1,那么都是1 

// 按位或

0 | 0 = 0;
1 | 0 = 1;
0 | 1 = 1;
1 | 1 = 1;

0110 | 0101 = 0111

2.3.异或 

相同为0,不同为1

// 异或

0 ^ 0 = 0;
1 ^ 0 = 1;
0 ^ 1 = 1;
1 ^ 1 = 0;

0110 ^ 0101 = 0011

2.4.取反

位置为0变成1,位置为1变成0

// 取反

~0011 = 1100
~1100 = 0011

2.5.移位

  1. 左移(<<):将一个二进制数的所有位向左移动指定的位数。左移操作会在右侧添加0,并丢弃左侧超出指定位数的位。左移操作可以看作是将一个数乘以2的指定次幂。

  2. 右移(>>):将一个二进制数的所有位向右移动指定的位数。右移操作会在左侧添加0或者符号位,并丢弃右侧超出指定位数的位。右移操作可以看作是将一个数除以2的指定次幂。

例如,对于二进制数1010,进行左移和右移操作:

  • 左移2位(1010 << 2):结果为101000,相当于将1010乘以2的2次幂(4),即40。
  • 右移1位(1010 >> 1):结果为0101,相当于将1010除以2的1次幂(2),即5。
  • 左移n位,原二进制对应的10进制数乘以2^n次方
  • 右移n位,原二进制数对应10进制除以2^n次方

注意这个操作的二进制数是原数据的“补码”!!!

// 例如
// int a = 7;
// int b = a << 1;
// 00000000 00000000 00000111 7的补码
// 00000000 00000000 00001110 左移后的7的补码
// 00000000 00000000 00001110 左移后的原码 大小为14(10)

当我们对int a = -7进行左移时,我们就要进行原反补码的转换了……

另外,右移操作符分为两种情况

  • 算术移位:丢弃右边位,左边空缺位填原本的符号位
  • 逻辑移位:丢弃右边位,左边空缺填补0
// 将7左移
//   00000000 00000000 00000111
// 0 00000000 00000000 0000111 0  左移 左边删去一位 右边补0


将7右移
//   00000000 00000000 00000111
//  0 0000000 00000000 00000011 1  右移 左边补0或1 右边删去一位

 注意:

  • 左移的本质为由低地址向高地址,而不是单纯的向左移动。
  • 右移是从高地址往低地址

这里因为计算机分为大端机和小端机原因,他们左移的逻辑不同。

3.部分面试题

3.1.不创建新的变量,实现两个变量的交换

void swap(int &a, int &b)
{
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
}

 这里的原理:

  • 对于任意的整数num,恒存在 num 异或 num = 0
  • 任何num恒存在:num 异或 0 等于num

总结一下就是异或就有交换律和结合率

接下来我们用实际数据来显示:

int a = 5;    // 0101
int b = 3;    // 0011
a = a ^ b;    // a = 0101 ^ 0011 = 0110
b = a ^ b;    // b = 0110 ^ 0011 = 0101 = 3
a = a ^ b;    // a = 0110 ^ 0101 = 0011 = 5

// 我们用10进制
// a = 5 ^ 3;
// b = 5 ^ 3 ^ 3 = 5
// a = 5 ^ 3 ^ 5 = 3

3.2.求一个整数存储在内存中二进制中1的个数 

int countFunc(int a)
{
	int count = 0;
	int array[32] = { 0 };
	// 核心逻辑
	for (int i = 31; i > 0; i--)
	{
		if ((a & 1) == 1)
		{
			count++;
			array[i] = 1;
		}
		a = (a >> 1);
	}
	// 打印这个二进制数
	for (int i = 0; i < 32; i++)
	{
		if (i % 8 == 0 && i > 0)
			cout << " ";

		cout << array[i];
	}
	cout << endl;
	return count;
}

原理:

  • 如果一个数num的二进制最后一位为1,那么 num&1 = 1,否则为0
  • 我们判断一个数有多少个1,就是不断的挪动这个数二进制的每一个位置,不断&1

总结一下我们就只需要不断的判断与1是否为1,是的话就计数,每次循环结束前,右移一位

 实际数据:

// a = 5
// 00000000 00000000 00000101

// 1
// 00000000 00000000 00000001

// a & 1 = 1
// 00000000 00000000 00000001  

// a = (a>>1)
// 00000000 00000000 00000010

// a & 1 = 0
// 00000000 00000000 00000000  

 3.3.有40亿个无序且不重复的无符号的整数,如何快速判断这个数是否在那40亿个数当中

题目解析:40亿个数据,我们如果存储在普通的数据结构中,遍历的话时间复杂度就是O(logN)到O(N),这个时间消耗是非常大的,而且需要开辟大量的空间,并且这些数据也是无序的,寻常的方法就需要先排序后遍历!

方法一:二分查找,我们通过二分查找的时间复杂度是O(logN),也就是2^N = 40亿,很容易解出来N=32次,也就是我们只用32次二分查找就能找到这个数字,这时空间消耗也是特别大的,计算如图:

这种方法内存消耗过大,因而不太合适

方法二:通过比特位映射,我们知道一个bit表示0或者1,我们可以通过40亿个比特位映射40亿个数据,如果这个数据存在,那我们就在40亿个比特位中对应的那个位置计为1,反之计为0. 接着我们通过位运算符来进行操作实现判断!!!我们再来计算一下空间消耗:

因此第二种方案是可行的!!!那么接下来我们对第二种方法进行实现:

 原理:

当我们抽象出这个模型时大概就知道怎么操作了!

  1. 因为0到2^32次方比特位,对应0到2^32次方个数,当我们需要查找一个数num时我们如何查找?首先每个整型表示着32个比特位,对于0来说他在1*32中,对于1来说他来2*32中,那么对于num来说他在num/32个位置
  2. 当我们查找到num在第几个整型时,因为一个数据映射在对应整型位置的32个比特位中的一个上,那么在第几个我们就可以通过 num%32来找到
  3. 我们通过1,2就能找到任意一个数的映射位置了,因为1个比特位表示着0或1,我们将这个位置设为1表示存在,设为0表示不在。需要只把这一个2^32个比特流的第num/32个整型的第num%32个位置设为1.(只改一个位置)

 将原理实现一下:

namespace zhong
{
	// N为非类型模版参数
	template<size_t N>
	class bitset
	{
	public:
		// N为比特位个数
		bitset()
		{
			// 开辟空间大小,所有整型均设为0
			_bits.resize(N / 32 + 1, 0);
		}

		// 把对应比特位的数据置为1
		void set(size_t x)
		{
			// 找到数组中的第几个整型
			size_t i = x / 32;
			// 找到这个整型下的第几个比特位
			size_t j = x % 32;
			// 将1左移j位置,到达映射位,通过 或 可以把这一位变为1其他位不变
			// 因为任何数 或1 都是 1, 1左移j位其他位置全为0,不会对其他位影响
			_bits[i] |= (1 << j);
		}
		// 删除这个值
		void reset(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			// 这里我们通过取反,使得第j位为0,其他位为1
			// 接着与一下,把对应映射的第j位变成0,其他位置因为是1,与 后均为本身
			_bits[i] &= ~(1 << j);
		}
		// 查找这个值在不在
		bool test(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			// 将1左移到对应位置与一下就好了

			if (_bits[i] & (1 << j))
				return true;
			else 
				return false;
		}

	private:
		vector<int> _bits;
	};
	

}
void test()
{
	zhong::bitset<100> bs;
	bs.set(66);
	bs.set(88);
	cout << bs.test(66) << endl;
	cout << bs.test(88) << endl;
	bs.reset(88);
	cout << endl;
	cout << bs.test(66) << endl;
	cout << bs.test(88) << endl;
}

我们在STL库中有位图这一个容器,底层实现原理如上所述。

我们在C++学习进阶:哈希思想的进一步体现-CSDN博客 中会对具体实现进行讲解。

这道题的代码:

void find_mass_data()
{
	// 文件操作将40亿个数据写入array中
	// 这里仅做模拟
	int array[] = {111,33,21,45};

	// 开辟42亿个比特位的位图
	bitset<0xffffffff> bs;

	for (auto e : array)
	{
		// 通过set实现映射
		bs.set(e);
	}

	int x = 9999999999;
	// 实现O(1)复杂度查找
	bool ifExit = bs.test(x);

}

注意这一块代码也只是模拟实现而已,具体思路就是这样,具体的解答应该根据具体场景……

  • 22
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值