1,数据类型介绍:
我们入门所学的基本数据类型无非就是:
类型名称 | 大小 | 描述 | 取值范围 |
char | 1个字节 | 字符类型 | 0~255 |
short | 2个字节 | 短整型 | ~ - 1 |
int | 4个字节 | 整型 | - ~ - 1 |
long(int) | 4个 / 8 个字节 | 长整型 | ~ - 1 或者31 改为63 |
long long | 8个字节 | 更长的整型 | -2^63 ~ 2^63-1 |
float | 4个字节 | 单精度浮点型 |
double | 8个字节 | 双精度浮点型 | 大小看具体环境 |
这些类型就确定了我们看待内存空间的一个视角,就比如看到 int 我就可以知道对应变量里面存的是一个整数,看到 double 就可以知道对应变量里面存的是小数。而大小则决定我们使用的数据范围,以便我们做出更好的额选择。
除此之外,还有构造类型,例如数组arr[ ],结构体 struct,枚举enum 和
联合union。
同样还有不可缺少的指针类型,例如 int *p,char*p,float*p,void*p这里的void表示空类型,常用于函数返回类型,例如void hanshu(int x)
也有函数的参数类型,例如自定义一个函数void hanshu(void)
同时也可以用来存放类型不定的指针。
2,整形在内存中的存储
这里我们创建a,b两个整形变量,那么他们是如何存储的?
计算机中所存的整数有三种表示方法,分别是原码,反码和补码,正数的原码,反码,补码相同。
负数的三种表示方法则各不相同:
在二进制语言中,最高位为符号位,最高位为0,则为正数,最高位为1则为负数。
负数的原码按二进制翻译成对应的大小,并且符号位为1,原码符号位不变。其余为按位取反得到补码,补码+1得到反码,例如-10
原码100000000000000000001010
补码111111111111111111110101
反码111111111111111111110110
二计算机中的数值一律用补码来存储,计算也是用补码来计算,然后原码才是我们所看到的数值。
存储用补码,原因在于:
使用补码,可以将符号位和数值域统 一处理; 同时,加法和减法也可以统一处理(CPU 只有加法器 )此外,补码与原码相互转换,其运算过程 是相同的,不需要额外的硬件电路。
至此我们可以前往调试模式下的vs,在内存中监视int变量的值是否为补码的形式存放的。
(这里的存放的数值是16进制,例如a的内存中存放的14转化为10进制就是20.因为正数的原码反码补码相同,则看不出内存中存放的是原码还是补码,因此我们看下面的变量b对应的二进制数值。)
通过计算器我们可以看到内存中存放的FFFF FFF6所对应的二进制刚好为我们上例子中所提的补码,这也验证了上述观点,
但是我们自习观察会发现,内存中存放的是F6FF FFFF,与我们的结果恰好反过来了。
这就要涉及到 大小端问题了
3,什么是大小端,该如何判断?
什么是大端模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址 中;
什么是 小端模式: 是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地
址中。
那为什么会出现大小端之分:这是因为在计算机系统中,我们是以字节为单位的,每个地址单元 都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的short 型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32 位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因 此就导致了大端存储模式和小端存储模式。 例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为 高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高 地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则 为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式 还是小端模式。
那我们改如何判断到底是小端存储还是大端存储:
我们可以想到int类型存放的是四个字节,读取的也是四个字节,由于大小端存放在低地址或者是高地址的低位不一样,所以如果在读取的时候我们将其强制转换为char类型只访问1个字节,那就可以很清楚的从访问的第一个字节知道到底是小端存储还是大端存储
这里以二进制00 00 00 01为例子
&a->int *
char*p=(char*)&a,此时解引用p就是访问的a的低地址里的内容
如果解引用后的值打印为1则为小端,否则为大端。
4,浮点型在内存中的存储
在c语言中, 要使用float关键字或者是double关键字来定义浮点类型的变量, float类型变量占用的空间大小为四个字节, double类型占据把个字节大小的空间, 与整形数据存储的方式不同, 浮点数类型的存储是按照某种指定的指数形式来存储的
系统把一个浮点型的数据分成小数部分(使用M来表示), 和指数部分(使用E来表示)并分别存放, 指数部分采用规范化的指数形式,指数也分正负(符号位, 使用s来表示)如图所示:
再次说明:
- S:符号位
- E:指数部分
- M:小数部分
这种规定的浮点数的格式就被称为 IEE - 745标准:
此图在计算机组成原理里面又可以有新的定义:
表1中有四个二进制数的区域,每个区域都是8bit,但是根据不同的定义,可以将前8个bit位称为阶码,后24个bit称为尾数。
下面我们分别来看下面的这个这三个数据域(指的是符号位,指数部分和小数部分)
- S:S是符号位,用来表示正负,1表明为负数,0表明为正数。
- E:E代表指数部分,指数部分规定,只能是1 ~ 254,不能是全0或者是全1,指数部分运算前都要减去127(这是IEEE-754的规定),因为还要表明负指数。
--------------------------------------------------------------------------------------------------------
==举个栗子:假设指数域的数据为10000001转化为十进制为129, 减去127得2,所以实际指==数部分为2
- M:小数部分,例如 0010 0000 0000 0000 0000 000底数左边省略了一个1(这是IEEE - 754的规定),使用的诗句底数为1.0010 0000 0000 0000 0000 000
- 最后计算出来的10进制结果为:(-1)^S * M * 2^E
如何计算浮点数的10进制值, 对于整数的转化为10进制相比很清楚了吧大家,遵循下面这张图:
来自百度百科
当我们将b = 2带入这个式子, 那么就得到了二进制的表示法:
举个例子:
那么我们接下来看看,浮点数在计算机里面的存储:
#include<stdio.h> int main() { float a = 3.6; return 0; }
查看a在内存中的视图:
可以看到这里面有8个数字,他们是两个十六进制位连在一起,将00 00 90 40转化为10进制数就是如下:
解释:每一个字母对应一个二进制的bit位。 例如第一个s对应第一个二进制数0, 表明符号位,为正数,依次类推。为什么内存里面是00 00 90 40, 而这里却是40 90 00 00 的原因就是计算机使用的是小端存储,也就是高位存放在高地址处
也就是说40为高地址,同时也为高位, 但是我们生活中习惯把高位放在左边,也就形成了上图的情况。
但是我们需要知道的是,按照上图的算法,可以得到:
这个浮点数的二进制表示位 :
01000000 10010000 00000000 00000000
解释
- 首先第一个0为符号位表示正负数,因为是0,所以表示正数
- 符号位的后8bit位指数部分(1000000 1),转化位的10进制位129,根据标准-127得到实际的指数为2
- 后面23位为小数部分(0010000 00000000 00000000),前面省略了1. 所以完整的二进制雄小数表达为:1.0010000 00000000 00000000, 使用在线计算机计算得:
得1.125
- 最后综合上面的得到最后结果: (注意这个后面还需要-127)
得:1.125 * (2^2)= 4.5
最后也可以通过1.0010000 00000000 00000000左移2位得100.10000 00000000 00000000来实现:
- 注意这个最后得根据公式 (-1)^S * M * 2 ^ E不一定等于最后你表示得结果,因为:
浮点数的小数部分是通过如下
,,
=======================
写在后面:下面是我以前写的,写的很不好,所以我在这里把浮点数的存储给重新写了一次, 这次写是我后面考研,又来重新回顾这个浮点数的存储,看见写的很乱就重写了,而且我以前写这篇文章的时候使用了英语全角,因此显示会很不好看。 但是下面的内容仍然很具有参考价值。
我们常见的浮点型:3.14159;1E10(1.0*10^10)
(包括float,double,long double)浮点型取值范围限定在:float.h头文件里(可以用everything来搜索float.h并在vs中打开查看))
举个例子
运行结果为
浮点数存储规则:
num 和 *pFloat 在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?
要理解这个结果,一定要搞懂浮点数在计算机内部的表示方法。
浮点数在计算机中的存储是有国际标准的(IEEE(电气和电子工程协会)754)
具体形式为:
(-1)^S * M * 2^E
(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位
..单位
举个例子来解读这个标准,例如一个十进制数4.0:
写成二进制就为100.0,使用科学科学计数法保留小数点后两位为1.00x2^2。
按照国际标准就可以知道 这个浮点数的S=0,M=1.00,E=2
IEEE 754规定,对于32位的浮点数,最高位是符号位,接下来8位是指数E,剩下的32位为有效数字位M
对于64位的浮点数,最高的1位是符号位,接下来的11位为指数E,剩下的52位为有效数字M。
前面说过, 1≤M<2 ,也就是说, M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。
IEEE 754 规定,在计算机内部保存 M 时,默认这个数的第一位总是 1 ,因此可以被舍去,只保存后面的 xxxxxx部分。比如保存 1.01 的时 候,只保存01 ,等到读取的时候,再把第一位的 1 加上去。这样做的目的,是节省 1 位有效数字。
以 32 位 浮点数为例,留给M 只有 23 位, 将第一位的1 舍去以后,等于可以保存 24 位有效数字。
至于指数 E ,情况就比较复杂。
首先, E 为一个无符号整数( unsigned int )
这意味着,如果 E 为 8 位,它的取值范围为 0~255 ;如果 E 为 11 位,它的取值范围为 0~2047 。但是,我们
知道,科学计数法中的 E 是可以出
现负数的,所以 IEEE 754 规定,存入内存时 E 的真实值必须再加上一个中间数,对于 8 位的 E ,这个中间数
是 127 ;对于 11 位的 E ,这个中间
数是 1023 。比如, 2^10 的 E 是 10 ,所以保存成 32 位浮点数时,必须保存成 10+127=137 ,即
10001001 。
然后,指数 E 从内存中取出还可以再分成三种情况:
E 不全为 0 或不全为 1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将
有效数字M前加上第一位的1。
比如:
0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为1.0*2^(-1),其阶码为-1+127=126,表示为 01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进
制表示形式为:
E 全为 0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于 0的很小的数字。
0 01111110 00000000000000000000000
E 全为 1
这时,如果有效数字 M 全为 0 ,表示 ± 无穷大(正负取决于符号位 s );
好了,关于浮点数的表示规则,就说到这里。
5,浮点数精度丢失
浮点数使用的是指数表示法,需要记忆的数值范围比较多,同时我们需要注意浮点数的精度问题。
这个精度的意思就是小数点后面的位数,意思是小数点后面的6~7位是准确的,再到后面的位数就显示不了了。
通过上面的浮点数再内存中的存储,我们可以知道,float后面的小数位有23位,前面的1.是默认省略的,所以23位也就是 = 8388608,我们看这个数字,数一数一共是7位,这就意味着最多能有七位有效数字,但是能绝对能保证的为6位,也即float的精度为6~7位。double的有效位同理。
举个例子:
#include<stdio.h> int main() { float a = 1.23456789e10; // e10为科学计数法-》x * 10 ^10,也就是12345678900 float b; b = a + 20; // 结果为12345678920,科学计数法来表示就是,1.234567892e10 printf("%f", b); return 0; }
float a = 1.23456789e10; e10为科学计数法为 x * 10 ^10,也就是12345678900
结果为12345678920,科学计数法来表示就是,1.234567892e10, 但是结果输出为如下:
也即是说,但是呢,后面2345678848,这个时候就是精度丢失了后面三位。我们将这个float换位double就可以了。因为double表示的及高度位15~16位,对于float的精度丢失,double完全可以捕捉到这个丢失。
另外针对强制类型转换,int转化为float 可能造成精度丢失,因为int是10位有效数字,但是int强制转化为double却不会,float转化为double也不会丢失。