前言
到现在,我们都大概认识了C语言的基本内置类型有那些,那么这些类型到底是以什么样的形式存储到内存中的呢?
接下来我们就为大家揭晓这个谜团,原来整型和浮点型的存储并不一样。
一、数据类型介绍
char short int long long long float double |
//字符数据类型 //短整型 //整形 //长整型 //更长的整形 //单精度浮点数 //双精度浮点数 |
//C语言有没有字符串类型?
//C语言是没有特别设置类似于Java的String字符串类型,但是我们可以通过char【】数组来实现对于字符串的存储。
类型的意义:
1.使用这个类型开辟内存空间的大小(大小决定了使用范围)
2.如何看待内存空间的视角 --- 比如涉及到算术转换的时候,整型提升的时候,或者强制指针类型转换的时候,都要考虑类型的内存大小
1.1类型的基本归类
整型: unsigned 表示无符号
signed 表示有符号 且signed int 和 int 在大部分的编译器中本质上没有什么区别
char 字符型 1个字节
unsigned char
signed char
short 短整型 2个字节
unsigned short
signed short
int 整型 4个字节
unsigned int
signed int
long 长整型 4个字节
unsigned long
signed long
浮点型:
float 单精度浮点数 4个字节
double 双精度浮点数 8个字节
构造类型:
所谓的构造类型就是,这些类型是你可以自定义类型,不是C语言基本内置类型(整型家族、浮点型家族),可以自己设定类型名称,如结构体类型 struct arr{};struct arr就是这个结构体的类型名,当然也可以使用typedef来再次定义,就不多讲了。
数组如 int arr[11] 数组类型为:int [11] ,数组名为:arr 所以更改[ ]的数值就是不一样的数组类型
1.数组类型 如 int arr [ ] 其数组类型为 int [ ] 数组名为 arr
2.结构体类型 struct
3.枚举类型 enum
4.联合类型 union
指针类型:
以下类型的指针是样例,这些指针的类型 分别为 int* char* float* void*一般存放的是对应类型的地址,当然可以存放不同类型的地址(强制转换)这样可能会发生截断,下面会有样例
int *pi 整型指针pi
char *pc 字符型指针pc
float* pf 单精度浮点型指针pf
void* pv 无符号指针pv
空类型:
void 表示空类型 (无类型)
通常是应用在函数的返回类型、函数的参数、指针类型
二、整型在内存中的存储
上文介绍,一个变量在创建的时候,会在内存中开辟空间的,空间的大小是根据不同的类型而决定的。
接下来我们来认识一下整型家族是如何在内存中存储。
整形的范围在limits.h头文件中可以看到
2.1原码、反码、补码
初步认识:
1. 计算机中的整数有三种2进制的表示方法,即原码、反码、补码。
2. 三种方法都有符号位和数值位两部分,符号位用 ’ 0 ‘ 或 ’ 1 ‘来表示,0 表示为正数,1 表示位负数 ,数值位正数的原码、反码、补码都相同。负数的三种表示方法各不相同
原码:
直接将数值按照正负的形式翻译成二进制就可以得到原码
将原码的符号位不变,数值位按位取反,0 -> 1 或 1 -> 0 ,得到反码
补码:
将反码加一得到补码
注意:
正数的三码合一(都相同),负数三码表示都不相同,按照上面的方法改变
2.2 认识数值在内存中的存储
对于整型来说,数值在内存中的存储是存放的补码
这个时候就有些同学开始疑问了,为什么三码中选择了补码?
这是因为:
1.在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和 数值域统一处理;
2.同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运 算过程是相同的,不需要额外的硬件电路。
让我们看看vs中的内存存储形式:
我们让它显示的是十六进制,所以我们可以知道,0a 其实就是10 ,14实际上就是20(16+4)
b=20的二进制为
00000000000000000000000000010100
0000 0000 0000 0000 0000 0000 0001 0100 (4个4个分开)
0 0 0 0 0 0 1 4 (16进制)
二进制是这样的,但是在内存中确实倒置存放的,这是为什么呢?
2.3 认识大小端
什么是大端小端:
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。
为什么要有大小端呢?
1.数据的存放,本质上任意顺序存储都可以,只要能存进去就可以,但是,存进去就要取出来。肆意顺序存放,等取出来的时候,就很麻烦,所以诞生了大小端这两种存放的方式
2.大端是正序排放,低位放在高地址,00 00 00 14 (二进制)放在大端为 00 00 00 14
3.小端是逆序排放,低位放在低地址,00 00 00 14 (二进制)放在小端为 14 00 00 00
4.正序存放,就正序取出,逆序存放,就逆序取出
请看一道面试题:
请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。
(以下两种方法都可以)
2.4 练习
答案是:-1 -1 255
关于char的小技巧:
三、浮点型在内存中的存储
我们已经介绍了整数家族在内存中的存储,接下来让我们看看,浮点型的存储是否有不同?
常见浮点数
1.23
1e10
浮点数家族包括,float、double、long double类型
浮点数的表示范围在float.h头文件中定义
3.1举例
为什么会这样呢?
这是因为浮点数的存储方式和整数存储方式不一样导致的
3.2浮点数的存储规则
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位
也就是说
对于32位的浮点数有这样存储空间的分配
对于64位浮点数
特别规定:
前面说过, 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从内存中取出还要分三种情况:
1.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,则其二进制表示形式为:
0 01111110 00000000000000000000000
2.E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,
有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字
3.E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)
如11111111 ---> 255 E=255-127 ,次方很高,正负无穷只看S为0还是1
3.3 例题的解释
总结
1.我们知道了整数和浮点数的在内存中的存储方式是不一样的,整数是通过原码、反码、补码二进制的形式,存储在内存中。
2.大小端的问题,在探寻整数的存储方式的时候,发现了大小端,这是为了方便数据的存取设定的两种方法,大端->低位存高地址 , 小端->低位存低地址。如何查询当前编译器是大小端的方法(面试题),vs默认是小端。
3.浮点数的SME存储,S表示正负(1bit),E表示几次方(unsigned int)(8bit或11bit),需要真实值加上127或1023(相应的返回的时候减去),M表示小数的存储(精度有23bit或者52bit)