将晦涩难懂的技术讲的通俗易懂
分类: C/C++
2014-04-22 00:05:19
写在前面:本来因为一个朋友问我为什么可以给unsigned int赋值负数,我打算写一篇关于解释unsigned的文章。但是写的过程中发现有很多地方需要涉及其他的知识点,特别是关于整型提升和算数转换。所以就翻了一下之前看过的书,做了一个总结,感觉自己又学到了不少。由于我不是写教科书,有些用语和描述难免不够准确。另外,由于本人能力有限,有错之处在所难免,希望各位看到此文的朋友如果发现有什么错误请留言指正,或发邮件交流。
(电子邮箱: ——转载请注明出处,lvyilong316
注意:double和float都不可用unsigned,short修饰,另外float还不可用long修饰
首先我们先看一段程序:
int _tmain(int argc, _TCHAR* argv[])
{
int a=2,b=-5,r;
unsigned int ua=2,ub=-5,ur;
bool bo;
printf("a: %d,ua: %d\n",a,ua);
printf("a: %u,ua: %u\n",a,ua);
printf("b: %d,ub: %d\n",b,ub);
printf("b: %u,ub: %u\n",b,ub);
bo=(b==ub);
printf("bo: %d\n",bo);//1
bo=(ub>a);
printf("bo: %d\n",bo);//1
r=a+ub;
printf("a+ub: %d,a+ub: %u\n",r,r);
ur=a+ub;
printf("a+ub: %d,a+ub: %u\n",ur,ur);
return 0;
};
首先,查看数据的内存表示(十六进制),发现相同的赋值int和unsigned int的内存表示
是一样的。但是十进制不同,也就是说程序对相同内存数据的解释不同。
十六进制 十进制
那么给unsigned 赋于负值是什么情况?从上面也能看到对于内存来说其实和signed是一样的。我们知道计算机是以补码表示数字,所以-5无论负值给谁,他在内存中的表示都是固定的,而signed和unsigned仅仅表名对与其所修饰的变量,这段内存是如何解释,即把最高位当做符号位还是最高数值位。
从程序输出的前四行可以看到结果也完全相同。我们知道:和signed与unsigned的作用相同,%d,%u的不同就在于对内存的解释不同,前者将内存中的数据看成有符号的,后者看成是无符号的。(将signed int使用%u输出,实质就是相当于对这块内存的重新解释)。
结论:相同赋值的int和usigned int在内存中的表示是一样的,signed和unsigned只是告诉程序对内存的不同解释方式,前者按照有符号数解释(考虑符号位),后者按无符号数(不考虑符号位)解释,这一点和%d,%u的作用是一样的。
接下来看输出的后面四行,b==ub:好理解,两个数都属于int,signed不同算数转换为unsigned,内存相同,解释相同(同为unsigned),所以相等。我们看下面一个。ub>a(-5>2):这是什么情况?同样还是算数转换,都按unsigned解释,当然-5对应的数大了。
继续往下看,r=a+ub;这个又做了什么?记住计算机所有加减运算都是按照补码进行,也就是我们平时说的有符号数。所以无论是signed int之间运算,还是unsigned int之间运算,还是signed和unsigned之间运算,都是对用的有符号数运算,再根据结果是赋值给signed还是unsigned做相应解释,但无论赋值给signed类型还是unsigned类型,运算结果的内存表示是一样的。
接下来看下面一段程序:
int _tmain(int argc, _TCHAR* argv[])
{
char a;
unsigned char ua;
int i,j;
unsigned int ui,uj;
a=200;
ua=a;
printf("a: %d, %u, %x\n",a,a,a);
printf("ua: %d, %u, %x\n",ua,ua,ua);
i=a; j=ua;
ui=a;uj=ua;
};
我们知道char的表示范围在-128-127,unsigned char在0-256;所以我们看看给char赋值一个超出其表示范围的数(200)会是什么效果。
首先,看下a和ua的内存表示,如下图:
这和预想一样,相同赋值的a和ua的内存表示也相同。我们再看下输出:
首先,第一列(按照十进制输出),对于a(char型),200超过了其表示范围,因为200对应的二进制表示为1100 1000,已经占用到了最高位(符号位)。按十进制输出时把最高位符号位(1)解释为负数。后面100 1000正好是56。注意:变量的输出与变量是unsigned还是unsigned无关,而取决于%d或u%等对内存的再解释。
第二列是什么情况?没错,类型提升。a按照有符号数扩展,高位补符号位,ua按照无符号扩招,高位补0。?我们再看下面几句的赋值情况:
i=a; j=ua;
ui=a;uj=ua;
对比i和ui,可以得出结论:有符号类型(char)无论向有符号类型(int)还是无符号类型(unsigned int)扩展,都会按照有符号数的扩展规则(高位补符号位)。
对比j和uj,可以得到结论:无符号类型(unsigned char)无论向有符号类型(int)还是无符号类型(unsigned int)扩展,都会按照无符号数的扩展规则(高位补0)。
这就得到有符号数和无符号数的有一个不同点——扩展规则不同。
练习:
unsigned char a = 0xA5;//a=1010 0101
unsigned char b = ~a>>4;//b=1111 0101,注意a要先做整型提升,~的优先级大于>>
unsigned char c = a>>4;//c=0000 1010
c = ~c;//c=1111 0101
unsigned char d =~a;//d=0101 1010
d = d>>4;//d=0000 0101 也要整形提升扩展
unsigned char e = ( (~a)>>4);//e=1111 0101
看下面例子:
int d = -1;
所以,不要因为无符号数不存在负值而用它表示数量(如年龄、国债等), 尽量使用int之类的有符号数,这样在混合运算中, 这样就不必担心边界情况(如-1被翻译为非常大的正数)。 只有在使用位段和二进制掩码时,才使用无符号数。 应该在表达式中使用强制类型转换,使所有的操作数均为有符号数或无符号数, 这样就不必由编译器来选择结果的类型。
3.1 unsigned 到底有什么用
3.2 signed和unsigned的另一区别
3.3 慎用unsigned
if (d <= sizeof(arr)/sizeof(arr[0]))
...
这样的比较语句有问题,sizeof运算符返回无符号数。
if语句在signed int和unsigned int之间测试相等性,
按照前两节讨论的,这里会发生算数转换,也就是有符号的d会被解释成unsigned。这样,-1就变成一个非常巨大的正整数,导致比较结果与预期的不符。 解决的方法是使用强制转换,(int)(sizeof(arr)/sizeof(arr[0]))。