C语言笔记之数据类型(一)
在《计算机眼里的数字》这篇文章中,我曾提到,字节是计算机最小的可寻址的单位,地址对应的是一个个字节,而不是字节的每个位。这样编址的原因很简单——单个的位所能表示的信息量太少了,只有两种状态0和1,只有把足够多的位组合起来才能表示足够丰富的信息。那么为什么一定要是8呢?因为大家都这么做。。。好吧,肯定一定的历史原因,我就不深究了。
但是,即使是8个位组成的字节,其所能表示的信息量仍然是有限的,因为最多只有256中状态组合,如果用每种状态对应0~255之间的数字的话,那么就无法表示256这个数。这时候只好用两个字节来表示大于255的数。同样的道理,当数字大到超过两个字节所能表示的数的极限后,就用四个字节。。。是的,你也发现了,字节数目总是翻倍增长,为什么不用3个字节呢?甚至,为啥不用1.5个字节呢?对于前者,是因为考虑到对齐的原因,这其中有太多东西要说,我就不展开了;而后者,前面其实说到了,因为没有位的编址,无法深入到字节内部把数据揪出来。。所以,存储肯定会有一定的浪费,在所难免。
上面罗嗦了那么多,其实只是为了引出本文的猪脚——C语言数据类型,为什么C语言要分那么多类型呢?因为对于不同大小的数,所需要的存储空间大小不同。如果都用4个字节存储,那么肯定不用分数据类型,但是好浪费哦~所以,本着节省内存的考虑,数据类型就诞生了。C的数据类型分为基本数据类型和复合数据类型,后者只是前者的某种组合。基本数据类型按照其在计算机中的存储方式又分为整数类型和浮点数类型。
一、整数类型
整数类型包括char、short、int、long、long long,它们没有小数部分。char虽然是字符类型,但是由于存储的是ASCII码,本质上也是按整数存储的,所以归为这一类。不同的整数类型具有不同的字节数,从而所占用的存储空间不同。然而,这一点是不确定的,准确的字节数依赖于具体的机器和编译器——机器不仅分品牌,还有32位和64位之分。下面的表格给出不同类型所占有的典型的字节数(来源《深入理解计算机系统》):
类型 | 32位机器 | 64位机器 |
char | 1 | 1 |
short | 2 | 2 |
int | 4 | 4 |
long | 4 | 8 |
long long | 8 | 8 |
char * | 4 | 8 |
float | 4 | 4 |
double | 8 | 8 |
注意,上表只是典型值,以32位为例:在有的机器上,short和int都是2个字节,long是4个字节;而有的机器上,short是2个字节,而int和long是4个字节。C语言仅仅规定:short <= int <= long,然后char是1个字节。不过,对于大部分机器,上表够用了。要查看自己机器上每种类型所占的字节数,请用sizeof(类型)来查看。
对于整数类型,C还用了signed和unsigned来修饰它们,以获得有符号数和无符号数,缺省时,认为是有符号数。正如在《计算机眼里的数字》中提到的那样,有符号数和无符号数的二进制表示可能是一样的,区别仅仅是解读方式。
有了各种类型之后(即字节数确定之后),就可以规定它们所表示的数的范围。下表是32位机器各种类型的典型取值范围:
signed | unsigned | |
char | -128 ~ 127 | 0 ~ 255 |
short | -32768 ~ 32767 | 0 ~ 65535 |
int | -2147483648 ~ 2147483647 | 0 ~ 4294967295 |
long | -2147483648 ~ 2147483647 | 0 ~ 4294967295 |
long long | -9223372036854775808 ~ 9223372036854775807 | 0 ~ 18446744073709551615 |
这些界限值可以通过包含limits.h头文件加以查看,比如对于int值的各种范围,可以通过打印INT_MAX INT_MIN
UINT_MAX UINT_MIN 来查看。
我们用几个例子来看一下相同类型之间signed和unsigned的转换。
1、将signed强制转换成unsigned:
<span style="font-size:18px;">#include <stdio.h> int main(void) { int a = -1; unsigned int b = (unsigned int)a; printf("a = %d, b = %d\n", a, b); printf("a = %u, b = %d\n", a, b); printf("a = %d, b = %u\n", a, b); printf("a = %#x, b = %#x\n", a, b); return 0; } </span>
运行结果如下:
<span style="font-size:18px;">a = -1, b = -1 a = 4294967295, b = -1 a = -1, b = 4294967295 a = 0xffffffff, b = 0xffffffff</span>
可以明显的看出,将一个负数强制转换为无符号数,并没有改变其位模式(二进制表示),它仍然按照原来的模样存储,第四行的结果证明了这一点;而前三行的结果表明,即使不做signed到unsigned的强制类型转换,只需要在打印时改变一下输出格式,就能达到同样的效果。(这里我开始怀疑把一个变量声明为unsigned有啥意义?)
而将一个有符号的正数转换为同类型的无符号数又如何呢?
2、将unsigned强制转换成signed:
<span style="font-size:18px;">#include <stdio.h> int main(void) { unsigned short a = 32767; short b = (short)a; printf("a = %u, b = %u\n", a, b); printf("a = %u, b = %d\n", a, b); printf("a = %d, b = %u\n", a, b); printf("a = %#x, b = %#x\n", a, b); return 0; } </span>结果如下:
<span style="font-size:18px;">a = 32767, b = 32767 a = 32767, b = 32767 a = 32767, b = 32767 a = 0x7fff, b = 0x7fff </span>可以看出,当一个非负数处于0~TMax(TMax代表某一类型有符号数的最大值)的范围时,无论把它的二进制表示解读成signed还是unsigned,结果都是一样的。这是因为,处于这部分范围内的数,转换成二进制时,最高为都为0,如果按signed解读,它是一个正数,数字部分就是7fff,;而按unsigned解读,07fff == 7fff,结果总是一样的。
那如果正数的范围超过了TMax呢?
<span style="font-size:18px;">#include <stdio.h> int main(void) { unsigned int a = 2147483648u; int b = (int)a; printf("a = %u, b = %d\n", a, b); printf("a = %d, b = %u\n", a, b); printf("a = %#x, b = %#x\n", a, b); return 0; } </span>
结果:
<span style="font-size:18px;">a = 2147483648, b = -2147483648 a = -2147483648, b = 2147483648 a = 0x80000000, b = 0x80000000</span>可以看到,底层的二进制表示仍然是一致的,只是解读方式发生了变化:由于最高位是1,所以解读成signed时,是一个负数;解读成unsigned仍然是正数,结果不同。
注意:因为变量分为signed和unsigned,对应的常量也要分为signed和unsigned;类型前没有修饰时,默认为signed,对应的,一个常量数字默认为signed,即有符号数。如果希望一个常量数字被当成无符号数,就要在其末尾添加字母‘u‘或‘U’。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。