补码
补码的概念,存在于整数的存储中,包括 int、short、long、char(由于char是用ASCII码存储在内存中,因此可以看作1字节的整数)等。浮点数的存储思路与整数不同,并且十分复杂,此处不赘述。 有符号的整型变量无论有多少个字节,第一个bit(第一个比特位)都作为符号位存储,0 表示整数,1 表示负数,然后剩下的bit用来存储数据。这里以 int 为例:
这样我们可以得到数字的原码。如 int -1 的原码: 10000000 00000000 00000000 00000001 由于计算机底层的加减法计算需要,人们又发明了反码与补码。反码与补码的变化只存在于负数中,整数的原码、反码、补码相同。反码就是除了符号位,剩下的位全部取反。那么 -1 的反码就是: 11111111 11111111 11111111 11111110 而补码就是反码 + 1。可以得到: 11111111 11111111 11111111 11111111 这便是 - 1 在内存中存储的方式。可以用代码验证下: 可以看到完全正确。 由此我们可以又补码理清整数的存储规律了。以1字节为例: 这便是1字节整数的取值。注意黄色行,1000 0000是-128,或许联系前后文可以想当然,但是如果我们要去计算它的反码与补码,就会发现计算不出。反码为补码减一,而数据位全为 0,借 1 只能向符号位借,而这是不可理喻的。倘若向符号位借了 1,就会出现这样的结果: 反码:0111 1111 原码:0000 0000 可以看到结果是 0。而原来符号位上是 1,因此是个负数,由此便得到了 -0。而表格的末尾就是 +0 了。这样重复显然是浪费。作为无效值就不如作为特殊值,这样还能多存储一个数字。所以,计算机规定, 1000 0000 这个特殊的补码就表示 -128。 其次,我们再按照“传统”的方法计算一下 -128 的补码: -128 的数值位的原码是 1000 0000,共八位,而 char 的数值位只有七位,所以最高位的 1 会覆盖符号位,数值位剩下 000 0000。最终, -128 的原码为 1000 0000。 反码:1111 1111。 反码转换为补码时,数值位要加上 1,变为 1000 0000,而 char 的数值位只有七位,所以最高位的 1 会再次覆盖符号位,数值位剩下 000 0000。加上符号位,最终求得的 -128 的补码是 1000 0000。 2字节、4字节、8字节与此大同小异。
unsigned 的使用
unsigned,即无符号,是一个类似short、long 的关键字,在定义变量时修饰,可以使变量的所有位都拿来存储数据。通常用于非负数的存储中,如人数、内存地址等。 如:
unsigned short a = 1;
unsigned int b = 2;
unsigned long c = 3;
这样就可以增大变量存储的范围。假设原来1字节的变量,由前面的表格可知有符号的范围是-128~ 127,而无符号的范围就变成了0~ 255。 需要注意的是,虽然无符号变量不存储负数,但用负数赋值时仍然需要一个将原码转换成补码的过程,最终存储的仍是一个补码,只不过由于是 unsigned 类型,所以在读取时不用再将补码转换成原码了,而是直接看做原码,且不考虑符号位。 考虑如下实例:
#include <stdio.h>
int main()
{
unsigned int a = -1;
printf("%x\n", a);
printf("%u\n", a);
printf("%d\n", a);
return 0;
}
在这里我定义一个无符号的变量 a,同时赋值 -1。由于是负数,所以要转换成补码,即 ffff ffff(十六进制)。接下来用代码验证。 第一个printf 用十六进制打印变量a,可以看到内存中存储的a 确实是-1的补码,再用无符号的形式打印一下,这里的4294967295实际上就是-1补码的十进制。最后用有符号数的打印方式打印,由于是有符号,所以要经历一个由补码到原码的过程,因此又变成了-1。
printf 的格式控制符
在用printf 函数时需要利用格式控制符来打印相应的、不同形式、类型的东西,因此在这里进行汇总。
联系前面的实例,可以看出,printf 在打印时,若用的是有符号的格式控制符,那么会将内存中的补码转换成原码进行打印;若用的是无符号的格式控制符,那么直接将内存中的补码进行打印。
printf 格式控制符的标准格式: %[flag][width][.precision]type
说明: flag: ① -左对齐,若无则右对齐。 ② +在输出整数和小数时输出正负号。 ③ (空格)在输出整数和小数时为正加上空格,为负加上负号。 ④ # 在八进制和十六进制中加上前缀,在小数中(%f/%e/%g)即使没有小数部分也会强制加上小数点。
width: 指定输出长度,不足位以空格补齐,超出则按数据本身长度输出,不起作用。
precision: 表示输出精度,也就是小数的位数。 当小数部分的位数大于 precision 时,会按照四舍五入的原则丢掉多余的数字;当小数部分的位数小于 precision 时,会在后面补 0。
另外,.precision 也可以用于整数和字符串,但是功能却是相反的: 用于整数时, .precision 表示最小输出宽度。与 width 不同的是,整数的宽度不足时会在左边补 0,而不是补空格。当 precision 大于变量的宽度时要在 n 的前面补0;当precision 小于变量宽度时不再起作用。
用于字符串时,.precision 表示最大输出宽度,或者说截取字符串。当字符串的长度大于 precision 时,会截掉多余的字符;当字符串的长度小于 precision 时, .precision 就不再起作用。 顺带说一句,在给整型变量赋值的时候是可以用八进制和十六进制的,如:
int a = 0175;
int b = 0x9b3f;
照例都是要经过一个由原码到补码的过程。如:
printf 不支持二进制打印,可以用itoa 函数(stdlib.h)将十进制转换成二进制,用字符串存储再打印。
|