“整数数据类型”+“各种类型的编码”+“类型转换” 的细节过程

总感觉数据类型和编码过程的概念在脑子里比较模糊。。。。所以细细来研究一下很有必要了!

 

 

(一)理解整数数据类型的含义和编码方法

 

C 中的整数类型可以分为有符号整数和无符号整数:

有符号整数类型(5种):                                               同义词

                                signed char ----------------------------

                                int------------------------------------------signed,signed int

                                short---------------------------------------short int, signed int, signed short int

                                long----------------------------------------long int, signed long, signed long int

                                long long----------------------------------long long int, signed long long, signed long long int

无符号整数类型(6种):

                                _Bool---------------------------------------bool

                                unsigned char---------------------------

                                unsigned int------------------------------unsigned

                                unsigned short---------------------------unsigned short int

                                unsigned long----------------------------unsigned long int

                                unsinged long long--------------------- unsigned long long int

也就是说,有本质上区别的整数类型有5+6一共11种。

在32位的机器上(比如我自己的机器Intel P7350),也就是说,“虚拟内存空间”的地址范围是:

0000    0000    0000    0000    0000    0000    0000    0000    (最小值)

1111    1111    1111    1111    1111    1111    1111    1111    (最大值)4294967295(类型是无符号的)

1           2           3           4            5           6           7           8           (该行计数,不然眼都花了!)

所以在32位地址空间大约为4GB,而我的机器内存才2GB。其实这就是计算机中“字长(word size)”的概念:字长就是计算机内存地址能编码的最大比特位(这里是32)。

整数数据类型是怎样编码的呢?

无符号类型的编码很明确

无论是无符号还是有符号的字符类型都有8位(1个字节)

对于其他的数据类型,C只定义了最小的存储空间大小。short至少为2个字节,long至少占4个字节,long long至少占8个字节。这里的“至少”的含义short可以是>2个字节的,long可以>4个字节的,long long 可以大于8个字节的。各自类型实际占了多少类型,这个到底取决于CPU还是编译器呢? 我来考察一下我的编程环境吧。(我用的CPU是Intel P7350,编译是GCC4.1.2

)下面是我的测试程序:

  1 #include<stdio.h>
  2 int
  3 main()
  4 {
  5         printf("sizeof(short)=%d/n", sizeof(short));
  6         printf("sizeof(int)=%d/n", sizeof(int));
  7         printf("sizeof(long)=%d/n", sizeof(long));
  8         printf("sizeof(long long)=%d/n", sizeof(long long));
  9         printf("sizeof(unsigned int)=%d/n", sizeof(unsigned int));
 10         printf("sizeof(unsigned short)=%d/n", sizeof(unsigned short));
 11         printf("sizeof(unsigned long)=%d/n", sizeof(unsigned long));
 12         printf("sizeof(unsigned long long)=%d/n", sizeof(unsigned long long));
 13
 14         return 0;
 15 }


运行的结果是:

 

 

[root@localhost ~]# ./a.out
sizeof(short)=2
sizeof(int)=4
sizeof(long)=8
sizeof(long long)=8
sizeof(unsigned int)=4
sizeof(unsigned short)=2
sizeof(unsigned long)=8
sizeof(unsigned long long)=8

 

可以看到long 与long long 的长度是一样的,并且有符号与无符号是等长的!

尽管不同的环境下,各种数据类型的长度不是一成不变的,但是遵循下面的规则:

 sizeof(short)  <=  sizeof(int)  <=  sizeof(long)  <=  sizeof(long long)

 

其实,整数类型(这里不包含布尔类型11-1=10)声明的本质是告诉编译器2个信息:(1)在内存中要取的位数   (2)第一位是不是符号位。下面看看在我的环境下的整数类型编码情况:

现介绍一下3个基本概念:(注意:原码,反码, 补码是无符号的时候都是一样的,这样的编码主要是针对有符号而言的!)

原码--------------符号位+实际二进制值,如-3原码1000 0011 (以8位为例)。

反码--------------原码(不包含符号位)取反      -3反码1111 1100

补码--------------反码+1                                  -3补码 1111 1101

所以,有符号的数的补码比反码大1。

所以对于有符号的数的编码方法就有了两种:

(1)one's complement (反码表示法)

(2)two's complement ( 补码表示法)

现在有一个问题:为什么不直接用原码表示?主要是为了解决“+0” 和“-0”的问题!我们要把“-0”给变成其他数!

如果用原码表示8位有符号数:1 1111111(-127)--------1 0000000(-0)~0 0000000(+0)----------0 1111111(+127)

如果用反码表示8位有符号数:1 0000000(-127)--------1 1111111(-0)~0 1111111(+0)----------0 0000000(+127)

如果用补码表示8位有符号数:1 0000001(-127)--------1 0000000(-0)~0 0000000(+0)----------0 0000001(+127)

可以看出:如果把补码的“-0”放在最前面(-127)前面,正好可以表示-128!再把1的补码(0 1111111)与127的补码位置掉换!这样就是从 1 0000000(-128)~0 1111111(127)连续的序列。(在这里真是佩服编码的设计者!很巧妙)

而反码的数据处理就比较麻烦了。补码是从-127到127连续上升的,除了在“-0”、“1”、“127”的位置。只要进行3个数字的移动就可以确保出现递增序列。反码只需要在补码的基础上减去1得到的序列! 反码没有补码来的直观简洁,所以几乎现在所有的机器都都采用补码来表示有符号数!

 

 

(二)类型转换的过程细节-细细揣摩

无符号之间的转换很简单,只要改变空间字节大小就OK。

重点是有符号与无符号之间的转换问题:

        强制类型转换的本质是:参数位表示不变,变化的是它的解释方法。

下面的程序证明了这一点:

  1 #include<stdio.h>
  2 int
  3 main()
  4 {
  5         int i = -3;
  6         unsigned j = (unsigned)i;
  7         printf("%#x:%d/n", i, i);
  8         printf("%#x:%u/n", j, j);
  9         return 0;
 10 }
运行结果:

0xfffffffd:-3
0xfffffffd:4294967293
i和j的位表示相同,变化了的是它的解释方法。

 

最后总结一些注意点:

(1)在赋值的时候,如果要表达无符号的意图,那么就要在数的后面加“U”或“u”,否则将当成是有符号来处理。

(2)在一个表达式中同时出现有符号和无符号的时候的处理办法是:有符号数将被强制的转换成了无符号数了。所以将出现一些奇怪的事情。

 如果谁能找出下面的BUG,我想他会非常的兴奋,起码我是这样。。。。。

 

  1 #include<stdio.h>
  2
  3 float sum_float( float a[], unsigned length )
  4 {
  5         int i;
  6         float sum = 0;
  7
  8         for( i = 0; i <= length - 1; i++ ){
  9                 sum += a[i];
 10         }
 11
 12         return sum;
 13
 14 }
 15 int main()
 16 {
 17         float a[4] = { 1.1, 2.2, 3.3, 4.4 };
 18         printf("%f/n", sum_float (a, 0));
 19         return 0;
 20 }

 

(当length=0的时候,出现内存溢出的错误,因为语句i<=length-1中的length为无符号数,所以i,1都将被转换成无符号数,这也不会有问题,这里的BUG出现在length = 0的情况。按道理返回的是0.00..但是这里得到运行结果是“Segmentation fault(段错误)”,原因在于length-1的结果-1将被转换成无符号数,然而unsigned类型并不能存放这么大的数,所以出现了内存溢出的错误!)

如果把for( i = 0; i <= length - 1; i++ )换成for( i = 0; i < length ; i++ )就会避免这样的BUG!但这也不是个好办法,避免这样的错误的办法就是绝对不要用无符号的数。这不能说明无符号数没有用途,在有的地方无符号数是非常有用的!比如,把字看成是位的集合而没有任何数字意义事,还有当实现模运算和多精度运算时,无符号数也非常有用。

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值