基础理论,大佬请绕道
关于数据类型的转换网上其实简单的分类了隐式转换和显式转换,基本上JAVA、C#、C++都遵循这个语法逻辑,如果光讲这个恐怕连培训班出身的人都不如吧,虽然我没上过培训班,但是估计他们也讲过这里面的深层原理。
大家都知道(没错只有你不知道)数据是以二进制存储在计算机的外存、内存、Cache里的。大概是这样子:
关于计算机的存储结构有机会也可以讲一讲,保证不鸽。
如果说是以二进制存储代码,这里就存在问题了,假如说我写一段这样的代码:
我们期望的是有符号短整形x转换成无符号短整型y,y的值应该为4321,但是实际上却转换成了61215
但是若将x转换为正数,转换的结果又没有出现奇怪的变化,这是怎么回事呢?
但是呢,你懂了不代表别人懂啊。
没错,这就是今天的关键词:
回到正题,继续上面的问题。为什么short型 -4321转换成ushort型就变成了一个莫名其妙的数字呢?我们来分析分析。
首先,要明白怎么把十进制数转换成二进制数:
第一点,必须要知道成文规定数字有正负的时候最高位的1和0分别代表负数和正数。
比如-1是11,1是01,-2是110,2是010。
第二点,就是明白如何将十进制数转换成二进制。也许有人会说:
“手算就是逊啦,我都是百度直接做进制转换的网站的。”
(考研考场上或者面试的时候还能掏出自己的笔记本上百度?真有你的。)
最主流的方法是取余法,就是拿你手头的十进制数不停除2,直到变为0。
因为是余数除法,所以商一定是整数(比如7/2 = 3 余1),每次除法的余数就是你要求的十进制对应的二进制的其中一位,顺序是从低到高
比如说10。
①.10/2 = 5 余0
②.5/2 = 2 余1
③.2/2 = 1 余0
④.1/2 = 0 余1
结果就是④③②①的余数:1010
要验证也很简单,二进制的第一位数字如果是1,则代表20 ,第二位如果是1则代表21,依次类推。(第一位默认是指最低位,也就是1010最右侧的0,不会有人这个不清楚吧,不会吧不会吧。)
所以1010是21 + 23 = 2 + 8 = 10
验毕。
(注意这里的问题其实都涉及的是整数,一旦涉及到小数。。。那就进入反人类的领域了。今天暂且不论)
因此-4321的二进制码为:
1,001 0000 1110 0001
即212 + 27 + 26 + 25 + 20 = 4321
最左侧(最高位)的1是我们刚刚上面讲的符号位,用逗号隔开方便区分,一般表示有符号二进制数的时候会使用逗号。
符号位1代表负数,0代表正数。
而61215的二进制码为:
1110 1111 0001 1111
计算方法不再赘述。
有人会问,“第一位不是符号位吗?怎么正数也是1。”
注意:ushort是无符号型数字,没有正负号,就省下了符号位
假如说int A,B;
A = 1;
B = A;
由于计算机的运算都是以二进制来处理,这么来讲上述的两个二进制码应当相同。但实际上两个二进制码完全不同。
不过呢,计算机是肯定不会抽风的,反正比起计算机抽风,电脑前的你抽风了或许还比较可能。
我们做一个转化。
1,001 0000 1110 0001
转化为
1,110 1111 0001 1111
这么一看不是和61215的二进制码相同吗?
(61215的二进制码为:
1110 1111 0001 1111 )
不是计算机抽风了,也不是你我抽风了,而是计算机把原二进制码转化成了另一种二进制码进行了赋值,并不是我们想象的将一个数字转换成二进制码之后直接复制给另一个变量这么简单。
当然有仔细的小伙伴会问:
直接告诉你答案:首先要规范化措辞,这个二进制码的形式叫做补码,而原二进制码,也就是数字直接转换成二进制形式的码叫做原码。
补码的形式是在原码的基础上,如果是负数就取反加一;如果是正数就保持不变。符号位该是什么就是什么,不受影响。
计算机关于整数的运算都是用补码来做的,也就是说,计算机内存储的整数都是补码形式,而不是原码
最开始举的例子是等长类型的类型转换,short和ushort无非是有无符号位的区别,实际上长度还是一样的,都是16位。
通常来讲长类型数据转短类型意味着什么?数据缺失。
这里的例子int型是默认的32位类型,short型是16位类型。
关于这里的16进制数有的人可能也比较迷糊,稍微提一下。
如果这么理解的话:
int型32位,也就是二进制最长能表达32位,十六进制就是最长8位
short型16位,也就是二进制最长能表达16位,十六进制就是最长4位
再来看上面的图例:
x是正数,补码就是它原码,286a1,一共5位,转换成A之后,由于A最长十六进制只有4位,因此由低向高数4位截断,只得到了86a1。
y是负数,我们讲过补码是取反加1,不过十六进制怎么计算补码呢?
我们讲过十六进制1位是二进制4位,手写计算的时候就把十六进制拆成二进制就行了,比如说16进制F是15,我们可以写成二进制的1111。
因此y的补码是FFFF7751,结果由于short只能保留4位十六进制数,最后只剩下7751。
这种情况比长类型转换成短类型情况要多一点,因为长->短仅仅是截断,而短->长意味着填充。
以上图为例
填充的方式有两种:
有符号型(第一组) | 无符号型(第二组) |
---|---|
为正数时剩余位置直接填充0,为负数时填充F | 直接填充0 |
什么意思呢,就是说short类型十六进制只有4位,int类型有8位,如果short的负数转换成int类型,别忘记了,计算机存储的都是补码,负数补码是要取反的。因此这个时候补缺剩下4位十六进制数的时候补进去的0取反都变成了F。
也就是short类型的-4321的十六进制补码为EF1F,转换成int类型的时候补进去0000 EF1F,但是补码是取反的,因此变成了FFFF EF1F。(可以这么理解)
考虑这么一个问题:为什么计算机不用原码这个简单直观的方式存储二进制数呢?
答案很简单。
哎,还真没说错,就是因为计算机是个蠢比,所以人类才想了一大堆办法绕着圈子解决逻辑上很简单的问题。
原码计算的问题在这里:
假如说拿-1 + 1
我们一看就知道是0
而计算机呢:等于-2啊,愚蠢的人类(1001 + 0001 = 1010,别忘了首位是符号位)
这个时候熟练的码农兄弟们就会讲"这个我熟,多加几个if就完事了,到时候跑的效率咋样就不关我事了"
如果是这样,原码计算得比较绝对值大小,然后拿大的减去小的,保留绝对值大的数的正负号。
问题是加减乘除计算太基操了,计算机每时每刻都要执行相关计算,这一点冗余带来的问题可就大了。
因此这群天才就想到了一个绝佳的方案,引入一个和原码相关的特殊的二进制码,简化四则运算的操作,这就是补码。
不管你信不信,实际上引入补码之后,加减乘除都是加法。。。这就意味着仅仅是引入了一个负数取反加一的操作,就减少了四则运算的冗余操作。
拿刚刚的-1 + 1 的例子来讲
-1的补码是 1111(1001取反->1110加1->1111)
1的补码是它自己,0001。
这样[-1]补+[1]补 = (1)进位溢出被剔除 (0000)二进制 = (0)十进制
因此,补码的优点就在于,进位(连带符号位)导致的溢出剔除可以直接得到正确的计算结果。这就是补码的好处
这里讲的都是比较基础的补码知识,下次讲讲补码的兄弟姐妹,还有补码的取值范围,以及小数的问题。
补码有个相当有趣的一点,在原码里,0有+0和-0,即1000和0000。
而在补码里,-0代指能取到的最大负数,因此0只能由+0表示。
有兴趣可以从数学的角度(正负补码相加)思考这是为什么。