有符号数和无符号数之间的转换
各位小伙伴咱们先从一个简单的例子demo1.c
入手:
#include <stdio.h>
int main()
{
if (0 > -1)
{
printf("AAA\n");
}
else
{
printf("BBB\n");
}
return 0;
}
不言而喻,这个程序的运行结果为:
AAA
因为程序上来就是一个if
判断语句,如果
0
>
−
1
0 > -1
0>−1为真,则打印输出AAA
,反之,则打印输出BBB
。故输出了AAA
。
那我们继续再看,如果在0的后面加上一个u
,demo2.c
结果会如何?嗯哼?
#include <stdio.h>
int main()
{
if (0u > -1)
{
printf("AAA\n");
}
else
{
printf("BBB\n");
}
return 0;
}
程序运行结果为:
BBB
下面咱们来简单探讨一下,大家都知道demo1.c
的 0 与 1 0与1 0与1都是默认int
类型。对于64位机器上int
为4bytes
,即32bit
且有符号,再加上绝大多数的机器内部是以补码的形式存储的;所以我们把int
型的0、1的32位形式补码表示出来:
0 ->[0000 0000 0000 0000 0000 0000 0000 0000]
-1->[1111 1111 1111 1111 1111 1111 1111 1111]
对于demo2.c
的程序是在demo1.c
的基础之上在0后面加上一个u
。故把0隐式转换为unsigned int
,这是无符号int
版本。那到底为什么结果就输出了BBB
呢?这个又有什么影响呢?
原来C语言是这样规定的:对于大多数C语言的实现,处理同样的字长的有符号数和无符号数之间相互转换的一般规则是:数值可能会改变,但是位模式不变。大概意思就是,现在的demo2.c
的条件判断0u > -1
就是unsigned int 0 > [signed] int -1
。所以需要一个类型向另外一个类型进行隐式转换。对于同样字长的有符号数和无符号数之间的隐式转换是:有符号转向无符号数,当然,内部位上不会转变,只是改变读取位上的方式。所以把原先按照int类型读取-1的方式,改变为用unsigned int 的方式来读取其位上的0、1
字符串序列:因为-1的位模式为[1111 1111 1111 1111 1111 1111 1111 1111],所以其转换为无符号的十进制表示即为:unsigned int的最大值 4294967295 4294967295 4294967295。所以程序中的条件判断语句0u > -1
等价于0u > 4294967295
。所以才会得出这个程序运行结果。当然我们也可以验证一下的:
#include <stdio.h>
int main()
{
int x = -1;
if (0 > x)
{
printf("AAA\n");
printf("%d\n", x);
printf("%u\n", x);
}
else
{
printf("BBB\n");
printf("%d\n", x);
printf("%u\n", x);
}
return 0;
}
程序运行结果:
AAA
-1
4294967295
等价于demo1.c
#include <stdio.h>
int main()
{
int x = -1;
if (0u > x)
{
printf("AAA\n");
printf("%d\n", x);
printf("%u\n", x);
}
else
{
printf("BBB\n");
printf("%d\n", x);
printf("%u\n", x);
}
return 0;
}
程序运行结果:
BBB
-1
4294967295
等价于
demo2.c
这里再次验证了结论,就是数值可能会改变,但是位模式不变。
C语言允许在各种不同的数字数据类型之间做强制类型转换。例如,假设变量x声明为int
,u声明为unsigned
。表达式(unsigned)x
会将x的值转换成一个无符号数值,而(int)u
将u的值转换成一个有符号整数。将有符号数强制类型转换成无符号数,或者反过来,会得到什么结果?从数学的角度来说,可以想象到几种不同的规则。很明显,对于在两种形式中都能表示的值,我们是想要保持不变的。另外一个方面,将负数转换成无符号数可能会得到0.如果转换的无符号数太大以至于超过了补码能够表示的范围,可能会得到该范围的最大值。不过,对于C语言的实现来说,对这个问题的回答都是从位级角度来看,而不是从数的角度。
比如说,考虑下面的代码:
short int v = -12345;
unsigned short uv = (unsigned short)v;
printf("v = %d, uv = %u\n",v,uv);
在一台采用补码的机器来说,上述代码会产生如下输出:
v = -12345, uv = 53191
原因如下:
首先对于有符号整型v来说,-12345在采用补码的机器上面存储的位模式为[1100 1111 1100 0111]
其次对于无符号整型uv来说,53191在采用补码的机器上面存储的位模式位[1100 1111 1100 0111]
还可以反过来检验:
/**十六进制表示写作0xcfc7的16位位模式既是-12345的补码表示,又是53191的无符号表示。同时注意:12345+53191 = 65536 = 2^16这个属性可以推广到给定位模式的两个数值(补码和无符号数)之间的关系。*/
#include <stdio.h>
int main()
{
unsigned short x = 0xcfc7;
short y = 0xcfc7;
printf("%u\n", x);
printf("%d\n", y);
return 0;
}
程序输出示例:
53191
-12345
综上,***强制类型转换的结果保持位值不变,只是改变了解释这些位的方式***。因为由上图可知,-12345的16位补码表示与53191的16位无符号表示是完全一致的。将short强制类型转换为unsigned short改变数值[解释这些位的方式],但是不改变位表示本身。
对于大多数C语言的实现,处理同样的字长的有符号数和无符号数之间相互转换的一般规则是:数值可能会改变,但是位模式不变。