起因是我在将字符流打印16进制的时候,很莫名其妙地出现了ffffff,我还以为是内存拷贝的时候出现了错误,找了好久,终于明白原来是与char的符号位有关。找了两篇很具体的文章,贴出来分享学习一下。
首先看一下百度百科上对unsigned关键字的解释吧:
整型的每一种都分有无符号(unsigned)和有符号(signed)两种类型(float和double总是带符号的),在默认情况下声明的整型变量都是有符号的类型(char有点特别),如果需声明无符号类型的话就需要在类型前加上unsigned。无符号版本和有符号版本的区别就是无符号类型能保存2倍于有符号类型的正整数数据,比如16位系统中一个short能存储的数据的范围为-32768~32767,而unsigned能存储的数据范围则是0~65535。由于在计算机中,整数是以补码形式存放的。根据最高位的不同,如果是1,有符号数的话就是负数;如果是无符号数,则都解释为正数。另外,unsigned若省略后一个关键字,大多数编译器都会认为是unsigned int。
char c = 0xc9;
printf("A:c = %2x\n",(unsigned char)c);
printf("B:c = %2x\n",c & 0xff);
printf("C:c = %2x\n",c);
输出如下:
A:c = c9 B:c = c9 C:c = ffffffc9
可以看到:
把c转换成unsigned char打印是正确的。视作情况A。
把c与 0xff做&操作后打印正确。视作情况B。
对c不做任何处理,则问题复现了,打印出ffffffc9。视作情况C。
现在来逐一分析解释ABC三种情况。
首先我们必须知道,printf()函数的%x(X)输出的是Int型别的16进制格式。所以char型别的c变量会被转换成Int型别。
其次,我们的知道计算机是用补码表示数据的。关于原码,反码,补码的知识请自行充电。
情况C:
c的补码:11001001(0xc9)。
c的反码:11001000(0xc9)。
c的原码:10110111(0xc9)。
因为char型别是带符号的,所以最高位的1这里视为负号。
把c转换成Int型别 char -----> Int
Int_c的原码:10000000 00000000 00000000 00110111(把c原码的最高位1 提到最高位。其余高位补0)。
Int_c的反码:11111111 11111111 11111111 11001000
Int_c的补码:11111111 11111111 11111111 11001001(0xffffffc9)。
所以打印出来看似诡异的值其实是合情合理的。如何避免?看AB情况。
情况B:
我们在情况C的基础上将c与0xff做&操作。
Int_c的补码:11111111 11111111 11111111 11001001(0xffffffc9)。
&
00000000 00000000 00000000 11111111
最终结果为: 00000000 00000000 00000000 11001001(0xc9)。
情况A:
c的补码:11001001(0xc9)。
c的反码:11001001(0xc9)。
c的原码:11001001(0xc9)。
这里强制转换c为unsigned char型别。因此最高位的1不是正负号
把c转换成Int型别 char -----> Int
Int_c的原码:00000000 00000000 00000000 11001001(把c原码的最高位1 提到最高位。其余高位补0)。
Int_c的反码:00000000 00000000 00000000 11001001
Int_c的补码:00000000 00000000 00000000 11001001(0xc9)。
因此打印正常。
以32位机为例,int 分为无符号 unsigned 和有符号 signed 两种类型,默认为signed。二者的区别就是无符号类型能保存2倍于有符号类型的数据。32位下,signed int 的表示范围为:-2147483648 ~ 2147483647 (最高位做符号位)。unsigned int 的表示范围为:0 ~ 4294967295 (不保留符号位)。我们都知道,两个不同的数据类型在进行混合使用时,会自动进行类型转换。其转换原则就是:向着精度更高、长度更长的方向转换。也就是我们平常见到的 char 转为 int ,int 转为 long,float 转为 double . etc.