我们都知道计算机的语言中只有”0”、”1”码。但是计算机语言中也有负数。我们再C语言中,unsigned是无符号数,而signed是有符号数,那么计算机有是怎样标记有符号数的正负呢。
计算机虽然只能看的”0”、”1”,但是它也懂得做个标记。计算机是通过把基本数据类型的最高位腾出来,用来存符号,同时约定如下:最高位如果是 1,表明这个数是负数,其值为除最高位以外的剩余位的值添上这个“-”号;如果最高位是 0,表明这个数是正数,其值为除最高位以外的剩余位的值。所以unsigned int取值范围为0 ~ 2^31-1,而signed int 的范围为-2^31 ~ 2^31-1(减去的1是因为还有0)。signed char 类型数其值表示的范围为 -2^7 ~ 2^7-1。
对于有符号数,我们要关心的一个重要问题是是否会溢出。因为数据的溢出有可能导致我们的程序朝着我们的相反方向发展。下面我们来看一个例子:
#include <stdio.h>
#include <unistd.h>
int main()
{
char num = -128;
num -= 1;
printf("%d\n", num);
return 0;
}
上述结果会是多少呢?-129?然而计算机并不这样认为。它的运行结果是127。下面是运行结果:
[root@localhost Desktop]# ./test
127
这是因为-128 - 1,计算机会认为是(-128) + (-1),而负数在内存中它们都是用补码的形式存储的。同样也是以补码形式计算。-128的补码为 1000 0000,而 -1 的补码为: 1111 1111。两者相加会使符号为溢出:
数值 补码
-128 : 1000 0000
-1 : 1111 1111
-128 - 1 --> (-128) + (-1)
|
|1000 0000
|1111 1111
_____|______________
1|0111 1111 (结果溢出) 0111 1111 ---> 127
|
如果你在编程时,这些都是小细节,但是许多人可能在平时都会忽略。比如下面的代码:
int main()
{
char a[1000];
int i;
for(i = 0; i < 1000; i++){
a[i] = -1 - i;
}
printf("%d\n", strlen(a));
return 0;
}
上述代码打印值又会是多少呢。当代码逻辑一多时,我们很容易忽略溢出问题。我刚开始看这个代码时第一反映也是1000。但是它的运行结果时255。下面使程序的运行结果:
[root@localhost Desktop]# gcc -o test UnsignedSignedTest.c
[root@localhost Desktop]# ./test
255
这是因为刚开始i = 0时, a[0] = -1; i = 1时,a[1] = -2; i = 2时,a[2] = -3; …….. , 当i = 127时,a[127] = -128; 当 i = 128时,a[128] = 127(上一个代码中推导过); ……. ; 当i = 254时, a[255] = 0; 而strlen计算大小时是在寻找 0 值。当遇到0结束。所以答案时255.
那么
int i = -20;
unsigned j = 10;
i+j 的值为多少?为什么?
我们一起计算下:
数值 原码 补码
-20 1000 0000 0001 0100 1111 1111 1110 1100
10 0000 0000 0000 1010 0000 0000 0000 1010(整数的补码是他本身)
|
|1111 1111 1110 1100
|0000 0000 0000 1010
__|_____________________ 补码 原码 数值
|1111 1111 1111 0110 --> 1111 1111 1111 0110 --> 1000 0000 0000 1010 --> -10
|
代码验证: