二进制与位运算详解

目录

文章目录

一、什么是二进制?

二、原码、反码、补码

三、位运算符

四、位运算例题讲解

1、不创建临时变量(第三个变量),实现两个数的交换。

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,运算、循环结束。

本题的关键在于理解进位运算与非进位运算的方法。建议小伙伴们自行将数带入程序进行计算,这样可以更好的理解程序的运算方法。

小结

        了解二进制有助于我们了解计算机基础的运算方式。二进制对于计算机处理数据也是极为重要的。位操作符则为我们处理二进制数据提供了可行性。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这题怎么做?!?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值