1计算机理解的进制数
学习一门编程语言,无论是C/C++还是JAVA等等任何一门编程语言,都没有捷径,唯一的捷径就是多敲代码,多思考。把自己的疑问通过代码来表达出来,然后,验证问题、解决问题。
但是,在学习编程之前,我们还是需要一些基础知识的准备,例如了解数据在内存中存储的形式、二进制、十六进制的计算等等。
2内存中数据存储格式
我们渡过的每一天都是从白天到黑夜,再从黑夜到白天这样循环重复着,如同中华文化里的太极,表示着阴和阳之间的相互转换。这个世界上,任何事情都有两面性,也就是阴和阳。
同样,计算机也是由阴和阳组成。我们知道,计算机是一台电子设备,是由N多个二极管、三极管组成。这些二极管和三极管的特性就是“截止和导通”;“截止”和“导通”,就形成了电路的“开”和“关”。
计算机中的内存设备,就是由N多二极管、三极管组成,里面就存放着电路的打开和关闭的状态。最终,电路的打开和关闭的状态,就是由0和1这两种数值来表示。
首先,我们来看一个电影情节:有2个黑帮在进行约谈,随时可能会发生冲突。一个黑帮的头领对手下说:
(1) 在交谈的最后,如果我把吃饭的筷子放在碗上,就表示和谈,不要动手;
(2) 如果我把筷子放在碗的旁边,就表示谈判破裂,你们马上动手;
在这个电影情节中,我们得到这样的信息:
(1)碗是一个容器,可以把筷子放在上面;
(2)如果碗里面放筷子,表示和谈,不动手;
(3)如果碗里面不放筷子,表示谈判破裂,马上动手;
那么,这个碗就是一个“容器”,存放了我们需要的信息。
在计算机中,内存是存放计算机数据的地方,就是一个“容器”,可以存放二极管的“截止”和“导通”状态,就是存放电路的“打开”和“关闭”状态。可以表示为:
(1)电路的“打开”状态用1表示;
(2)电路的“关闭”状态用0表示;
那么,当我们按下键盘的某个按键时,就在内存的某个地方,存放1数值;当键盘弹起的时候,内存中存储键盘按下状态的地方,从1值变为0值。那么,计算机就可以知道键盘的按下和释放的状态。
此时,我们知道,计算机的内存,存放0和1这样的数据。在计算机中,存储数据的最基本单位是字节(Byte),一个字节由8位(bit)组成。一个位(bit)就是就是存储一个二进制数据。
3十进制数据
在讲解二进制数据之前,我们先来看看我们最常用的十进制数据。
从小学读书起,我们就开始学习数学,最简单的加法有:
5 + 3 = 8;
5 + 5 = 10;
10 + 16 = 26;
看到这样的加法算术很简单,它只有一个规则:
(1)个位数与个位数相加,满10进1;
(2)十位数与十位数相加,满10进1;
(3)百位数与百位数相加,满10进1;
...
以此类推,这种规则表示的数,我们称为:十进制数;
表示十进制数的元素就有:
0,1,2,3,4,5,6,7,8,9
这样的10个数值;
所以,从我们最常见的算术开始,我们了解了十进制数。它的规则就是:满10进1;那么,根据这个规则,我们可以定义N进制数,它的规则就是:
(1)表示N进制数的元素,有N个符号;
(2) 数据的表示,是满N进1;
4二进制数据
通过上一节的讲解,我们知道二进制就是由0和1组成的数字,它表示电子设备的打开和关闭状态。计算机中存储的任何数据都是二进制数据。
例如十进制3这个数值,在计算机中都表示成的二进制数据是11这样数值。如果十进制3这个数值存放到1个字节中,1个字节使用8位表示,那么,表示的数值是 0000 0011,所以,表示8位二进制数据。
根据上面提到N进制数规则,我们定义二进制数的规则是:
(1)表示二进制数的元素有 2 个符号,就是0和1这两个数值;
(2)二进制数的表示是满2进1;
知道了二进制数据的表示方法,那么,我们试着把十进制19这个数值的二进制数据写出来。
把十进制数据转换成二进制数据,使用的方法是:除2取余法,计算过程如下:
19/2 = 9余1
9/2 = 4余1
4/2 = 2余0
2/2 = 1余0
1/2 = 0余1
除到商为0的时候停止;
然后,从最末尾的余数开始往上取,得到10011这个二进制数据。所以,十进制19这个数值的二进制数据就是10011。如果把十进制19这个数值存放到1个字节的变量中,那么,在1个字节的内存中表示为0001 0011,因为1个字节是8位,上面我们求出10011二进制数据占据了5位,那么,剩下的高3位没有数据填充,所以,填充0值。
所以,我们可以总结十进制转二进制的方法:
(1) 采用除2取余法;除到商为0的时候停止。
(2)余数从下往上取;
(3)高位补零;
现在我们试着把二进制转换为十进制。在转换之前,我们先看看十进制表示的一个例子。例如189这样的一个数值,在数值序列中,从右边开始算起,从0开始计算,就有:
9在第0位,8在第1位,1在第2位,那么,就可以表示为:
1*10^2 + 8*10^1 + 9*10^0 = 100 + 80 + 9 = 189
那么,是十进制表示,所以,基数为10,例如第2位,就有10^2表示10的2次方,就是10*10,这就是第2位的权重值;注意:在数学计算中,任何数的0次方,都等于1。 所以有:
1*10^2 + 8*10^1 + 9*10^0
= 1*100 + 8*10 + 9*1
= 100 + 80 + 9
= 189
所以,我们可以总结N进制数值的表示算法:
(1)每个位置上的数值,与权重值相乘,然后求和;
(2)权重值是N^x,表示N的x次方,x是数值在数值序列上的位置;位置从右边开始算,从0开始;
同理,我们就把10011这个二进制转换为十进制,也是使用相同的算法。
把10011这个数值序列解开,从右边开始计算,第0位是1,第1位是1,第2位是0,第3位是0,第4位是1;
那么是二进制表示,所以,基数为2,例如第3位,就有2^3的权重,就有:
1*2^4 + 0*2^3 + 0*2^2 + 1*2^1 + 1*2^0
= 16 + 0 + 0 + 2 + 1
= 19
所以,我们把19这个十进制转换成了10011二进制数据,再把二进制数据转换为十进制数据。
再多举一个例子,把18这个数值转换为二进制表示,有:
18/2 = 9余 0
9/2 = 4余 1
4/2 = 2余 0
2/2 = 1余 0
1/2 = 0余 1 //除到商等于0的时候,停止计算;
然后,从下往上取余数,得到10010二进制数据。然后,10010二进制转换为十进制数据,有:
1*2^4 + 0*2^3 + 0*2^2 + 1*2^1 + 0*2^0
= 16 + 0 + 0 + 2 + 0
= 18
通过这些例子的分析,我们讲解了二进制的表示形式和二进制与十进制数值间的相互转换。
5十六进制数据
在介绍十进制数的时候,我们已经定义了N进制数,它的规则就是:
(1)表示N进制数的元素有N个符号;
(2)满N进1;
那么,十六进制数就是有16个符号表示,如下:
0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F
其中字符A表示10数值,字符B表示11数值,字符C表示12数值,字符D表示13数值,字符E表示14数值,字符F表示15数值。注意:这个字符,也可以使用小写字符a、b、c、d、e、f表示。
在学习二进制数据的时候,我们知道,把一个十进制数据转换N进制数据,采取除N取余法。所以,把一个十进制数据转换为十六进制,就是采取:除16取余法。例如,把十进制数18这个数值,转换为16进制,有如下的计算:
18/16 = 1余 2
1/16 = 0余1
然后,从下往上取余数,得到十六进制数12这样的数值。但是,我们有些疑惑,当我们看到12这个数值的时候,如果不表明是十六进制,我们可能会认为是十进制数据。
所以,在计算机系统中,我们定义十六进制数,使用0x前缀表示。所以,上面计算出来的十六进制数据就可以表示为0x12。当我们看到一个数值,如果是以0x开头,就表示这个数据是十六进制数据。
那么,我们把十六进制数0x12数值,转换为十进制数,算法与二进制转换为十进制数一样。对于0x12数值序列,第0位是2,第1位是1,所以有:
1*16^1 + 2*16^0
= 16 + 2
= 18
此时,就可以把十六进制0x12数值对应的十进制计算出来了。我们再计算一个例子,计算十进制是169的数值,转换为十六进制,有:
169/16 = 10余 9
10/16 = 0余 10
其中,十进制10数值使用十六进制字符A表示,所以,得到的十六进制数是0xA9。注意,对于10~15的数值,在十六进制中,使用字符A~F(或者小写字母a~f)进行表示。例如十进制15这个数值,使用十六进制表示,就是0xF。
现在,我们把0xA9这个十六进制转换为十进制,计算方式如下:
A*16^1 + 9*16^0
= 10 * 16 + 9 * 1
= 160 + 9
= 169
注意,在计算的时候,十六进制的字符A表示十进制的10这个数值。
6有符号数和无符号数
学习了二进制数据,我们知道,在计算机系统中,存储的任何数据都是以二进制数据的格式存储。存储数据的最小单位是字节(Byte),一个字节的容量是八位(bit),可以存储8个二进制数据。在讲解“有符号数”和“无符号数”之前,我们先来举一个例子。
例如,有个人(称呼A)去爬山,半路上碰到一个朋友,朋友送了他一瓶水,此时A手上拥有一瓶水;那么,有如下的情况:
(1) 如果A觉得渴,那么,A觉得手上的这瓶水很宝贵,就把这瓶水喝掉;
(2) 如果A觉得不渴,那么,A觉得手上的这瓶水很重,麻烦,就丢掉;
(3) A手上的那瓶水,永远是那瓶水,本身没有任何变化。但是,A以不同的角度来看这瓶水,那么,会得到不同的结果。
在这个例子中,可以进行比较,如下:
(1)人员A就如同一个计算机系统;
(2)人员A手上拿有一瓶水,就如同计算机内存中,一个字节存储的一个数值;
(3)例如,在内存的一个字节中存储255这个整数值。那么,一个字节中的255这个数值,永远是255,不会有任何改变。但是,计算机系统把255这个数值,以不同的角度来看待,会有不同的结果。
那么,在计算机系统中,可以把内存中存储的数值当做“有符号数”和“无符号数”来看待。同一个数值,以不同的角度来看待,有不同的结果。
下面,我们来看看计算机系统对“有符号数”和“无符号数”的处理。
有符号数的定义是:字节的最高位作为符号位,其余的是数值位。例如一个字节中存储的二进制数为1100 1000,最高位1作为符号位,其余的7位100 1000作为数值位。
那么,符号位占据1位,就有0和1这样的两种数值,就有:
(1)如果符号位为0,那么字节中存储的数值是正数;
(2)如果符号位为1,那么字节中存储的数值是负数;
对于1100 1000这样的二进制数据,符号位是1,就表示负数。在有符号数中,表示负数的算法是:
(1)把数值位中存储的二进制数据,每个位都取反,就是原来为0的值变为1,原来为1的值变为0;
(2)给对取反后的二进制数据加1,得到的数值就得到负数值;
所以,有符号数可以表示正数,也可以表示负数。
无符号数的定义是:没有符号位,所有的位数都是数值位。所以表示的都是正数。
例如1100 1000这个数值,如果作为有符号数看待,那么符号位是1,数值位是100 1000。所以,符号位是1,所以,这个数据是负数。然后,表示成十进制时,对数值位的操作是:
(1)数值位取反,得到011 0111;
(2)对取反后的数值 011 0111加1得到011 1000,数值位的值为56;
那么,1100 1000这个二进制数据表示为“有符号数”时,就是-56这个数值。
如果作为无符号数看待,那么,就没有符号位,所有的位数都是数值位,所以11001000都作为数值位,表示的十进制数值是180。
例如,0111 0011这个数值,如果当做“有符号数”看待,那么,其符号位是0,所以,表示正数,数值位是115,所以,表示正115这个数值。如果当做无符号数看待,所有位都是数值位,计算得到115这个数值,所以,表示正115。所以我们可以总结:
(1)无符号数,总是表示正数。所有位数都表示数值位。
(2)有符号数,可以表示正数和负数,最高位是符号位,其余位都是数值位。如果符号位是0,则表示正数;如果符号位是1,则表示负数。对于负数的表示方法是:数值位全部取反,再加1,得到的数值就是负数值。
7 ASCII码
在计算机系统中,存储在内存中的任何数据,都是使用二进制形式进行存储。例如,十进制2这个数值,转换为二进制是10,如果存储到内存的一个字节中,就是0000 0010,因为一个字节是8位的容量,而二进制10只占用2个位,所以,高位用0补全。
此时,我们可以把一个数值存储到一个字节中。那么,对于计算机处理的'A'、'B'、'C' …等字符,怎么样处理?怎么样存储到内存中?
为了解决这个问题,计算机使用了ASCII编码。ASCII(American Standard Code for Information Interchange,美国信息互换标准代码,ASCII)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统。
ASCII编码就定义了特定的符号,对应特定的数值,例如'A'符号对应的数值是65,那么,我们只需要把65这个数值存储到内存中,就是把'A'符号存储到了内存中。
ASCII编码的对照表如下。
可以看到,字符'a'和'A'都使用了不同的数值进行表示。如果内存中存储了97数值,就可以表示'a'字符,如果存储了65数值,就可以表示'A'字符。
8总结
学习计算机软件编程,必须熟练掌握二进制、十进制、十六进制数据的应用。特别是面向计算机底层系统的开发,经常需要处理各种二进制、十六进制的数据。例如,在网络编程中,需要定义网络协议数据包,那么,一个字节就可以拆分成不同的数据位来表示不同的操作,就需要操作二进制数据。
所以,熟练掌握二进制、十进制、十六进制的应用非常重要。