在C语言中,有很多数据类型,同时,我们数据类型所占空间大小不同,但是我们分的类型就仅仅只是所占大小不同吗?int和float同样占了四个字节,那他们为什么表示的东西不同?那今天就带大家一群探究其中的奥秘。
目录
数据类型介绍
基础数据类型
char //字符数据类型 short //短整型 int //整形 long //长整型 long long //更长的整形 float //单精度浮点数 double //双精度浮点数
上面这些是我们数据类型的基本分类,细分整型还有无符号和有符号的整型,还有指针、空类型和一些构造类型。
整型
char unsigned char //无符号字符整型 signed char //有符号字符整型 short unsigned short [int] //无符号短整型([]内可以省略) signed short [int] //有符号短整型([]内可以省略) int unsigned int //无符号整型 signed int //有符号整型 long unsigned long [int] //无符号长整型([]内可以省略) signed long [int] //有符号长整型([]内可以省略)
指针类型
int *p; char *p; float* p; void* p;
构造类型
> 数组类型 //int[]、char[]等 > 结构体类型 struct > 枚举类型 enum > 联合类型 union
空类型
void
void通常用于我们的函数返回类型、函数的参数、指针类型等等。
我们分成这么多的类型,其意义是开辟的空间不同和以上面视角看待内存,每种类型在我们内存的存储方式是有所不同的。
整型的存储
我们知道,数据类型的不同决定了开辟空间的大小不同和存储方式的不同,那我们的整型是怎么存储在内存中的?就比如
int a=20; int b=-10;
a和b都是int类型的变量,所占大小都是四个字节,那他们在内存空间是怎么样的?怎么储存在我们的内存里面?
原码、反码、补码
在计算机中,能直接识别的是二进制,而我们的二进制有三种表示形式,就是我们的原码、反码、补码。内存中储存的就是我们的补码,不管是哪一种表示方法,都有符号位和数值位两部分组成,符号位就是开头的第一位,0表示正数,而1表示复数,对于无符号的整型则都是数值位。对于二进制而言,正数的原反补三码是相同的,负数的则都不同。
负数的原码
负数的原码就是我们数本身转换成二进制的码,不需要任何的变换,比如我们上面的a和b的原码就是
int a=20; //00000000000000000000000000010100 原码 int b=-10; //10000000000000000000000000001010 原码
负数的反码
负数的反码是将我们的二进制位都取反,1变成0,0变成1,但是我们的符号位是不变的。
int b=-10; //10000000000000000000000000001010 原码 //11111111111111111111111111110101 反码
负数的补码
反码+1得到补码。
int b=-10; //10000000000000000000000000001010 原码 //11111111111111111111111111110101 反码 //11111111111111111111111111110110 补码
对于我们的原反补来说,原码变成补码就是全部取反然后+1,补码变成原码同样,可以全部取反然后+1,也可以先-1,然后在进行全部取反。
那为什么我们的整型在内存中存储的反码呢?
对于计算机而言,使用补码可以将我们的符号位和数值位一起计算,同时,对于计算机而言,cpu是只有加法器的,所以在计算途中,如果是原码的话,计算出的结果是不对的。同时我们还可以通过调试中的内存来查看我们数据在内存中是什么样子的,我们的a和b在内存中是
编辑
编辑
我们看到他们在内存中并不是一串串的二进制编码,那不是二进制这些是什么呢?这些其实就是我们的补码变成了十六进制的形式放在了我们的内存当中,我们可以将补码转成十六进制看看是不是和图片里面的是否相同
int a=20; //00000000000000000000000000010100 补码 // 00 00 00 14 int b=-10; //11111111111111111111111111110110 补码 // ff ff ff f6
我们发现,是十六进制但是缺是倒着放在了我们的内存当中,这是为什么呢?
储存模式
对于计算机而言,我们开内存中开辟的空间都是以字节为单位的,像char的一个字节,int的四个字节,那字节多了,计算机的存放顺序就成为了一个问题,这个时候就出现了两种存储模式,大端和小端。
大端存储
大端存储指的是将我们的数据的低位放在高地址上,数据的高位放在低地址上,看上去是按照顺序存放进去的。比如我们的a和b,在大端存储的计算机上,他们的存储就如下面所示
int a=20; //00000000000000000000000000010100 补码 // 00 00 00 14 //0x00 00 00 14 大端存储 int b=-10; //11111111111111111111111111110110 补码 // ff ff ff f6 //0xff ff ff f6 大端存储
小端存储
小端存储和大端存储时反着来的,指的是将我们的数据的低位放在低地址上,数据的高位放在高地址上,视觉观感上是倒着放但是没有完全倒着放。图片上所示的就是我们的小端储存。
浮点型的存储
那我们知道了整型在内存中的存储了,那浮点型是怎么存储在内存中的呢?请大家和我来一起看一道有意思的题:
int main() { int n = 9; float *pFloat = (float *)&n; printf("n的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); *pFloat = 9.0; printf("num的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); return 0; }
我们来想一下,程序应该输出什么?都是九吗?还是另有答案?那接下来我们就揭晓答案
编辑
我们看到,为什么同样的都是n,因为让打印的不同,结果就会各不相同?并且差异很大?我们上面说到了,数据类型的不同,代表的开辟空间大小的不同和观看内存方式的不同,所以这也直接说明了在内存中,浮点型和整型的存储方式有很大的差距。
浮点型存储规则
我们既然知道了浮点型和整型存储是不一样的,那具体不一样在哪里?为什么同样的一组数,因为看待它的方式不同,导致的结果差异很大?这里就需要了解到一个组织IEEE(国际电气和电子工程协会),IEEE有一条标准,IEEE754规定,对于任意的一个二进制浮点数可以表示为以下的形式:
(-1)^S*M*2^E
其中(-1)^S表示的是符号位,S为0表示整数,为1表示负数,M表示有效数字,2^E就是我们的指数位。。举例来说:5.5就可以按照这个表示方法表示为(-1)^S*1.011*2^2,我们是怎么得到这三个数字的呢?S 是表示我们的正负数,我们可以一眼看出是正是负然后得到,那M和E呢?我们知道,5.5的变成二进制是101.1,将我们的小数点挪到我们第一位之后,就变成了1.011,这就是我们得到的M的方法,那E其实就是和我们的科学计数法10的几次一样了,那我们知道了这三个数字有什么用呢?IEEE在754标准中还规定了,每个数字应该放在哪,怎么放
单精度
编辑
双精度
编辑
S
S相对来说并没有什么规则,和有符号位的整数一样。
M
对于我们的M 来说,因为是二进制,所以M 的范围就是在1~2这个区间,所以在存储的时候一般是将小数点之前的1省略掉,这个时候就节省了一个有效数字位,我们的单精度23位就可以存储24位了,同理双精度也从52位变成了53位,
E
E相较来是它们三个中最复杂的,首先,它是一个无符号的整数,所以对于我们的单精度来说,取值范围是0~255,双精度是0~2047,但是上面说,E的得到和科学计数法中10的几次方是一样的,可以得到负数,所以我们都是得到E之后,如果是单精度就加128,双精度就加1024,就例如我现在的E 是-1,那就需要加上127,得到126,然后把126变成二进制放到E 的位置,对于E还有一些注意事项
E不能全为0或者全为1
当E 全为0时,表示这个数字时个无限接近于0的数字,这个时候我们的精度时不够用的,所以这个时候一般表现为0或者十分接近于0的数字。
当E 全为1 时,这个时候如果我们的有效数字M 为0,则表示我们这个数无限大,表示正负无穷大。