文章目录
信息的表示与处理
众所周知,计算机采用二进制,因此一切计算机中的信息本质上都是01串,只是它们的编码与解码协议不同。同样的01串,经过不同的解码协议其含义是不同的,比如说LSP可以表示Language Server Protocol,也可以被解释成Old Sex Pi(大雾 ∙ \ ^\bullet ∙ v ∙ \ ^\bullet ∙ )
那么为什么计算机采用二进制(binary),而不是我们熟悉的十进制(decimal)或者十六进制(hexadecimal)呢?因为二进制在信号的表达上有天然的优势,比如说我让低电势表示0高点势表示1,这里的低和高又有一个容错。因为现实世界是肯定有噪声的,所以高电势和低电势分别表示为一个范围。而如果要应用10进制的话那么得切分很多个阈值,而且容错性还不强,故二进制是最优选。
上面都是扯谈,下面开始正文。
1 信息存储
1.1 寻址与字节顺序
计算机中的最小信息存储单位为bit,而最小寻址单位为byte。也即每一个指针指向一个byte。而指针的大小则由计算机的字长(word size)决定。字长决定的最重要的系统参数就是虚拟地址空间的最大大小,一个字长为 w w w的机器的寻址空间为0~ 2 w − 1 2^w-1 2w−1,故32位机器的最大寻址空间为 2 32 B y t e = 4 G 2^{32}\mathrm{Byte}=4\mathrm{G} 232Byte=4G,这显然跟不上现代人民对计算机性能的要求,因此现在大多数计算机都是64位的。
确定了地址的大小,也要确定其编码与解码协议。地址实际上也就是由几个字节拼起来的,所以接下来我们介绍字节内部的顺序。字节顺序分为两种,小端法(little endian)和大端法(big endian),这两个起源于《格列佛游记》的说法,被人们广泛且颇具偏好的接受。一方面大多数Intel兼容机都只采用小端法,另一方面IBM和Oracle的大多数机器则是按打断模式操作。
小端法指最低有效字节排在最前面,大端法指最高有效字节排在前面。举个栗子, 54 1 ( 10 ) 541_{(10)} 541(10),根据大端法我们读作“五百四十一”,根据小端法我们读作“一百四十五”。我本人更倾向大端法,因此本文后续非特别声明一般默认大端法。
1.2 字数据大小
计算机中的数据类型有许多,下面介绍一些比较基础的类型的数据大小。
short与long两个限定符的引入可以为我们提供满足实际需要的不同长度的整形数。int通常代表特定机器中证书的自然长度。short类型通常为16位,long类型通常为32位,int类型可以为16位或32位。各编译器可以根据硬件特性自主选择合适的类型长度,但要遵循下列限制:short与int类型至少为16位,long类型至少为32位,并且short类型不得长于int类型,而int类型不得长于long类型。————《C程序设计语言》
也就是说对于short、int、long,C并没有规范其类型大小,因此在不同的编译器上分配的大小可以不一样。指针型数据由机器的为而定。而float和double表示的浮点数,其标准由IEEE规定了,故在各个机器上都是一样的。下面列举各个机器上的gcc为他们分配的大小
有符号 | 无符号 | 32位机器 | 64位机器 |
---|---|---|---|
[signed] char | unsigned char | 1 | 1 |
short | unsigned short | 2 | 2 |
int | unsigned int | 4 | 4 |
long | unsigned long | 4 | 8 |
long long | unsigned long long | 8 | 8 |
int8_t | uint8_t | 1 | 1 |
int16_t | uint16_t | 2 | 2 |
int32_t | uint32_t | 4 | 4 |
int64_t | uint64_t | 8 | 8 |
char * | 4 | 8 | |
float | 4 | 4 | |
double | 8 | 8 |
对关键字的顺序以及是否包括“signed”,C语言允许多种存在形式,比如unsigned long = unsigned long int = long unsigned = long unsigned int。
1.3 位运算、逻辑运算与移位运算
位运算指&与、|或、^异或、~取反,前三个是双目运算符,最后一个是单目运算符。异或也叫半加运算,其运算法则相当于不带进位的二进制加法,输入相同输出为0,输入不同输出为1。
逻辑运算符与位运算符比较类似,不过逻辑运算符是作用于整个数据类型上而不是位上。&&于、||或、!非,前两个双目运算,最后一个单目运算。
移位运算分为<<左移、>>右移。其中右移分为逻辑右移和算术右移。左移表示原来的二进制串一起向左移动,剩余的地方补0,显然这有可能导致溢出,溢出的部分我们舍弃掉。逻辑右移表示原来的二进制串一起向右移动,空出的地方补0,溢出的地方舍弃。算术右移表示原来的二进制串一起向右移动,空出的地方按最高位补齐,溢出的地方舍弃。
在Java中>>表示逻辑右移,>>>表示算术右移。在C中没有限定这两种右移在使用域上的区别,然而一般来说对有符号用算术右移,对无符号用逻辑右移。很显然算术右移可以保持有符号int的正负,在下一节整数计算时我们也会提到。
另外一个要注意的就是当移位运算的移位特别大时。一位由 w w w位组成的数据类型,如果要移动 k ≥ w k\ge w k≥w位,会得到移动 k m o d w k\quad mod\quad w kmodw位。
2 整数表示与计算
2.1 无符号整型
无符号 w w w位整型,其权重向量为 [ 2 w − 1 , 2 w − 2 , . . . . . . , 2 1 , 2 0 ] [2^{w-1},2^{w-2},......,2^1,2^0] [2w−1,2w−2,......,21,20],它只能表示自然数,不能表示负整数。
2.2 有符号整型
有符号 w w w位整型,其权重向量为 [ − 2 w − 1 , 2 w − 2 , . . . . . . , 2 1 , 2 0 ] [-2^{w-1},2^{w-2},......,2^1,2^0] [−2w−1,2w−2,....