[zzerX@blog ~ ]:

位运算 看这篇就够了

前言

😬不知道大家是不是像我之前一样一看到二进制丶十六进制就头大,更别说位运算了,也不知道位运算的作用是啥,索性花了一些时间整理出这些知识点。

"我们知道程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。
 在系统软件中,常常需要处理二进制位的问题。C语言提供了6个位操作运算符。这些运算符只能用于整型操作数,即只能用于带符号或无符号的char,short,int与long类型。"

而二进制有8位(最低)丶 16位 丶32位 丶64位甚至更多,我们要怎样进行 这令人愉悦的折磨呢 位运算呢?😇

X进制与二进制

既然位运算操作的是二进制数,就需要先把其他数转换成二进制. 如果你已经知道如何进制转换可以跳到位运算这段。

“首先看下十进制转二进制

125转换成二进制是01111101,计算方法很简单正十进制数除以二,得到的除以二,依次类推直到商为零时为止,然后在旁边标出各步的余数,最后倒着写出来,再根据实际高位补零,即除二取余,倒序排列,高位补零

/*  以下是计算过程

商/2  --------- 余数  */
125/2 --------- 1
62/2  --------- 0
31/2  --------- 1
15/2  --------- 1
7/2   --------- 1
3/2   --------- 1
1/2   --------- 1
 0

将余数倒置则是1111101,然后我们发现结果只有7位数,而二进制最低位是8位,所以进行高位补零之后得到他的最终结果01111101

前置知识:被除数÷除数=商··· ···余数 
例:       10  ÷ 3  =3... ... 1

二进制转X进制

知道了十进制转二进制,那你知道反过来该怎么算吗?

将二进制中的位数分别与对应的值相乘,然后相加得到的就为十进制

我们看到01111101这个二进制,它是一个八位数,从低位(最右边)开始计算,分别乘对应的值,最后相加,那如何区分一个二进制是正数还是负数呢?当然是看首位,首位为1为负数,首位为0为正数,这里的首位指的是二进制的头一个数,而不是位数的最低位(计算是从低位开始,判断正负则看首位)。

二进制: 0 1 1 1 1 1 0 1
第几位: 7 6 5 4 3 2 1 0 
      =(1*2^0)+(0*2^1)+(1*2^2)+(1*2^3)+(1*2^4)+(1*2^5)+(1*2^6)+(0*2^7)
      = 1+0+4+8+16+32+64+0
      = 125

这样就得出了结果125,然后首位为0则是正125

其他进制转二进制

8进制丶16进制 2进制等相互转换其实是很简单的,因为其有对应关系

8  = 2^3  (82)取一分三 (28)取三合一
16 = 2^4  (162)取一分四 (216)取四合一
32 = 2^5  (322)取一分五 (232)取五合一
...
//即二进制分组
 二进制           01111101
 分组(取四合一)   0111 1101
 计算对应数        7    D

负数转二进制

上面说的是正整数转二进制的方法,那么负数呢?
如果你已经知道了一个正十进制数的二进制结果,那么他的负十进制数的二进制结果则是:
二进制取反,然后对结果再加一

如何理解呢,我们已经算出125的二进制(8位情况下)表示是01111101,取反的意思则是0变成1,1变成0,结果是10000010,然后加一,而二进制逢二进一,所以得出得结果为10000011,所以-125的二进制在8位的显示中为10000011。
为什么要说在八位的显示中?
如果是在16位 32位有何不同?

你可能会发现一个问题,如果用10000011反过来计算十进制的话,会发现他是131 而不是-125,为什么会这样呢?
那是因为有符号无符号的表示方式不同,在java中byte,short,int,long都是有符号的,他们也有对应的位数和范围(以下^代表幂):

符号 占位 字节 范围
byte 8 1 -2^7 ~ 2^7-1(-128 ~ 127)
short 16 2 -2^15 ~ 2^15-1
int 32 4 -2^31 ~ 2^31-1
long 64 8 -2^63 ~ 2^63-1

当定义八位(byte)-125时它代表的八位二进制是10000011,当你反过来算时得到的值131>八位(byte)的范围,所以他并不能用byte表示。

byte a = 131; //错误 范围超出

所以当10000011用来表示八位二进制是-125
正确算法应该是把正数转负数的步骤逆向运算,正数转负数时我们是取反再加一,反过来当范围超需要表示为负数时减一取反才得出的结果才是正确的值,然后再根据首位为1是负数(转换之前的首位),为0是正数确定符号。

(byte)           1000 0011 = -125
(short)0000 0000 1000 0011 =  131
(short)1111 1111 1000 0011 = -125

以上就是关于各进制与二进制之间的转换了。

位运算

接下来我们看一下如何进行位运算,位运算到底有什么用呢?

" 大部分时候我们不会用到位运算,特别是码农时代,明了直接才更有用。但是位运算就没有用处了么?不是,位运算的作用大多体现在研究类运用。位运算生涩难懂,不过有个很明显的优势就是特别快。因为这是计算机看的语言,也是内部运算的语言."
*如果在平时编程当中如果正常的运算能更让人理解的话是没必要使用位运算表示的,强行使用无异于炫技罢了,代码是让人看的。

位取反(~)

位取反即之前负数转二进制所用的方式,即0变1,1变0

10001100
01110011

位与(&)

位与即如果两个位进行比较两位同时为1,结果才为1,否则结果为0
例如: 125 & 7

           125   &    7
 二进制: 01111101 & 00000111

位与比较:    
0 1 1 1 1 1 0 1
---------------
0 0 0 0 0 1 1 1
| | | | | | | |
× × × × × √ × √
| | | | | | | |
0 0 0 0 0 1 0 1

结果: 125&7 = 0000 0111 = 5

位或(|)

位或即如果两个位进行比较两位同时为0,结果才为0,否则结果为1
例如: 125 | 7

           125   |    7
 二进制: 01111101 | 00000111

位或比较:    
0 1 1 1 1 1 0 1
---------------
0 0 0 0 0 1 1 1
| | | | | | | |
√ × × × × × × ×
| | | | | | | |
0 1 1 1 1 1 1 1

结果: 125|7 = 0111 1111 = 7

异或(^)

位异或即如果两个位进行比较相同取0,不同取1
例如: 125 ^ 7 (java中^代表异或)

           125   ^    7
 二进制: 01111101 ^ 00000111

位异或比较:    
0 1 1 1 1 1 0 1
---------------
0 0 0 0 0 1 1 1
| | | | | | | |
√ × × × × √ × √
| | | | | | | |
0 1 1 1 1 0 1 0

结果: 125^7 = 0111 1010 = 122

异或的几条性质:

1、交换律
2、结合律 (a^b)^c == a^(b^c)
3、对于任何数x,都有 x^x=0,x^0=x
4、自反性: a^b^b=a^0=a;

编程算法中的作用:交换两个数(利用性质)

public void Swap(int  &a,  int  &b){  
  if  (a  !=  b){  
    a  ^=  b; //a=a^b
    b  ^=  a; //b=b^(a^b) = (b^b)^a = a 
    a  ^=  b; //a=(a^b)^a = (a^a)^b = b
        }  
}

右移(>>)

将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
125>>3

           125   >>    3
右移:    
0 1 1 1 1 1 0 1
---------------
0 0 1 1 1 1 1 0 |1     >> 1
0 0 0 1 1 1 1 1 |0 1   >> 2
0 0 0 0 1 1 1 1 |1 0 1   >> 3
^ ^ ^ 
正数补0负数补1
结果: 125>>3 = 0000 1111 = 15
等价与 125/23次方的商

左移(<<)

同样的,左移则是将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
125>>3

           125   >>    3
右移:    
      0 1 1 1 1 1 0 1
      ---------------
    0|1 1 1 1 1 0 1 0  << 1
  0 1|1 1 1 1 0 1 0 0  << 2
0 1 1|1 1 1 0 1 0 0 0  << 3
                ^ ^ ^0
结果: 125<<3 = 1110 1000 = -24(八位)       
      125<<3 = 1110 1000 = 1000(十六位)
相当:125*23次方(若左移时舍弃的高位不包含1)

无符号右移(>>>)

无符号右移则始终补0,不考虑正负数。

总结:

符号 描述 运算规则
~ 1变0,0变1
& 两个位都为1时,结果才为1
| 两个位都为0时,结果才为0
^ 异或 两个位相同为0,相异为1
>> 右移 各二进位全部左移若干位,高位丢弃,低位补0
<< 左移 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)
MENMORY TOAST >>>>>>

— May 27, 2021