1.预备知识:原码、反码、补码
原码、反码、补码是3种整数二进制表示方式。计算机中有规定:正整数三种类型均相同、负整数三种类型各不相同。但是要注意的一点的是:三种表示方式都是32位的,无意义的位数用数字“0”来填充。
1.1正整数原码、反码、补码
对于正整数,三种表示方式都是直接把十进制数进制转换为二进制数。例如:十进制数10的原码、反码、补码均为 00000............0001010(一共有32位)。
1.2负整数原码、反码、补码
1.2.1原码
负整数的原码与正整数的区别在于最高位数变成了“1”,因此,原码的最高位就可以看做是数字的“符号位”(1为负数、0为正数)。举10与-10的例子:
1.2.2反码
除了负整数的符号位,其余位数全部倒转(0变1,1变0):
1.2.3补码
补码=反码+1.
2.移位操作符
2.1左移操作符
左移操作符“<<”,先看以下代码:
int main()
{
int a = 3;
a = (a << 1);
printf("%d", a);
return 0;
}
输出结果为 6.
左移操作符的规律是:二进制数的所有位数向左移动x位,超过32位的位数舍去,右边不足的位数用0补上。以上面这个例子来说:
同时,我们发现左移操作符每操作一次就会使二进制数中的所有“1”向高位移动一位,这个作用的效果就是操作后的数是操作前的数的两倍。( a>>x=(2^x)*a )
2.2右移操作符
右移操作符“>>”
一般来说,右移操作符的含义是:所有二进制位向右移动x位,最右边多余的位数舍去,但最左边的符号为不改变(是1还是1,不会用0覆盖)。
3.位操作符
位操作符是针对两个数或者一个数进行操作的。要注意的一点是:我们创建的整型变量,存放在内存中存放的其实是数据的补码形式(也就是说数据先加工为二进制原码再转变为补码存入内存中)
位操作符有4种类型:&(按位与)、|(按位或)、^ (按位异或)、~(按位取反),用法如下: &..........................(有0则0,两个1才能为1) |............................(有1则1,两个0才能为0) ^...........................(相同为0,相异为1) ~...........................(所有0变1,1变成0)
我们可以用一些数字对这些操作符进行简单的测试:(前三种操作符都是两个数字,按位取反是一个)
有兴趣的同学可以手算一下,原码与补码之间的换算都是取反+1,也就是说:原码取反+1=补码;补码取反+1=原码。
4.题目举例
经过上述学习,我们初步了解到了移位操作符与位操作符的基本作用,现在我们通过一些题目更加深入地了解一下这些操作符的作用。
(题目1:不创建第三个变量实现两个整数的交换)
int main()
{
int a = 6;
int b = 4;
printf("a=%d\n", a);
printf("b=%d\n", b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a=%d\n", a);
printf("b=%d\n", b);
return 0;
}
这串代码的输出结果如下:
简要讲解一下它的原理:a=a^b;相当于将ab间不同的位数存储在a中,b=a^b;此时a和b的差异又变成了最初始的a了。用公式来理解 b=(a^b)^b=a^b^b=a^(b^b)=a^0=a; 同理,最后一行a=a^b=(a^b)^a=a^a^b=(a^a)^b=0^b=b; 这样,a和b就成功地交换了。
(题目2:求一个整数存储在内存中的二进制数中1的个数)
int main()
{
int a = 0;
int count = 0;
while (1)
{
scanf_s("%d", &a);
for (int i = 0; i < 32; i++)
{
if (a & 1 == 1)
{
count++;
}
a = a >> 1;
}
printf("1的个数为:%d\n", count);
count = 0;
}
return 0;
}
输出结果如下:
原理是:将目标数与000........001(十进制1)按位与可以检测目标数最右边的数是不是1,如果是1的话就令count++。检测完之后,为了检测其他位数,再将目标数每次用右移操作符操作一次,这样可以丢弃检测完的数(因为二进制数有32位,所以写for循环重复32次即可)。