目录

二进制数与位运算

原码、反码、 补码

在计算机中,用来表示有符号数的机器数有三种,即原码、反码、补码,三种表示方法均有“符号位” 和“数值位”两部分

符号位都是占据最高位,用0表示 '正数' ,用1表示'负数' 
数值位,三种表示方式各不相同
正数:原码、反码、补码都一样
真值:3
原码:0000 0011 最高位为0,表示正数
反码:0000 0011
补码:0000 0011
负数:原码、反码、补码不一样
真值:-3
原码:1000 0011 最高位为1表示负数
反码:1111 1100 由原码转化,原码的符号位不变,数值位全部取反
补码:1111 1101 由反码的基础上+1得到

在计算机系统中,数值一律用补码来存储! 主要原因:使用补码,可以将符号位和其他位统一处理;同时减法也可按加法来处理(如2-1等于2+(-1)) 需要注意的是两个补码表示的数相加时,如果最高位(符号位)有进位,则会把进位舍弃,处理完成后我们如上面所示用补码反推出真值即可

举个栗子
第一步:真值->原码->反码->补码
真值:8
原码:0000 1000
反码:0000 1000
补码:0000 1000
真值:-3
原码:1000 0011
反码:1111 1100
补码:1111 1101

第二步:补码之间的运算,此处为相加
8的补码:0000 1000
-3的补码:1111 1101
相加得补码:0000 0101  # 补码相加,高位有进位会被舍弃

第三步:补码->反码->原码->真值
上一步得到的补码结果:0000 0101
符号位是0,为正数,那么就简单了,正数的原、反、补码都一样,所以一步到位
补码->反码->原码:0000 0101
原码->真值:5

位运算符

  1. 按位与&:两位全为1,结果才为1,否则为0
  2. 按位或|:两位只要存在一个1,结果就为1,否则为0
  3. 按位异或^:只有在两位不相同,即一个为0一个为1的情况下,结果才为1,否则为0
  4. («) n:各二进制位全部左移n位,高位丢弃,低位补0
  5. (») n: 各二进制位全部右移n位,如果是正数,则高位补0,如果是负数则高位补1

按位与&

位与(&)是一个双目运算符,也就是说&符号的左右各有一个操作数。它是把两个操作数在二进制的形式上按位进行比较

按位与&:两位全为1,结果才为1,否则为0

示例1:8  &  -3
计算8&-3
第一步:真值>原码>反码>补码
真值:8
原码:0000 1000
反码:0000 1000
补码:0000 1000
真值:-3
原码:1000 0011
反码:1111 1100
补码:1111 1101

第二步:补码之间的运算,此处为&
8的补码:0000 1000
-3的补码:1111 1101
&得补码:0000 1000

第三步:补码>反码>原码>真值
上一步得到的补码结果:0000 1000
符号位是0,为正数,那么就简单了,正数的原、反、补码都一样,所以一步到位
补码>反码>原码:0000 1000
原码->真值:8
示例2:-9&-8
计算-8&-9
第一步:真值>原码>补码
真值:-8
原码:1000 1000
反码:1111 0111
补码:1111 1000
真值:-9
原码:1000 1001
反码:1111 0110
补码:1111 0111

第二步:补码之间的运算,此处为&
-8的补码:1111 1000
-9的补码:1111 0111
&得补码:1111  0000

第三步:补码>反码>原码>真值
补码->反码
补码结果:1111 0000
符号位是1,为负数,参照上面的步骤
补码>反码:-1,得到反码:1110  1111
反码>原码:符号位不变,其余位取反,得到原码:1001 0000
原码>真值:-16

应用

  • 逻辑与(&&)的替代使用: 逻辑与(&&)有一个特点就是短路,而位与(&)就没有短路的效果

    当需求不管第1个表达式的结果如何,都必须要运算第2个表达式的时候就可以用 & 取代 &&

  • 利用 & 运算符的特性,来判断二进制数第一位是0还是1 用/if ((a & 1)==0)/代替/if (a % 2 == 0)/来判断a是不是偶数。

按位或 |

位或(|)也是一个双目运算符。它是把两个操作数在二进制的形式上按位进行比较

按位或|:两位只要存在一个1,结果就为1,否则为0

示例1:-8|-9
计算-8|-9
第一步:真值>原码>补码
真值:-8
原码:1000 1000
反码:1111 0111
补码:1111 1000
真值:-9
原码:1000 1001
反码:1111 0110
补码:1111 0111

第二步:补码之间的运算,此处为|
-8的补码:1111 1000
-9的补码:1111 0111
|得补码:1111 1111

第三步:补码>反码>原码>真值
补码>反码
补码结果:1111 1111
符号位是1,为负数,参照上图2的步骤
补码>反码:-1,得到反码:1111 1110
反码>原码:符号位不变,其余位取反,得到原码:1000 0001
原码>真值:-1

应用

  • 逻辑或 || 的替代 使用位或 | 操作boolean表达式的时候,效果与逻辑或 || 一样,且没有短路效果

按位异或^

按位异或^:只有在两位不相同,即一个为0一个为1的情况下,结果才为1,否则为0

示例1:-8 ^ -9
第一步:真值>原码>补码
真值:-8
原码:1000 1000
反码:1111 0111
补码:1111 1000
真值:-9
原码:1000 1001
反码:1111 0110
补码:1111 0111

第二步:补码之间的运算,此处为^
-8的补码:1111 1000
-9的补码:1111 0111
^得补码:0000 1111

第三步:补码>反码>原码>真值
上一步得到的补码结果:0000 1111
符号位是0,为正数,那么就简单了,正数的原、反、补码都一样,所以一步到位
补码>反码>原码:0000 1111
原码>真值:15
示例2:^ -8  单独一个^代表取反的意思
计算:^ -8
第一步:真值>原码>补码
真值:-8  原码:1000 1000
反码:1111 0111
补码:1111 1000

第二步:
-8的补码:1111 1000
^取反得补码:0000 0111

第三步:补码>反码>原码>真值
上一步得到的补码结果:0000 0111
符号位是0,为正数,那么就简单了,正数的原、反、补码都一样,所以一步到位
补码>反码>原码:0000 0111
原码>真值:7

应用

  • 异或(^)运算有两个非常有趣的结论:
    • 任何一个数异或(^)它本身,结果是0;
    • 任何一个数异或(^)0,结果是它本身。

向左位移« n

« n:各二进制位全部左移n位,高位丢弃,低位补0

示例1:-8  <<  3
计算-8<<3
第一步真值>原码>补码
真值:-8
原码:1000 1000
反码:1111 0111
补码:1111 1000

第二步:<< n 各二进制位全部左移n位高位丢弃低位补0
补码1111 1000
<<3: 1100 0000

第三步补码>反码>原码>真值
补码->反码
补码结果:1100 0000
符号位是1,为负数,参照上图2的步骤
补码->反码:-1,得到反码:1011 1111
反码->原码:符号位不变,其余位取反,得到原码:1100 0000
原码->真值:-64

应用

  • 将一个数左移(«)n位,相当于乘以了2的n次方。这种运算在效率和性能上都比算术运算中的要高很多。

向右位移(») n

(») n: 各二进制位全部右移n位,如果是正数,则高位补0,如果是负数则高位补1

示例1:-8  >>  3
计算-8>>3
第一步:真值>原码>补码
真值:-8
原码:1000 1000
反码:1111 0111
补码:1111 1000

第二步:>> n 各二进制位全部右移n位,如果是正数,则高位补0,如果是负数则高位补1
补码:1111 1000
>>3: 1111 1111

第三步:补码>反码>原码>真值
补码->反码
补码结果:1111 1111
符号位是1,为负数,参照上图2的步骤
补码->反码:-1,得到反码:1111 1110
反码->原码:符号位不变,其余位取反,得到原码:1000 0001
原码->真值:-1
示例2:8  >>  3
计算8>>3
第一步:真值>原码>补码
真值:8
原码:0000 1000
反码:0000 1000
补码:0000 1000

第二步:>> n 各二进制位全部右移n位,如果是正数,则高位补0,如果是负责则高位补1
补码:0000 1000
>>3: 0000 0001

第三步:补码>反码>原码>真值
上一步得到的补码结果:0000 0001
符号位是0,为正数,那么就简单了,正数的原、反、补码都一样,所以一步到位
补码>反码>原码:0000 0001
原码->真值:1
示例3:-300  >>  8
计算-300>>8
第一步:真值>原码>补码
真值:-300
原码:1000 0001 0010 1100 # -300 已经超过了8位二进制能表示的范围,需要用16位表示
反码:1111 1110 1101 0011
补码:1111 1110 1101 0100

第二步:>> n 各二进制位全部右移n位,如果是正数,则高位补0,如果是负责则高位补1
补码:1111 1110 1101 0100
>>8: 1111 1111 1111 1110

第三步:补码->反码->原码->真值
补码>反码
补码结果:1111 1111 1111 1110
符号位是1,为负数,参照上图2的步骤
补码>反码:-1,得到反码:1111 1111 1111 1101
反码>原码:符号位不变,其余位取反,得到原码:1000 0000 0000 0010
原码->真值:-2

应用

  • 将一个数右移n位,相当于除以2的n次方取整。这种运算在效率和性能上都比算术运算中的要高很多。

    注意: 右移正整数会有除法的效果,但是右移负整数得到的结果会比除法的效果要小一个数。

    13»2得到3

    13/4得到3

    -13»2得到-4

    -13/4得到-3

    这是因为计算机在除不尽的时候统统采用的是向下取整,而我们人则习惯于直接去掉小数部分

& 0xff 的作用

首先我们都知道,& 表示按位与,只有两个位同时为1,才能得到1,0x代表16进制数,0xff数的二进制 1111 1111 占一个字节。与其进行 & 操作的数,最低的8位不会发生变化。

下面说说&0xff的应用:

为了取得低八位

通常配合移位操作符 » 使用

例如:有一个数字0x1234,如果只想将低8位写入到内存中 0x1234&0xff

0x1234 表示为二进制 0001 0010 0011 0100

0xff 表示为二进制 1111 1111

两个数做与操作,显然将0xff补充到16位,就是高位补0

此时0xff 为 0000000011111111

与操作 1&0 =0 1&1 =1 这样 0x1234只能保留低八位的数 0000000000110100 也就是 0x34