岔路口、数据在内存中的保存方法

By | 2015年2月23日

这一篇文章中的知识对大多数新手来说,可能会比较难以理解,特别是那些对二进制不太熟悉的人,如果你还不知道什么是二进制,那么你很有必要去浏览教程中预备章的内容:什么是进制

如果你对确实对阅读本文章的内容感到困难,你可以放心的大胆跳过,但是这并不意味着这些知识不重要,相反,了解这些知识将有助于你不会轻易掉入C语言的陷阱(C语言就是一个到处充满陷阱的语言)


前面我已经一再重申,数据在内存中都是无差别的二进制数据,计算机也只有能力去处理二进制数(唔,不考虑三进制计算机……)

而整数、浮点数只不过是我们对这些二进制的不同的解释方法罢了,这篇文章就是要告诉你我们使用何种方法去处理这些让人讨厌的数据的

让我们从最简单的正整数开始

表示一个整数是非常简单的,就像我们生活中用十进制表示正数一样,只需要在对应的位上写上数字即可。

内存中表示的正整数就是将十进制转换为二进制数的结果,例如十进制3转换为二进制就是11(2)

如果我们将它存入一个unsigned char类型的变量中(前面我们知道他是1字节)

在内存中就是0000 0011,如果是unsigned int(4字节),那么他就是0000 0000|0000 0000|0000 0000|0000 0011,(可以看到前面的三字节都没有参与表示数值的作用)这种由十进制直接转换得出的数据叫做源码

下面,我们就需要考虑到负数的情况。在十进制中,我们在负数前添加了一个横杠“-”,以此来区别于绝对值相同的正数。二进制当中我们当然也需要使用一个符号来区别正数与负数,这个符号便是“符号位”,它的位置被规定为“最高位”,这和人类的习惯相同,我们总是把负号写在数字的最前面!

补码其实就是补数,在计算机中人们给它换了个名字而已。实际上计算机中所有的有符号数都是使用补码储存的(有的人可能会认为只有负数使用补码)

在详细解释补码的之前,请容许我介绍一下“模”的概念

“模”是一个计数系统表示数的范围,任何有计数范围的计数系统都有“模”!比如一个字节,一个时钟。

让我们以时钟为例子,时钟的面板上(不要给我扯电子表)写有1-12一共十二个数字

这里请容许我将12换成0,这样会更接近与我们的表示数的方法(从0开始)

它的模便是12。显然这个值在时钟上是表示不出来的(我们已经将12的地方换成了0)

这意味着,一旦数值到了12,时针就转了一圈,而又会从0开始计数。这种超过储存空间所能表示最大值,而产生数据被丢弃的情况叫做溢出(想象一下往装满了水的水桶里倒水的情形,在编程中不受控制的溢出是危险的,我们应该去避免它的发生

模就是计数器产生溢出的量。

溢出会造成数据的丢失,但正是因为这个原因,使得我们可以使用加法来代替减法计算!

让我们再看下面的时钟,灰色的指针指向12(我知道这两个指针差不多……早知道我就用别的颜色画了),黑色的指针指向3,这是时钟现在的位置

而正确的时间却是12点,也就是说,我们的钟快了3小时。

123

 

wait!一定是快了3小时么?

如果是慢9个小时呢?

管他是怎么样,现在我们需要把它调整到正确的时间上,我们有就两种调法,一种是退回三小时,另一种则是多转9小时(你要是闲的蛋疼,转21个小时也可以)

如果我们规定退回为负,多转是正,那么上面调整时间的方法就可以简化为+9和-3

神奇的事情发生了,-3和+9是一样的效果!!!

按照这个思路不难发现,在时钟里,1和11,2和10,3和9,4和8……都有这样的效果,而它们的特点便是加起来等于模!

在计算机中也有这种情况,以1个字节为例,他所能表示的最大的值是1111 1111,如果我们再+1呢,就会发生进位,变成1|0000 0000,但是我们只有一个字节啊,因此这个由进位产生的“1”,将会被丢弃

这就是说,产生进位现象的数就是1|0000 0000,这便是1字节情况下的模

也就说,在这种储存情况下,+1111 1111和-000 0001是一样的效果!

我们知道,一个数减去一个正数,等于加上这个数的相反数,即1-1=1+(-1)=0

这意味着,1111 0000 – 0000 0001 = 1111 0000 +1111 1111 = 1110 1111

我们将第二个数字换成十进制,那么第一个式子便成了1111 0000 – 1,他就是1111 0000 + (-1),这不就是第二个式子吗?

这说明1111 1111就是-1这个值!

-1 + 1是0,1111 1111+ 0000 0001 = 1|0000 0000,也就是0

知道了-1,-2也就不成问题了,-2不就是-1-1吗?那它就是1111 1110!

相同的,1111 1110就是-2,以此类推,1000 0000便是-128!

那么-129呢?

【这还不简单,1000 0000 – 1,不就是0111 1111吗?】

wait!如果-129是0111 1111的话,那么它加上129就应该是0,129的二进制是1000 0001!而按照我们前面的算法,这个数字应该是-127!这样便会产生混乱!为什么?因为我们没有规定这个数字是正数还是负数!

为此,人们将最高位为1的数规定为负数!

一字节的有符号数,所能表示的数据范围是-128-127,如果你要问为什么正数要少一个,因为有一个0啊!0000 0000按照上面的方法,就是+0,而0有一个就够了,于是所谓的-0便被拿出来表示了一个负数,这样负数就多了一个!(以前的人们总是想尽办法节省资源)

正数的补码就是直接转换成的二进制数,这种数字叫源码

现在我们再来看看补码与源码(直接转换出的二进制数)之间的转换(当然是负数的转换)

将负数转换为补码的更为简便的方法是将数据取反,再加上1

如将-13转化为单字节型的补码,先将13换成二进制数0000 1101,我们将他按位取反(包括符号位,由源码按位取反的数叫反码)就是1111 0010,最后加上1

就是1111 0011,这便是-13在signed char中保存的样子

弄清楚了单字节,多字节也不成问题了,譬如常见的四字节int类型。-13转化成四字节就是0000 0000|省略2字节0|0000 1101(源码)

按位取反后1111 1111|省略2字节1|1111 0010(反码),最后加一,就成了1111 1111|1111 1111|1111 1111|1111 0011

实际上,所有的有符号数都是使用补码这种方法保存的,正数(和0)的补码和源码相同,负数则是其绝对值的补数

小数我们放下一篇再讲吧……不然这篇文章就太长了

2015-3-1日修改

One thought on “岔路口、数据在内存中的保存方法

  1. www.88bifa.com

    人的才华就如海绵的水,没有外力的挤压,它是绝对流不出来的。流出来后,海绵才能吸收新的源泉。

    回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注