我们在该文中分析了uchar/char的范围及越限情况。
期间有个现象在这里分析以下:
我们发现,uchar以十六进制的形式输出是FF,但是char类型以十六进制输出的是ff ff ff ff
uchar ch1 = 0xFF;
char ch2 = 0xFF;
printf("%d,%d\n%X,%x\n",ch1,ch2,ch1,ch2);
printf("%d",sizeof(int));
分析:编译器是32位的,所以是 ff ff ff ff,共32位, 4个字节。又因为,对于char类型,ff(1111 1111),首位是1,故为负数,所以,拓展成4个字节后,前面全是1
补符号位原则:
如果数据类型为有符号的(比如char类型),且数据最高位是1,则转换成多字节数时,高位全都补1,即十六进制数都是F。
包括数据为负数时,比如 int = -32768;
如果数据类型为无符号的(比如uchar类型)高位全都补0,即十六进制数都是0,默认是不写的。
此外,char与多字节的位运算(即,强转成多字节时),以及如下的位移运算也算牵扯到补符号位的问题。
数据存储在计算机中的是二进制,如果是正数存的是原码(也可以理解为补码,因为正数的原码和补码相同),如果是负数存的是补码,归纳为,
1)数据存在计算机的都是补码;
2)整型为十六进制数,则表示的就是补码;//char ch = 0xaa;
3)整型数据为十进制数,则需要先转换成二进制补码的形式(十六进制);
4)同一个数存储在相同位数的数据类型中,其存储的二进制(十六进制)是完全一样的,不会发生丢位,最终转换为十进制数,或者扩展为多字节数是多少,是由我们要解析的形式决定的。
// char ch1=-1; uchar ch2 = -1; 二者存储的二进制都是 FF;
//printf("%x,%x",ch1,ch2);//输出的是FF FF FF FF; FF
5)数据转换过程: uchar ch =-1; printf("%x,%d,%u",ch,ch,ch);
- 在32位机上编译: -1默认的是int型,转换为二进制:FF FF FF FF
- uchar位一个字节,故截取低八位,为FF;
- 按照补符号位原则,根据%x,%d,%u的规则,解析输出;
或者使用如下的思路:
6)printf %d相当于数据类型转换为int %u 相当于转换为 unsigned int
7)初始化的整型常量,默认是 int类型的。
举例:
//十进制数显示问题
uchar ch1 =-1;
printf("%x,%d,%u\n",ch1,ch1,ch1);
char ch2 =-1;
printf("%x,%d,%u\n",ch2,ch2,ch2);
printf("*****************\n");
//十六进制数显示问题
uchar ch3 =0xAA;
printf("%x,%d,%u\n",ch3,ch3,ch3);
char ch4 =0xAA;
printf("%x,%d,%u\n",ch4,ch4,ch4);
printf("*****************\n");
//数据类型强转后数据显示问题
uchar ch5 = -1;
printf("%x,%d,%u\n",ch5,ch5,ch5);
char ch6 = -1;
printf("%x,%d,%u\n",(uchar)ch6,(uchar)ch6,(uchar)ch6);
printf("*****************\n");
//不同数据类型相互赋值后的显示问题
uchar ch7 = -1;
char ch8 = -1;
char ch9 = ch7;
uchar ch10 = ch8;
int ch11 = ch7; //原数据类型无符号,值不受影响
uint ch12= ch7; //原数据类型无符号,值不受影响
int ch13= ch8; //短字节扩展为长字节需要补符号位
uint ch14 = ch8; //短字节扩展为长字节需要补符号位
printf("%x,%d,%u\n",ch7,ch7,ch7);
printf("%x,%d,%u\n",ch8,ch8,ch8);
printf("%x,%d,%u\n",ch9,ch9,ch9);
printf("%x,%d,%u\n",ch10,ch10,ch10);
printf("%x,%d,%u\n",ch11,ch11,ch11);
printf("%x,%d,%u\n",ch12,ch12,ch12);
printf("%x,%d,%u\n",ch13,ch13,ch13);
printf("%x,%d,%u\n",ch14,ch14,ch14);
//从下图结果可以看到值为-1时,无论数据类型是uchar还是char类型, 其十六进制数都是FF(不考虑符号位),这是因为,-1这个常量默认的数据类型是int
//判断数据转换,将原数据转换成补码,此时再理解。
short si = -32768;
unsigned short usi=si;
int i = si;
unsigned int ui=usi;
unsigned int ui2=-32768;//本身就是int/uint格式,不需要补位
unsigned int ui3=0xAA;
printf("short: decimal:%d Hexadecimal:%x\n",si,si);//扩充位数
printf("unsigned short: decimal:%d Hexadecimal:%x\n",usi,usi);//扩充位数
printf("int: decimal:%d Hexadecimal:%x\n",i,i);//没有扩充位数
printf("unsigned int: decimal:%d Hexadecimal:%x\n",ui,ui);//没有扩充位数
printf("unsigned int: decimal:%d decimal:%u Hexadecimal:%x\n",ui2,ui2,ui2);//没有扩充位数
printf("unsigned int: decimal:%d Hexadecimal:%x\n",ui3,ui3);//没有扩充位数
unsigned int ui2=-32768;//本身就是int/uint格式,不需要补位
printf("unsigned int: decimal:%d decimal:%u Hexadecimal:%x\n",ui2,ui2,ui2);//没有扩充位数
注意:当不需要补符号位时(如上述情况),且数值不超过uint的最大值,也没有超过int的最小值时,是可以正常显示的!!!!
short svalue= -32769;
printf("%d,%x\n",svalue,svalue);
规律:
使用单字节表示负数的补码与使用多字节表示的补码相比,多字节时,只要在多的位数补1即可。
例如:
1)未越限的情况
//-127的补码:
单字节://-128+1
1000 0000 + 1 = 1000 0001
2字节://取反+1
1000 0000 0111 1111 取反+1: 1111 1111 1000 0001
2)越限的情况
//-130的补码:
单字节://-128-2 //想象圆圈循环 逆时针转2个----》126(或者利用加上模的方法 -130 +256==126)
1000 0000 - 2 = 1000 0000 - 0000 0010 == 0111 1110
2字节://取反+1
1000 0000 1000 0010 取反+1: 1111 1111 0111 1110
解释:无论是否越限,此负数使用单个字节足以表示出来,故如果使用多字节表示,高字节(除符号位)都是0,取反后都是1.
越限后的循环,实际上非越限情况下的截断。
//对于越限的情况,另一种思路是,
1)可以按照高减低加的原则,先转换成正常值,然后,再看当前的数据类型是有符号的,还是无符号的,扩展位数是否需要添加符号位。
2)都按照正常数据来理解,如果当前数据类型是有符号的char,要以有符号形式输出则不会有问题,如要以无符号形式输出则值会发生变化;
如果当前数据类型是无符号的uchar,无论是以无符号形式还是有符号形式输出,值都相同,没有问题。
总结:
原则1:、数据类型中,数据越限情况的分析,遵循高加低减的原则。
原则2:以上大部分的例子(printf输出),本质上都是不同数据类型赋值转换的分析,此时要注意:
1)原数据类型的与目标数据类型的位数是否发生了变化;--->注意补符号位。
2)原数据类型与目标数据类型符号是否发生变化;
3)原数据类型是否是有符号的的类型
4)原数据类型是否发生了越限,如果是unsigned int 类型发生了越限,针对int型是否越限(因为默认的数据类型是int),如果是非unsigned int 的其他类型,参考原则1.