【计算机原理】基本数据类型

计算机中的信息由电路的高低电平来表示,因而计算机中的信息表示都是二进制的。所以我们将身边常用的数据一律转化为0,1代码放入计算机中进行处理。计算机总线位宽决定了计算机一次传输中能表示的二进制数最大位数。就目前而言,计算机以64位为主流,因而我们电脑处理器一次读取数据的位数最大为64位二进制数。比如十进制的1,在计算机中就表示为63个0加一个1.

在计算机中为了定义数据的大小,一位二进制数称之为字bit,八位二进制数定义为一个字节byte。对于64位计算机来讲,我们使用可以提供64位的数据位宽,因此具有8个字节的大小。对于计算机中常见的数据类型,以JAVA为例,char为2个字节,byte为一个字节,short为2个字节,int为4个字节,long为8个字节,float为4个字节,double为8个字节(参考自《Thinking in Java》 2010年5月第1版第14次印刷)。所以当这些数据被存储在内存中时,程序会为数据分配相应的位宽,并且在程序运行过程中,为了有效利用空间,存储在栈中的数据在内存中是相邻放置的,因此在极为必要在对象声明之时明确它们的大小。我相信你已经预见到类型强转所带来的风险了,不光是8字节转4字节时数据丢失的问题,更有4字节转8字节时内存管理和调整的问题。


负数的表示

计算机中如何表示负数是一个很有意思的问题。为了区别正负,计算机中数据使用补码形式以便于计算机对它们加以区别。补码就是一个二进制码的反码加一所得到的数值。计算时,先将对应的每一位进行取反操作,0变1,1变0, 然后对整个二进制值+1就得到了对应的负数的二进制形式。示例如下:

十进制:3,二进制原码:00000011,那么十进制数-3就表示为:00000011(3原码)->11111100(3反码)->11111101(3补码,即-3)。

那么问题来了,补码有什么好处呢?我们可以知道8位的二进制数,能表示的数据量为2^8个,即256个不同的数字,在无符号整型类型中,可表示范围为0-255。但是在引入负数时,我们就需要引入符号位来表示±,此时定义首位为符号位,使用原码时就出现了01111111表示127,11111111表示-127。但是细心的你可能发现对于0出现了什么样的情况,00000000和10000000同时表示数字0的情况,因为0=-0。更加严重的是,在运算过程中,二进制遇到了问题,看如下示例:

十进制:1+(-1)=0,对应的二进制:00000001 + 10000001 = 10000010为十进制的 -2。

但是换做补码之后这些问题就迎刃而解了。对于补码,首先00000000的补码依旧是00000000解决了重复表示问题,另外运算中,上式变为:

00000001 + 11111111 = 00000000

此时二进制运算也与十进制很好的统一了起来。


那么问题又来了,我手里有如下二进制码11110110,这到底是一个正数呢还是一个负数?

这个地方很难讲这一个字节的数据是一个什么类型,学习计算机的过程中,一定要明确一个概念就是计算机所有的数据都是二进制表示,这一个字节当声明为无符号整型时可以是一个正数,当声明为有符号整型时可以是一个负数,它也可以是一个C语言的字符,或者一个字符串中的一个字符,也可以是一张图片中的一个字节数据,一个音频文件的一部分等等。无论任何数据都无法独立存在,需要程序去解释,这也就是你在声明变量时,对类型的定义的作用。计算机调用程序就像解码,你得告诉它每段数据正确的意义,它才能正确的进行运算。


一类特殊的类型——数组

为什么我说数组一类特殊的数据类型,因为数组无法像之前的数据类型一样使用一个统一的长度来进行存储。数组将同一种类型的元素并列连续的存储在内存中。每一元素因为类型相同,所以位数相同。此时有一个问题出现了,在程序运行中,变量存储都需要程序为其分配相应长度的内存,但因为数组是一个装载有其他数据的数据类型,那如果我不知道元素的个数,岂不是计算机内存在接下来的分配中就面临难题了?这个数组到底需要多少的大小呢?所以解决的办法是,让数组在声明时一定需要指定长度。数组是一个数据类型我前边说过,它的第一个元素表明了这个数据类型的存储地址,如果访问数组中其它元素就以第一号元素在内存中的位置为起始点,加上访问元素的偏移量乘以元素长度就是你需要的元素的地址了。比如int array = new int[5];array的地址其实与array[0]是相同的,索引值0代表了偏移量。array[3]的访问地址其实通过是&array[0] + 3 * sizeof(int)来得到的。

对于高维数组来讲,其实也是线性存储的,存储方式类似于一维数组。在内存中数据放置顺序依次为array[0][0], array[0][1], array[0][2],array[0][3]...array[1][0], array[1][1],array[0][2]...array[n][m];低位索引优先增长的原则来逐一保存的。这里就又有一个很有意思的问题了,下面这段代码乍一看没有什么太大区别,但是当数组的大小不是10*10,而是1000*1000甚至更大时,两种遍历方式变产生了明显的耗时区别,第一种遍历远远快于第二种遍历,这是为什么呢?

int[][] array = new int[10][10];
		for(int i = 0; i < 10; i++)
			for(int j = 0; j < 10; j++)
				System.out.println(array[i][j]);
		
		for(int j = 0; j < 10; j++)
			for(int i = 0; i < 10; i++)
				System.out.println(array[i][j]);

其实道理很简单,上边的代码之所以会有区别,就是因为二维数组在内存中依旧是线性保存的,当行作为外循环,列为内循环时,访问二维数组就等于是依次访问,所以耗时很短。然而当行为内循环,列为外循环时,访问数组元素就是跳跃式访问,指针需要不停地大范围跳转,这就是耗时的根本原因了。

数组有其显著的有点,因为长度固定,元素存储地址连续,因此数组的访问从硬件层面上来讲是非常高效的。但是它同样也有局限,就是长度的变化不灵活使得数组在一些需要频繁改变元素个数的情况下不能很好的满足需求,因此便出现了容器类库中提供的可扩展数组容器(参考java.util.ArrayList),其本质是通过拼接数组来完成个数的增加和减少。同时可以很好完成元素增减的还有一些其他的容器类型比如链表,哈希表等,我将在数据结构中谈论他们。


总结

其实计算机就是一个海量二进制码的载体,任何一句命令,一个数字,一张图片,一段音频等等都是一连串的二进制编码。计算机为这些编码涉及了精密的传输和存储规则,保证编码可以被正确的保存和解释。所以刚接触计算机时,看到种类繁多的信息表示方式,不要认为计算机可以识别它们,其实计算机只认识二进制码,无论你的文件是.txt后缀,.png, .jpg后缀,甚至是.csv .avi .mat .css .html等等,它们其实都是01码,后缀只是告诉计算机程序如何解释和处理它们罢了。


你可能感兴趣的:(数字逻辑与计算机原理)