Chinaunix首页 | 论坛 | 博客
  • 博客访问: 313698
  • 博文数量: 82
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 490
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-13 10:58
文章分类

全部博文(82)

文章存档

2018年(2)

2017年(9)

2016年(71)

我的朋友

分类: C/C++

2016-12-11 15:04:00

1.1 原码、反码和补码

1)原码,拿char型变量来说,-88的源码对应的二进制有所不同。对于正数最高位为0,负数最高位为1

[+8] = [00001000]

[-8] = [10001000]

 

2)反码,反码是原码对应的二进制各位取反得到的数。如果原码为正数(不管该变量是有符号型还是无符号型),则反码等于原码;若原码为负数,则反码为原码除符号位以外的位取反。

[+8] = [00001000]= [00001000]

[-8] = [10001000]= [11110111]

 

3)补码,对于正数,补码与原码相同;对于负数补码是反码加1的结果。

[+8] = [00001000]= [00001000]= [00001000]

[-8] = [10001000]= [11110111]= [11111000]

 

1.2 C语言中符号型变量在内存中的存储方式和取值范围

关于无符号型变量的取值范围大家都很清楚,内存中是按原码存储的,符号型变量的正数也是按原码存储。而符号型变量的取值范围负数要比正数多一个,负数是按补码存储的。比如有char型变量a = -8,那么通过16进制打印出来是0x88,还是0xf8呢?对于初学者的你我一定会认为打印的是0x88,但结果却是0xf8,其实答案就是前边提到的-8的补码。

[+8] = [00001000]= [00001000]= [00001000]= [00001000]存储

[-8] = [10001000]= [11110111]= [11111000]= [11111000]存储

 

那么加减法是如何实现的呢?其实就是补码直接相加之后得到新的补码,如果是有符号型,且符号位为1,则该补码-1,再将除符号位以外的位取反即得到原码;其他情况则补码和原码相同。

[+8] + [-8]

= [00001000]+ [11111000]

= [00000000]

= [00000000]存储

= [00000000]

= [0]

 

[+1] + [-8]

= [00000001]+ [11111000]

= [11111001]

= [11111001]存储

= [10000111]

=[-7]

 

[+8] + [-1]

= [00001000]+ [11111111]

= [00000111]

= [00000111]存储

= [00000111]

=[7]

 

问题又来了在符号变量中既然[00000000] 原表示0,那么[10000000] 原表示-0吗?怎么符号型变量有两种表示0的方式,这显然是一个问题。下边我们来计算一下-0补码,我们补码取9位。

[10000000] 原  =  [11111111]反  =  [100000000]

 

可以看出,当截取补码低8位后,[10000000] 原等于[00000000] 补,说明-0确实等于0。这样一来char型的取值范围就变成了-127127[10000000] 原不使用岂不是浪费。下边我们来看看-128的补码究竟是多少。

由于-128要有符号位,所以只能用9位表示原码。

[110000000]=  [101111111]反  =  [110000000]

 

可以看出-0的补码和-128的补码并不相同,但是当只保留低8位时,确实是相同的。由于-128的补码只保留后8位并不会影响计算,所以使用了[10000000] 原来表示-128,即char型变量的取值范围变成了-128127,其它short型,int型道理相同。为了说明[10000000] 原表示-128不影响计算,下边来举个例子。

 

[+127] + [-128]

= [01111111]+ [10000000]

= [11111111]

= [11111111]存储

= [10000001]

=[-1]

 

可以看出得到的结果确实是正确的。下边用c语言验证一下前边所说的一些结论:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <limits.h>

  4. #define T char
  5. void ctobs(T n) {
  6.     const static int size = CHAR_BIT * sizeof(T);
  7.     char ps[CHAR_BIT * sizeof(T)] = {0};
  8.     
  9.     for (int i = size - 1; i >= 0; i--, n >>= 1) {
  10.         ps[i] = (01 & n) + '0';
  11.     }
  12.     ps[size] = '\0';
  13.     printf("%s", ps);
  14. }

  15. int main()
  16. {
  17.     char foo1 = -8;
  18.     char foo2 = 8;
  19.     char foo3 = -128;
  20.     printf("(char)\n");
  21.     printf("%4zd = 0x%02hhx = ", foo1, foo1);
  22.     ctobs(foo1);
  23.     printf("\n%4zd = 0x%02hhx = ", foo2, foo2);
  24.     ctobs(foo2);        
  25.     printf("\n%4zd = 0x%02hhx = ", foo3, foo3);
  26.     ctobs(foo3);    
  27.     printf("\n");

  28.     unsigned char foo4 = -8;
  29.     unsigned char foo5 = 8;
  30.     unsigned char foo6 = -128;
  31.     printf("\n(unsigned char)\n");
  32.     printf("%4zd = 0x%02hhx = ", foo4, foo4);
  33.     ctobs(foo4);        
  34.     printf("\n%4zd = 0x%02hhx = ", foo5, foo5);
  35.     ctobs(foo5);
  36.     printf("\n%4zd = 0x%02hhx = ", foo6, foo6);
  37.     ctobs(foo6);
  38.     printf("\n");
  39. }

结果:gcc version 4.8.1 (GCC)

  1. [root@192 util]# ./a.out
  2. (char)
  3.   -8 = 0xf8 = 11111000
  4.    8 = 0x08 = 00001000
  5. -128 = 0x80 = 10000000

  6. (unsigned char)
  7.  248 = 0xf8 = 11111000
  8.    8 = 0x08 = 00001000
  9.  128 = 0x80 = 10000000

从结果来看,我们可以发现赋值时,存储到内存的数据与其类型是否有符号没有关系。拿char型的-8unsigned char型的-8来说,其存储到内存的数据都是[-8] = [10001000]= [11111000]补,而最后打印出来的10进制之所以不一样是因为printf根据符号关系输出的。
 

1.3 符号和非符号变量移位

以前一直没有注意过这个问题,因为一直拿int型的变量来移位,基本上只用了前面的几位,自然没有考虑到符号位的情况。有符号和无符号的变量的区别就在于,当向右移位时,符号变量最高位会补1,而无符号变量最高位会补1,可以从下边这个示例得到验证。

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <limits.h>

  4. #define T char
  5. void ctobs(T n) {
  6.     const static int size = CHAR_BIT * sizeof(T);
  7.     char ps[CHAR_BIT * sizeof(T)] = {0};
  8.     
  9.     for (int i = size - 1; i >= 0; i--, n >>= 1) {
  10.         ps[i] = (01 & n) + '0';
  11.     }
  12.     ps[size] = '\0';
  13.     printf("%s", ps);
  14. }

  15. int main()
  16. {
  17.     char foo1 = -8;
  18.     unsigned char foo2 = 8;

  19.     ctobs(foo1);
  20.     printf(" = ");
  21.     ctobs(foo1 >> 1);
  22.     printf(" = ");
  23.     ctobs(foo1 << 1);
  24.     printf("\n");

  25.     ctobs(foo2);
  26.     printf(" = ");
  27.     ctobs(foo2 >> 1);
  28.     printf(" = ");
  29.     ctobs(foo2 << 1);
  30.     printf("\n");
  31. }
结果:gcc version 4.8.1 (GCC)

  1. [root@192 util]# ./a.out
  2. 11111000 = 11111100 = 11110000
  3. 00001000 = 00000100 = 00010000

从结果中可以看出符号型右移时高位确实补了1,而左移时低位任然补0.

参考资料:

http://blog.csdn.net/zgwangbo/article/details/51590186

http://blog.csdn.net/daiyutage/article/details/8575248


阅读(2001) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~