目录
2、整数转换。编写一个函数,确定需要改变几个(位才能将整数A转成整数B。
3、写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
一、什么是二进制?
在日常生活中,我们对“十进制”的应用及其广泛。我们都知道,十进制的进位方式为——满“10”进1。例如:09+01=10(为了方便理解,我在9前加了一位0来显示进位结果,上式即:9+1=10)
同样的,二进制亦是如此,进位方式为满“2”进“1”。例如,为方便,此处给2个4位二进制数字:0001、0001。我们对其进行相加运算。如下图:
二、原码、反码、补码
计算机中的整数有三种2进制表示方法,即原码、反码和补码。数据在内存中的存储方式为补码。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位
正数的原、反、补码都相同。
负整数的三种表示方法各不相同。
(32位操作系统下)
1、原码
直接将数值按照正负数的形式翻译成二进制就可以得到原码。
2、反码
将原码的符号位不变,其他位依次按位取反就可以得到反码
3、补码
反码(最低位)+1 就得到补码。
int a = 10;
//00000000 00000000 00000000 00001010 原码、反码、补码
//正数的原码、反码、补码相同
int b = -10;
//10000000 00000000 00000000 00001010 原码
//11111111 11111111 11111111 11110101 反码
//11111111 11111111 11111111 11110110 补码
注:在32位操作系统下,一个整型数据在内存中占4个字节,1个字节=8个比特位。上述代码中的补码即为整型数据在内存中的存储方式。二进制序列的第一位为此数的符号位,0表示“正”,用1表示“负”。
三、位运算符
注:此处为便于展示,示例均采取正整数,且仅取后8个比特位。
注:操作数必须为整数。
1、按位与(&):将两个操作数的对应位进行逻辑与操作。当两数对应的二进制数相同位次都为1时,结果位就为1,否则为0。
1&1=1
1&0=0
0&1=0
0&0=0
a = 0000 0001 // a=1
b = 0000 0011 // b=3
a&b= 0000 0001 // a&b=1
2、按位或(|):将两个操作数的对应位进行逻辑或操作。当两数对应的二进制数相同位次其中一个为1时,结果位就为1。当两数对应的二进制位都为0时,结果位就为0。
1|1=1
1|0=1
0|1=1
0|0=0
a = 0000 0001 // a=1
b = 0000 0011 // b=3
a|b= 0000 0011 // a|b=3
3、按位异或(^):将两个操作数的对应位进行逻辑异或操作。当两个数对应的二进制数相同位次数字不同时,结果位为1。相同时,结果位为0。
1^1=0
0^0=0
1^0=1
0^1=1
a = 0000 0001 // a=1
b = 0000 0011 // b=3
a^b= 0000 0010 // a^b=2
4、按位取反(~):将操作数的每个位进行取反操作,即0变为1,1变为0。在运算时,仅对非符号位产生运算。符号位不会发生改变。同样,当符号位为1的二进制数字转换为十进制时,符号位也不参与计算。
a = 00000000 00000000 00000000 00000001 // a=1
~a = 00000000 00000000 00000000 00001110 // a=14
a = 10000000 00000000 00000000 00000110 // a=-6
~a = 11111111 11111111 11111111 11111001
5、按位左移(<<):将左操作数的二进制位向左移动指定的位数,右边空出的位用0填充。例如,x << n 将x的二进制位向左移动n位。
a = 00000000 00000000 00000000 00000001 // a=1
a<<2 = 00000000 00000000 00000000 00000100 // a=4
6、按位右移(>>):将左操作数的二进制位向右移动指定的位数。x >> n 将x的二进制位向右移动n位。按位右移分为算数右移和逻辑右移。
1、算术右移:在使用无符号数时,右边空出的位用0填充;在使用有符号数时,右边空出的位用原符号位填充。最高位仍是符号位。(多数编程语言)
a = 00000000 00000000 00000000 00001111 // a=15
a>>2 = 00000000 00000000 00000000 00000011 // a=3
a = 10000000 00000000 00000000 00001111 // a=-15
a>>2 = 11100000 00000000 00000000 00000011
2、逻辑右移:使用逻辑右移运算符时,右边空出的位会用零填充。
a = 00000000 00000000 00000000 00001111 // a=15
a>>2 = 00000000 00000000 00000000 00000011 // a=3
a = 10000000 00000000 00000000 00001111 // a=-15
a>>2 = 00100000 00000000 00000000 00000011
四、位运算例题讲解
1、不创建临时变量(第三个变量),实现两个数的交换。
#include <stdio.h>
int main()
{
int a = 10; // a=00000000 00000000 00000000 00001010
int b = 20; // b=00000000 00000000 00000000 00010100
a = a^b;
b = a^b; // b = a^b^b = a^0 = a
a = a^b; // a = a^b^a = a^a^b = 0^b = b
printf("a = %d b = %d\n", a, b);
return 0;
}
通过运算可知:a^a=0;
a^0=a;
b同理。
补充:利用此方法,我们还可以求一组数中出现次数为奇数次数的数字。
eg:
这题我们就可以使用上面的结论,采用 按位异或(^) 符号
我们知道异或运算的性质:
a^a=0;
a^0=a;
a^b^a=b;
且其运算满足数学中的 ”交换律“——b^a = a^b;
所以我们可以设一个初始值key=0,将key逐个与数组元素进行按位异或操作。最终出现次数为偶数次的元素相互抵消,最终只剩下出现次数为奇数次的元素。
#include <stdio.h>
int main() {
int n,key=0,arr;
int i;
scanf("%d",&n);
for(i=0;i<n;i++)
{
scanf("%d",&arr);
key^=arr;
}
printf("%d",key);
return 0;
}
2、整数转换。编写一个函数,确定需要改变几个(位才能将整数A转成整数B。
示例1:
A = 29 = 00000000 00000000 00000000 00011101
B = 15 = 00000000 00000000 00000000 00001111
可以发现,在这两个数字的二进制数中,有2个位置对应的数不一样。此时我们采用 按位异或(^)符号,将对应位次不同的数转换为结果位为1的二进制数,即:
unsigned int dif = A&B = 00000000 00000000 00000000 00010010
我们再将dif每次向右移动一位并与数字1的二进制数进行按位与(&)的操作,即计算dif的二进制数中有多少个1,就可以得出需要改变的位数。
int convertInteger(int A, int B) {
int count = 0;
unsigned int dif = A ^ B;
while (dif != 0) {
if (dif & 1) {
count++;
}
dif >>= 1;
}
return count;
}
3、写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
int Add(int num1, int num2 ) {
while (num2 != 0) {
int sum1 = num1 ^ num2;
int sum2 = (num1 & num2) << 1;
num1 = sum1;
num2 = sum2;
}
return num1;
}
第一次循环:
num1 = 1 = 00000000 00000000 00000000 00000001
num2 = 3 = 00000000 00000000 00000000 00000011
num1^num2= 00000000 00000000 00000000 00000010 = sum1
num1&num2 = 00000000 00000000 00000000 00000001
(num1&num2)<<1= 00000000 00000000 00000000 00000010 = sum2
我们可以看到,通过对两数进行按位异或(^)的操作,可以完成不进位的计算。
num1^num2 即:
1+1=0,高位进1 1^1=0 需进位,结果位为0
1+0=1 1^0=1 不进位
0+1=1 0^1=1 不进位
0+0=0 0^0=0 不进位
num1^num2= 00000000 00000000 00000000 00000010 = sum1
对两数进行按位与(&)并向 左(高位) 移动一位的操作,可以完成进位运算。
(num1 & num2) << 1 即:
1+1=0,高位进1 1&1=1 需进位 <<1,左移一位即表示为高位进1
1+0=1 1&0=0 不进位
0+1=1 0&1=0 不进位
0+0=0 0&0=0 不进位
此时我们得到结果位,并将为结果位的二进制数向左移动一位,即完成进位操作
(num1&num2)<<1= 00000000 00000000 00000000 00000010 = sum2
当num2不为0时,我们将不进位相加的结果与进位相加的结果继续进行按位异或(^)操作,即
num1 = sum1;
num2 = sum2;
num1 ^ num2 = 00000000 00000000 00000000 00000100 = 4 = sum1
(num1&num2)<<1= 00000000 00000000 00000000 00000000 = 0 =sum2
此时num2=sum2=0,运算、循环结束。
本题的关键在于理解进位运算与非进位运算的方法。建议小伙伴们自行将数带入程序进行计算,这样可以更好的理解程序的运算方法。
小结
了解二进制有助于我们了解计算机基础的运算方式。二进制对于计算机处理数据也是极为重要的。位操作符则为我们处理二进制数据提供了可行性。