• 有符号数和无符号数


    在计算机中,数值类型分为整数型或实数型,其中整型又分为无符类型或有符类型,而实型则只有符类型。 字符类型也分为有符和无符类型。在程序中,用户可以自己定义是否需要一个非负整数;

    一、无符号数和有符号数的表示方式

    以一个字节(char类型)为例:若想要表示正负号,一般需要一个位来标记,如取最高代表正负号,则有符号和无符号的数值最大值对比如下:

    1 有符号:0111 1111 = 2^6+2^5+2^4+2^3+2^2+2^1+2^0     = 127; ==> 范围是 -128 ~ 127
    2 
    3 无符号:1111 1111 = 2^7+2^6+2^5+2^4+2^3+2^2+2^1+2^0 = 255;==> 范围是   0  ~ 255

    由上可看出:

    1. 同样一个字节大小,有符号和无符号表示的范围不同,但个数相同均为256个;
    2. 单纯这样存储是存在问题:
      1. 针对有符号数,0在内存中存在两种方式即+0和-0;
      2. 针对负数的大小,-1(1000 0001)和-2(1000 0010)单纯的从二进制存储来比较,应该是-2(1000 0010) > -1(1000 0001)这与实际逻辑不吻合;

    二进制补码避免了这个问题,这也是当今最常用的系统存储方式:即高位段0代表正数,1代表负数,表示正数为原码,而表示负数的方式采用:补码 = 反码+1

    PS:

    原码:一个整数,按照绝对值大小转换成的二进制数,最高为为符号位,称为原码。

    反码: 将二进制除符号位数按位取反,所得的新二进制数称为原二进制数的反码。 正数的反码为原码,负数的反码是原码符号位外按位取反。取反操作指:原为1,得0;原为0,得1。(1变0; 0变1)

    补码: 反码加1称为补码。

    1 例如针对有符号数的±1内存存储形式为:
    2 +10000 0001(原码)     ==> 0000 0001
    3 -11111 1110(反码) + 1 ==>  1111 1111
    4 -21111 1101(反码) + 1 ==>  1111 1110
    5 这样做也符合了正常逻辑:-1 > -2....

    注意: 单纯从一个字节8位二进制存储上来看,1111 1111 既可以表示有符号的-1又可以表示无符号的255

     1 //最高位是否表示正负号示例
     2 //0x80 = 1000 0000 &按位与操作,按位比较两数字,相同为1,不同为0;
     3 #include <stdio.h>
     4 int main()
     5 {
     6     char c = 1;
     7     short s = -2;
     8     int i = 3;
     9     printf("%d
    ", ( (c & 0x80) != 0) );           //0
    10     printf("%d
    ", ( (s & 0x8000) != 0) );         //1
    11     printf("%d
    ", ( (c & 0x80000000) != 0) );     //0
    12     return 0;
    13 }

    二、迷惑人的有符号下无符号数的比较操作

    无符号数与有符号数间的比较

     1 #include <stdio.h>   
     2 int main() 
     3 {
     4     int a = -1; 
     5     unsigned int b = 1; 
     6     if(a > b) 
     7         printf("a > b, a = %d, b = %u
    ", a, b); 
     8     else 
     9         printf("a <= b, a = %d, b = %u
    ", a, b); 
    10     return 0;
    11 } 
    12 //print: a > b, a = -1, b = 1

    当执行一个运算时(如这里的a>b),如果它的一个运算数是有符号的而另一个数是无符号的,那么C语言会隐式地将有符号 参数强制类型为无符号数,并假设这两个数都是非负的,来执行这个运算。这种方法对于标准的算术运算(四则运算)来说并无多大差异,但是对于像<和>这样的比较运算就可能产生非直观的结果。

    对大多数C语言的实现,处理同样字长的有符号数和无符号数之间的相互转换的一般规则是:数值可能会改变,但是位模式不变。也就是说,将unsigned int强制类型转换成int,或将int转换成unsigned int底层的位表示保持不变。

    示例中

    -1(变量a的值:1111 1111 1111 1111 1111 1111 1111 1111)这个有符号数强制转换成无符号数(1111 1111 1111 1111 1111 1111 1111 1111= 2^32-1= 4294967295,从二进制存储上来看,无符号数所有位都为1时表示的时最大值)然后再与 1(变量b的值:0000 0000 0000 0000 0000 0000 0000 0001)来进行比较;

    总结:当有符号数遇见无符号数参与计算时,则有符号数进行转换为无符号数

    三、查看验证结果(显示存储的形式)

    为了证明上面所说的内容,写段代码将内存中的存储形式显示出来:代码中函数show_byte,它可以把从指针start开始的len个字节的值以16进制数的形式打印出来。

     1 #include <stdio.h> 
     2 void show_byte(unsigned char *start, int len) 
     3 {
     4     int i = 0; 
     5     for(; i < len; ++i) 
     6         printf(" %.2x", start[i]); 
     7     printf("
    "); 
     8 } 
     9 
    10 int main() 
    11 {
    12     int a = -1;
    13     unsigned int b = 4294967295; 
    14     printf("a = %d, a = %u
    ", a, a); 
    15     printf("b = %d, b = %u
    ", b, b); 
    16     show_byte((unsigned char*)&a, sizeof(int)); 
    17     show_byte((unsigned char*)&b, sizeof(unsigned int)); 
    18     return 0; 
    19 }
    20 /*print:
    21 a = -1, a = 4294967295
    22 b = -1, b = 4294967295
    23  ff ff ff ff
    24  ff ff ff ff
    25 printf函数中,%u表示以无符号数十进制的形式输出,%d表示以有符号十进制的形式输出。通过show_byte函数,我们可以看到,-1与4 294 967 295的底层表示是一样的,它们的位全部都是全1,即每个字节表示为ff。
    26 */

    四、由无符号数值参与减法运算的错误

    1 //求某个数组中前length个元素的和的代码段:
    2 int sum_elements(float a[], unsigned length) 
    3 {
    4     int i = 0; 
    5     int sum = 0; 
    6     for(i = 0; i <= length -1; ++i) 
    7         sum += a[i]; 
    8     return sum; 
    9 }

    因为数据的长度(或个数)肯定是一个非负数,所以把length声明为一个unsigned很合理,计算的数据个数和返回类型也正确。的确如此,但是这都是在length不为0的情况,试想,当调用函数时,把0作为参数传递给length会发生什么事情?回想一下前面我们所说的知识,因为length是unsigned类型,所以所有的运算都被隐式地被强制转换为unsigned类型,所以length-1(即0-1 = -1),-1对应的无符号类型的值为最大值,所以for循环将会循环(4 294 967 295)次,另一方面,当0-1操作结束时数组也会越界,发生错误。

    那么如何优化上面的代码呢?其实答案非常简单,你也可以自己想一想,这里就给出答案吧,就是把for循环改为:for(i = 0; i < length; ++i) 

    1 //比较两个字符串长度:判断第一个字符串是否长于第二个字符串,若是,返回1,若否返回0;
    2 int strlonger(char *s1, char *s2) 
    3 {
    4     return strlen(s1) - strlen(s2) > 0; 
    5 } 

    在Linux下可用man 3 strlen命令查看,strlen函数的原型为:

    size_t strlen(const char *s); 

    该函数原型返回一个数据类型size_t,它被定义在stdio.h文件中,是一种机器相关的无符号类型,他被设计得足够大以便能够表示内存中任意对象的大小,即本质上属于unsigned int,

    另一方面,一个字符串的长度当然不可能为负,这样的定义显然是合理的,但是有时却因为这样,而存在不少的问题,如函数strlonger的实现。当s1的长度大于等于s2时,这个函数并没有什么问题,但是你可以想像,当s1的长度小于s2的长度时,这个函数会返回什么吗?没错,因为此时strlen(s1) - strlen(s2)为负(从数学的角度来解释的话),而又由于程序把它作为unsigned为处理,则此时的值肯定是一个比0大的值,即永远为真,返回1。换句话来说,这个函数只有在strlen(s1) == strlen(s2)时返回假,其他情况都返回真。

     1 //测试无符号数减法
     2 #include <stdio.h> 
     3 #include <string.h> 
     4 
     5 int strlonger(char *s1, char *s2) 
     6 {
     7     return strlen(s1) - strlen(s2) > 0; 
     8 } 
     9 
    10 int main() 
    11 {
    12     char s1[] = "abc"; 
    13     char s2[] = "cd"; 
    14     if(strlonger(s1, s2)) 
    15         printf("s1 is longer than s2, s1 = %s, s2 = %s
    ", s1, s2); 
    16     else 
    17         printf("s1 is shorter than s2, s1 = %s, s2 = %s
    ", s1, s2); 
    18 
    19     if(strlonger(s2, s1)) 
    20         printf("s2 is longer than s1, s2 = %s, s1 = %s
    ", s2, s1); 
    21     else 
    22         printf("s2 is shorter than s1, s2 = %s, s1 = %s
    ", s2, s1); 
    23     return 024 }

    若符合正常逻辑则修改strlonger函数改为:

    1 int strlonger(char *s1, char *s2) 
    2 {
    3     return strlen(s1) > strlen(s2); 
    4 }

    这样就可以利用两个无符号数进行直接的比较,而不会因为减法而出现负数(数学上来说)而影响比较结果。

     总结:当无符号数参与计算时,尽量避免减法,代之为比较逻辑

    五、无符号使用的建议

    1.  除一定非使用无符号类型时,才进行使用unsigned类型;
    2.  使用unsigned类型时避免比较运算和减法运算操作;

    六、补充部分:整数的溢出

    从第二项:迷惑人的有符号下无符号数的比较操作的分析部分可以看出:对于一个固定长度的无符号数:

    1 MAX_VALUE +1 == MIN_VALUE
    2 MIN_VALUE –1 == MAX_VALUE

    可以把无符号数看作是汽车的里程表,当达到它能表示的最大值时,会重新从起始点开始;即:

    1. 对于无符号数(unsigned int)超过最大值时,变量会从0开始;
    2. 对于有符号数(               int)超过最大值时,变量会从-2147483648开始;

    注意:溢出的行为是未定义的行为,也就是说,在程序运行时,溢出可能会从起始点开始,也可能是其它的情况;

  • 相关阅读:
    真-关闭win10安全中心(windows defender)
    HOOK IDT频繁蓝屏(Window 正确 HOOK IDT)
    windows 驱动开发 MDL 内核层 用户层共享内存
    C++将时间格式转换成秒数
    关于HOOK KiPageFault需要用到自旋锁研究
    提高VS2010/VS2012编译速度
    apache2.2服务无法启动 发生服务特定错误:1 的解决办法 (windows服务错误 日志查看方法)
    内核同步对象
    C++/MFC-线程优先级
    python xml转excel
  • 原文地址:https://www.cnblogs.com/Shuqing-cxw/p/9276260.html
Copyright © 2020-2023  润新知