目录
1.进制与原反补码
2.位运算
2.1.按位与
2.2.按位或
2.3.异或
2.4.取反
2.5.移位
3.部分面试题
3.1.不创建新的变量,实现两个变量的交换
3.2.求一个整数存储在内存中二进制中1的个数
这一部分本来是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.移位
左移(<<):将一个二进制数的所有位向左移动指定的位数。左移操作会在右侧添加0,并丢弃左侧超出指定位数的位。左移操作可以看作是将一个数乘以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