二、信息的表示和处理

251 阅读7分钟

现代计算机存储和处理的信息都是以二进制的值来表示。这不同于我们熟悉的十进制,因为有十根手指的人类,使用十进制表示法是很自然的事情,但是当构造存储和处理信息的机器时,二进制值可以更好的工作。
那为什么计算机要使用二进制而不是十进制呢?

  1. 技术实现简单:计算机由逻辑电路组成,逻辑电路通常只有两个状态,开关的接通与断开,这两种状态正好可以用"1"和"0"来表示;
  2. 简化运算规则:两个二进制数的和、积运算规则简单,有利于简化计算机内部结构,提高运算速度;
  3. 适合逻辑运算:逻辑代数是逻辑运算的理论依据,二进制只有两个数码,正好与逻辑代数中的"真"和"假"相互吻合;
  4. 易于进行转化,二进制与十进制数易于相互转换;
  5. 用二进制表示数据具有抗干扰能力强,可靠性高等优点。因为每位数据只有高低两个状态,当受到一定程度的干扰时,仍能可靠的分辨出它是低位还是高位;

当然单个的二进制位是没有任何意义的,所以我们通常会将位组合在一起,赋予不同位模式不同的含义,就可以表达任何有限集合;

这里我们会研究三种最重要的数字表示:无符号数编码,补码编码,浮点数编码。

一、信息存储

大多数计算机使用8位的块,或者字节(Byte),作为最小的可寻址的内存单位,而不是直接访问内存中单独的位。在程序中将内存视为一个非常大的字节数组,称为虚拟地址(这个后面在详细说)

1.1 十六进制表示法

不是在说二进制吗,怎么跑到十六进制上来了,听我慢慢说。

一个字节由8位来表示,在二进制表示法中,它的值域是 00000000 ~ 11111111,如果转换位十进制,值域就是 0~255。两种符号表示法对于描述位模式来说都不是非常的友好,因为二进制表示太冗长,十进制转换又比较麻烦,所以我们一般会用十六进制数来表示位模式(不是说计算机底层使用十六进制,而是我们通常在书面表示的时候采用十六进制)。

我们来简单看下十六进制和二进制的转换表

363003_1226157338Ux9k.gif

C语言中,十六进制的值通常以 0x 或 0X 开头

二进制与十六进制之间的转换通常采用取四合一法,如果无法凑齐四位,则在前面补0;

为什么四位的二进制可以表示一个十六进制呢?
因为十六进制范围是 0 ~ 15,用四位的二进制正好可全部表示 0000~1111

1.2 字数据的大小

我们听的最多的,64位机器还是32位机器。

每台计算机都有这样一个字长(word size),来指明指针数据的标称大小(nominal size)。因为虚拟地址是以这样的一个字来编码,所以字长决定虚拟地址空间的最大大小。也就是说字长位 w 位的机器,虚拟地址的范围位 0~2w2^w-1,程序最多访问 2w2^w个字节

比如32位的机器,总共可以表示 2322^{32} 个不同的数,代表 2322^{32} 个地址,也就表示了 2322^{32} 个字节;

2322^{32}Byte = 2222^{22}KB = 2122^{12}MB = 222^{2}GB = 4GB 这也就是为什么说32位的机器,内存最多只能到4G的原因,64位同理

因为机器的字数据大小不同,所以程序的编译方式也会不同,有的程序使用64位来编译,有的使用32位来编译,大多数64位的机器也可以运行32位机器编译的程序,这是一种向后兼容;

1.3 寻址和字节顺序

对于跨越多个字节的程序对象,我们要有这两个意识:这个对象的地址是什么?以及在内存中如何排列这些字节?

1.3.1 对象的地址

在几乎所有的机器上,多字节的对象都被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。

例如:假设32位的int类型,地址为 0x100,那么x的4个字节将被存储在内存的 0x100、0x101、0x102、0x103 的位置上;

1.3.2 大端法和小端法

现在我们找到了地址,那么一个值在这些地址上是按照什么样的顺寻存储的呢?

现有一个 w位的整数,其位表示为 [xw1_{w-1},xw2_{w-2},xw3_{w-3},......,x1_{1},x0_{0}],其中xw1_{w-1}最高有效位,x0_{0}最低有效位

小端法:最低有效位在最前面(从最低到最高)
大端法:最高有效位在最前面(从最高到最低)

Android只能运行与小端模式

之前在控制台打印十六进制值的时候还纳闷为啥反着来排序

1.4 字符串的表示

每个字符都由某个标准的编码来表示,我们最常听到的就是 ASCII 字符码,且在使用 ASCII 编码的任何系统上都字符表示都是一样的,与字节顺寻和字大小无关,所以,文本数据比二进制数据具有更强的平台独立性。

为什么字符串在内存中的表示无关大小端呢?
记得我们之前说的,对于跨越多个字节的程序对象,我们才需要考虑大小端,字符串在内存中机器就会看做一个个的单个字符,一个字符就是一个字节,并没有跨越多个字节,所以不需要考虑字节顺序的问题。

内存的单位是字节,对于字符来说,char是1个字节,不受主机字节序和网络字节序的影响,在内存中就一个单元,没有前后之分。但是当是组合内存空间时,因为有多个内存单元,就有前后之分,而小端和大端字节序的差别就在于怎么对这个前后内存单元进行组合。

1.5 布尔代数简介

二进制值是计算机编码、存储和操作信息的核心,所以围绕数值 0 和 1 的研究已经演化出了丰富的数学知识体系。这起源与1850年前后 乔治-布尔,因此被称为布尔代数。布尔注意到将逻辑值TRUEFLASE编码为二进制值1和0,能够设计出一种代数,以研究逻辑推理的基本原则;

下面看几种布尔代数的运算

布尔运算符对应的逻辑运算符
~NOT
&AND
|OR
异或(相异为真)

布尔环:a^b^a=b

任何数异或0,都等于它本身,异或1则会得到它的非(看下面这张图)

Snipaste_2022-03-27_21-04-32.png

1.6 C语言中的位级运算

C语言的一个很有用的特性就是它支持按位布尔运算。我们之前说的那些布尔运算符其实就是C语言中使用的。

位级运算的一个常见用法就是实现掩码运算,这里的掩码是一个位模式,表示从一个字中选出的位的集合;

1.7 C语言中的逻辑运算

逻辑运算符

逻辑运算符对应的逻辑运算符
~NOT
&&AND
||OR

这里注意,逻辑运算很容易和位级运算混淆,但是他们的功能是完全不同的。

  1. 逻辑运算的结果只有TRUEFLASE两种表达,分别用1(TRUE)0(FALSE)来表示;
  2. 逻辑运算和位级运算的第二个区别是,如果对第一个参数求值就能确定表达式的结果,那么逻辑运算符就不会对第二个参数求值;

1.8 C语言中的移位运算

  • <<: 左移位模式, x<<k 就表示 x 向左移动 k 位,丢弃最高的 k 位,并在右端补 k 个 0;
  • 逻辑右移:在左端补 k个0;
  • 算术右移:在左端补 k个最高有效位 的值;(这里注意了,最高有效位,和是0还是1没关系)

Java对右移有明确的定义,>>会进行算术右移,>>>会进行逻辑右移;