1.原码,反码,补码
- 按照一个数的正负,直接写出它的二进制表示形式就是原码
- 正数的原码、反码、补码是相同的
- 负数的原码、反码、补码要经过计算的
- 反码:原码的符号位不变,其他位按位取反
- 补码:反码+1
- 内存中存储的形式是:补码的二进制,所以在参与移位时,移动后都是补码
int n = 10
,整形占四个字节(32bit)
00000000000000000000000000001010 原码
00000000000000000000000000001010 反码
00000000000000000000000000001010 补码
int n = -10
10000000000000000000000000001010 原码
111111111111111111111111111111110101 反码
111111111111111111111111111111110110 补码
原码、反码、补码相互转换图:
这里可以看出:原码到补码与补码到原码的操作实际上是一样的
2.移位操作符
左移操作符 <<
左移操作符:将补码向左移动,在右补0
要注意:b = a<<1
左移运算符不是直接将a
左移,而是将左移后的值保存到b
中,a
的值是不变的
如果想让a
的·值变:a = a<<1
或a<<=1
int main()
{
//对正数的左移
int a = 10;
int b = a << 1;
printf("%d\n", a);
printf("%d\n", b);
}
00000000000000000000000000001010 -10的补码,进行左移
左移后的补码:00000000000000000000000000010100
输出结果:
int main()
{
//对负数的左移
int a = -10;
int b = a << 1;
printf("%d\n", a);
printf("%d\n", b);
}
a的补码:111111111111111111111111111111110110
111111111111111111111111111111101100 -b的补码
10000000000000000000000000010100 -b的原码
输出结果:
从10与-10的左移一位的结果中,可以看出左移一位有扩大2倍的作用
右移操作符 >>
右移操作符分为两种
1.算术右移(常见):右边丢弃,左边补原来的符号位
2.逻辑右移:右边丢弃,左边直接补0
右移的方式取决于编译器,但是常见的右移是算术右移
对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:
int num = 10;
num>>-1;//error
3.位操作符
位操作符有:
& -按位与
| -按位或
^ -按位异或
它们的操作数必须是正数,并且针对二进制计算
按位与&
对应的二进制有0就为0,只有对应的两个都为1时,才为1
int main()
{
int a = 3;
//00000000000000000000000000000011 a的补码
int b = -5;
//10000000000000000000000000000101
//11111111111111111111111111111010
//11111111111111111111111111111011 b的补码
int c = a&b;
//00000000000000000000000000000011 a的补码
//11111111111111111111111111111011 b的补码
//00000000000000000000000000000011 c的补码
}
按位或 |
对应的二进制位,有1则位1,两个同时为0则为0
int main()
{
int a = 3;
//00000000000000000000000000000011 a的补码
int b = -5;
//10000000000000000000000000000101
//11111111111111111111111111111010
//11111111111111111111111111111011 b的补码
int c = a|b;
//00000000000000000000000000000011 a的补码
//11111111111111111111111111111011 b的补码
//11111111111111111111111111111011 c的补码
}
按位异或 ^
对应的二进制位:相同位0,相异为1
int main()
{
int a = 3;
//00000000000000000000000000000011 a的补码
int b = -5;
//10000000000000000000000000000101
//11111111111111111111111111111010
//11111111111111111111111111111011 b的补码
int c = a^b;
//00000000000000000000000000000011 a的补码
//11111111111111111111111111111011 b的补码
//11111111111111111111111111111000 c的补码
}
异或操作赋一些特点:
1.a^a = 0
2.0^a = a
3.异或支持交换律
与^有关的变态面试题
不能创建临时变量(第三个变量),实现两个数的交换
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a^b;
b = a^b;
a = a^b;
printf("a = %d b = %d\n", a, b);
return 0;
}
首先让
a = a^b
,接着b = a^b
,其实就是b = a^b =a ^ b ^b = a
,a = a^ b = a^b ^a
,也就完成了交换
4.几道例题
这里,我们先想一下怎么得到二进制数字中的最后一个数字?
我们想一下求十进制数字的每一位:以11为例,11%2 = 1得到个位数字,11/2得到取出个位后的数字
其实二进制求每一位上数字一样
使用取模运算,除法运算是一种方法,同时也可以使用操作符进行计算
实际上,用1与其他数字做按位与&就可以得到最后以为二进制数字,因为1的二进制最后一位为1,其余为0,&的规则是:有0就为0,都是1才为1,所以得到的数字前面各位都为0,而最后一位由原数字决定,它最后一位为0,结果就为0.为1,结果就为1
接下来,为了得到去掉最后一位的剩余数字,只需把数字向右移动1即可11>>1
前面的基础比较容易理解,但是在做题时有的方法我们仍难以想到,所以我们接下来看几道题理解理解
1.求一个数的二进制形式中有多少个1
- 使用前面的取模除法运算:
//二进制中1的个数
int NumberOf1(int n)
{
int count = 0;
while (n)
{
if (n % 2 == 1) //如果最后一位为1.count+1
{
count++;
}
n = n / 2;
}
return count;
}
这里的循环条件位
n>o
因为只要不是数字0,其余数字的二进制数字中都会有1,所以n>0时进行循环,如果退出循环来,就说明剩下的数字中一定没有1
- 使用
&
,>>
int NumberOf1(int n)
{
int count = 0;
while (n>0)
{
int a = n & 1;
if (a==1)
{
count++;
}
n = n >> 1;
return count;
}
前面两种算法计算正数都可以,但是负数就计算不了,负数连while
循环都进不去,所以我们不能使用n>0
作为循环条件
不论正数还是负数,都是4个字节大小,32个比特位,所以我们需将二进制循环遍历32次求出每位上的数字即可
int NumberOf1(int n)
{
int i = 0;
int count = 0;
for (i = 0; i < 32; i++) //循环32次,求出每位上的数字
{
if (((n >> i) & 1) == 1)
{
count++;
}
}
return count;
}
((n >> i) & 1)
,将n
右移i
次,i
从0开始,每次加一,所以n
从右移1位开始,每次多移一位,因为左右移操作符不会改变原来n
的值,所以每次向后移动i
位,就巧妙地得到了i
位上的数字,在&1
就能得到每位上的数字
这里有一个几乎想不到的求法,用n = n&(n-1)
求
所以计算n = n&(n-1)
使用的次数,就可以得出1的个数
int NumberOd1(int n)
{
int count = 0;
while(n)
{
n = n&(n-1);
count++;
}
return count++;
}
2.获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列
void print(int n)
{
int arr[32] = { 0 };
printf("奇数位:");
for (int i = 1; i < 32; i+=2)
{
printf("%d ", (n>>i)&1);
arr[i] = (n >> i) & 1;
}
printf("\n");
printf("偶数位:");
for (int i = 0; i < 32; i += 2)
{
printf("%d ", (n >> i) & 1);
arr[i] = (n >> i) & 1;
}
printf("\n");
for (int i = 31; i >=0; i--)
{
printf("%d", arr[i]);
}
}
这里的原理其实和上一题一样,只是分为奇数和偶数,这里仍是对32位都遍历,只是每次
i
增加2
i
从1开始,每次增加2,这就是在遍历奇数位
i
从0开始,每次增加2,这就是在遍历偶数位
3.两个int(32位)整数m和n的二进制表达中,有多少个位(bit)不同?
int compare(int a, int b)
{
int count = 0;
for (int i = 0; i < 32; i++)
{
if (((a >> i) & 1) != ((b >> i) & 1))
{
count++;
}
}
return count;
}
这里也是,用(n>>i)&1得到两个数字的各个位,然后进行比较,看是否相等
其实这里也可以使用按位异或操作符
^
,如果对应每位相同位0,相异为1,在去求a^b
中1的个数
int compare(int a, int b)
{
//也可以用异或解决
int tmp = a ^ b;
//统计tmp里1的个数
int count = 0;
for (int i = 0; i < 32; i++)
{
if ((tmp >> i) & 1 == 1)
{
count++;
}
}
return count;
}
最后,其实在二进制的一些运算中,十分有趣,不时会让人感觉妙啊·~~