重学Android:从位运算到二进制表示(零)
前言
以下内容针对非科班同学,可以快速掌握位运算和二进制表示等计算机基础运算知识,看过源码的同学都知道,源码中大量运用了位运算知识,如果你对这方面不了解的话,看起来是比较困难的,如果你工作接触到蓝牙数据,串口数据收发等内容,这方面更是你必须了解的。
首先说下我自己,以前非科班出身,这方面内容几乎没了解过,但从实习开始领导直接告诉我让我负责蓝牙(用485通信的运动数据蓝牙模块)传输这方面工作(回头想想工作这几年一共接触到了不下于8种不同的蓝牙模块协议),就开始疯狂恶补这方面内容16进制,位运算,异或校验,校验和,进制转换…,越看越蒙,发现短期内并不能完全理解,于是就边学边开发,不知不觉就突然懂了,就是这么神奇,所以说如果前期看不懂没关系,看到能认识就行,再翻翻笔记理解一下,慢慢就懂了(经验之谈)!
说的有点多,直接开搂!
1. 二进制表达形式
1.1 什么是二进制?
计算机内部是通过二进制(0和1)来存储和处理数据的。每一位(bit)只能取两个值,0或1,而多个二进制位(bit)组合起来可以表示不同的数值。比如,8个二进制位可以表示的数值范围是00000000到11111111,即0到255,也代表一个字节。
1.1 高位低位?
我们或许在某些地方听说过,高几位和低几位之说,它们都是对于二进制而言诞生的,比如一个字节 8个bit 00010010 通常把左边的称为高位,右边的称为低位,可以叫高四位0001和低四位0010,这没什么讲的,大家了解就行。
2. 位运算概述
位运算是直接对二进制位进行操作的运算方式,常用于底层开发、性能优化和算法实现中。与普通的算数运算(加法、减法等)不同,位运算操作的是二进制位,速度极快,且适用于很多底层系统和嵌入式开发。常见的位运算包括:位与(&)、位或(|)、位异或(^)、位取非(~)、左移(<<)、右移(>>)以及无符号右移(>>>)。
2.1 位与(&)
位与(&)是一个二元运算符,它对比特位上的两个数字进行操作。当且仅当两个相应的位都是1时,结果才为1,否则为0。
举个栗子:
1101 (13)
& 1011 (11)
----------
1001 (9)
只有第二位和第四位都为1,所以结果是1001,即9。通常使用(&)提取相应位的值,比如获取一个字节的高4位 就 &0xF0
,获取低四位就 &0x0F
。
2.2 位或(|)
位或(|)也是一个二元运算符,它会将两个数字的对应位进行比较,只要其中一个位为1,结果就是1。
举个栗子:
1101 (13)
| 1011 (11)
----------
1111 (15)
只有第二位和第四位都为0时才是0,其它位只要有一个为1,结果就为1。所以结果是1111,即15。这个通常运用是将高位和低位相加得到一个值,比如一个值使用高四位存储数值另一个用低四位存储,那么使用(|)就能等到他俩的累加和。
2.3 位异或(^)
位异或(^)是一个二元运算符,两个位相同则结果为0,不同则结果为1。
举个栗子:
1101 (13)
^ 1011 (11)
----------
0110 (6)
这个的应用一般是计算数据帧的校验(异或校验),确保帧完整。
2.4 位取非(~)
位取非(~)是一个一元运算符,它对每一位进行反转操作,将0变成1,将1变成0。
举个栗子:
~1101 (13)
---------
0010 (-14) (假设使用4位表示,反转操作会影响符号位)
位取非对一个数的二进制表示进行反转。在计算机中,取非的结果通常会被解释为负数,因为它的符号位(最高位)发生了变化。
2.5 左移(<<)
左移运算符(<<)是将数字的二进制表示向左移动若干位,左移相当于将数值乘以2的幂。左移时,低位补0。
举个栗子:
00000001 (1)
<< 2
-----------
00000100 (4)
1左移两位后变成了4。每左移一位,相当于将数字乘以2。位运算由于是直接操作位,所以运算极快,现在知道为啥源码中这么常见了吧。
2.6 右移(>>)
右移运算符(>>)是将数字的二进制表示向右移动若干位,右移相当于将数值除以2的幂。右移时,符号位会根据符号来进行扩展。
举个栗子:
11100101 (-27)
>> 3
-----------
11111100 (-4)
-27右移3位后变成了-4。右移时,高位补充符号位(在负数情况下为1)。这时候肯定会有人疑惑包括我在内,就算保留符号位,右移三位也是10001100啊,为什么是11111100,这里就简单记住负数移位后的空缺位将用1填充,正数是0填充,这是因为负数一般用补码表示,马上会介绍。
这里只是个特殊的栗子讲解下,开发中最常用的是正数的右移,对于负数我几乎没碰到过。
2.7 无符号右移(>>>)
无符号右移(>>>)与带符号的右移不同,它不保留符号位,而是补充0到高位。
举个栗子:
11100101 (-27)
>>> 3
-----------
00011100 (28)
-27无符号右移3位后变成了28,因为无符号右移会用0补充符号位。
小结
运算符 | 符号 | 描述 | 示例 | 结果解释 |
---|---|---|---|---|
位与 | & | 如果对应位都是1,则结果为1;否则为0。 | 1101 & 1011 | 1001 (13 & 11 = 9) |
位或 | 丨 | 如果对应位中至少有一个1,则结果为1;否则为0。 | 1101 丨1011 | 1111 (13 | 11 = 15) |
位异或 | ^ | 如果对应位不同,则结果为1;如果相同,则为0。 | 1101 ^ 1011 | 0110 (13 ^ 11 = 6) |
位取非 | ~ | 对二进制表示的每一位数字进行取反。 | ~1101 | 0010 (假设操作在4位上,~13 = 2) |
左移 | << | 将二进制位左移指定的位数,低位补0。 | 0011 << 2 | 1100 (3 << 2 = 12) |
右移 | >> | 将二进制位右移指定的位数,高位根据符号位填充。 | 1110 >> 1 | 1111 (-2 >> 1 = -1,假设操作在4位上) |
无符号右移 | >>> | 将二进制位右移指定的位数,高位补0。 | 11100101 >>> 3 | 00011100 (-27 >>> 3 = 536870902,假设32位) |
3. 原码、反码与补码
在计算机中,负数是通过原码、反码和补码来表示的。这些方法影响着数值的存储方式及运算效率。了解这些概念是理解计算机如何处理负数的关键。
3.1 原码
原码是最简单的表示方法。它的最高位是符号位,0表示正数,1表示负数。其余位表示数字的大小。
举个栗子:
- +7的原码:00000111
- -7的原码:10000111
原码的优点是简单易懂,缺点是对于运算来说比较复杂,不方便进行加减运算,特别是当正负数混合时更为麻烦。
3.2 反码
反码同样最高位是符号位,0代表正数,1代表负数。其余位表示数值,与原码不同的是,负数的表示方法不同。负数的反码表示方法:将它的绝对值按位取反。
举个栗子:
- +7的反码:00000111
- -7的反码:11111000
反码的优点是对于加减运算来说比原码简单,特别是当正负数混合时更简单,但缺点是在进行乘除运算时会出现问题。
3.3 补码
补码是现代计算机最常用的负数表示方法。补码同样最高位是符号位,0代表正数,1代表负数。其余位表示数值,其表示方法不同于原码和反码。补码解决了反码的不足,使得加减法运算变得更加简单高效。负数的补码表示方法:将其绝对值的二进制表示取反,然后加上1。
举个栗子:
- +7的补码:00000111
- -7的补码:11111001
补码不仅能简化加减法运算,还能避免符号位带来的问题。
小结
类型 | 描述 | 正数示例 | 负数示例 | 特点 |
---|---|---|---|---|
原码 | 最高位为符号位,0表示正,1表示负,其余位表示数值。 | +7: 0000 0111 | -7: 1000 0111 | 简单,但正负数加减法操作复杂 |
反码 | 正数的反码与原码相同,负数的反码为其原码除符号位外各位取反。 | +7: 0000 0111 | -7: 1111 1000 | 解决了0的表示问题,简化了加减法 |
补码 | 正数的补码与原码相同,负数的补码为其反码加1。 | +7: 0000 0111 | -7: 1111 1001 | 计算机内部普遍使用,优化了运算效率 |
4. 最后
希望对你有帮助,一起努力,共同进步!
另外给喜欢记笔记的同学安利一款好用的云笔记软件,对比大部分国内的这个算还不错的,免费好用::wolai