讲解重点:
- 数据类型详细介绍
- 整形在内存中的存储
- 大小端字节序及判断
- 浮点型在内存中的存储解析
一:数据类型介绍
1.1 数据类型
char | 字符数据类型 |
short | 短整型 |
int | 整形 |
long | 长整型 |
long long | 更长的整形 |
float | 单精度浮点数 |
double | 双精度浮点数 |
1.2 类型的基本归类
整形家族
char
unsigned char
signed char
short
unsigned short
signed short
int
unsigned int
signed int
long
unsigned long [ int ]
signed long [ int ]
浮点数家族
float
double
构造类型
> 数组类型
> 结构体类型 struct
> 枚举类型 enum
> 联合类型 union
指针类型
int *p;
char *p;
float *p;
void *p;
二:整形在内存中的存储
||我们知道创建一个变量是要在内存中开辟空间的,而空间的大小是根据不同类型而决定的,
接下来我们就讨论一下整形数据在开辟的空间中是如何储存的
比如:
int a = 20;
int b = -10;
我们知道a和b都是分配4个字节的空间,那如何储存?
下面先来了解一下以下概念:
2.1 原码 反码 补码
计算机中的整数有三种2进制的表示方法:即原码 反码 补码
三种表示方法均有符号位和数值位两部分,符号位都是用0表示 ‘正’ ,用1表示 ’负‘,而数值位
- 正数的原码反码补码都相同
- 负整数的三种表示方法各不相同,如下
原码 :直接将数值按照正负数的形式翻译成二进制就可以得到原码
反码 :将原码的符号位不变,其余位取反就可以得到反码
补码 :反码+1就可以得到补码
对于整形来说:数据存放内存中其实存放的都是补码
那为什么呢?
在计算机系统中,数值一律用补码来表示和储存,原因在于使用补码,可以将符号位和数值域统一处理,我们举个例子:
int a=1;
int b=-1;
int c=a+b;
在内存中,如果用两个数的原码进行计算的话我们看看会发生什么:
a的原码:00000000000000000000000000000001
b的原码:10000000000000000000000000000001
那么a+b:10000000000000000000000000000001 ->该二进制表示-1 ,显然计算结果错误
接下来再用两个数的补码进行计算:
a的原码:00000000000000000000000000000001
a的补码:00000000000000000000000000000001
b的原码:10000000000000000000000000000001
b的反码:1111111111111111111111111111111111110
b的补码:1111111111111111111111111111111111111
a+b: 00000000000000000000000000000000 (从低位往高位进一最后32位都为0,计算结果位0,计算正确)
2.2 大小端的介绍
大端(存储)模式:是指数据低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式:是指数据低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中;
那为什么会有大小端呢?
这是因为在计算机系统中,我们是以字节为单位的,一个字节位8bit,像16bit的short类型,32bit的long类型,由于寄存器宽度大于一个字节,必然会存在如何将多个字节安排的问题,因此就出现了大小端存储模式
例如:
int a = 0x11223344;
低地址 高地址
大端字节序存储: 11 22 33 44
小端字节序存储: 44 33 22 11
2.3 例题讲解
了解了上面的概念以后,我们来实战几个例题:
#include <stdio.h> int main() { char a = -1; signed char b = -1; unsigned char c = -1; printf("%d %d %d", a, b, c); return 0; }
想一下这个代码的结果是什么呢?
结果是: -1 -1 255
接下来我来讲解一下他们的计算过程:
解析:
由于内存中储存的是数值的补码,所以我们先写出-1的补码
-1: 原码:10000000000000000000000000000001
反码:1111111111111111111111111111111111110
补码:1111111111111111111111111111111111111
由于char类型占1个字节,也就是8个bit位,所以a b c只能保存到后8位,即11111111
而%d是10进制的形式打印有符号数,并且字符型在进行整数运算时要进行整型提升(不了解的同学可以看我前面写的 --- 操作符详解(2)---)
char类型和signed char类型都有符号位,在进行整型提升时高位补充符号位
也就是 11111111111111111111111111111111 ,我们认为他是有符号数,而符号位为1代表负数,为了更加清晰的看出上面数字的大小,我们再把他转化为原码:
注:补码转换原码:符号位不变其他位按位取反加一
1000000000000000000000000001 --计算结果为 -1
unsigned char 类型不包含符号位,在整形提升时,高位补0,
00000000000000000000011111111,它的符号位为0,代表正数,所以他的原码和补码相同,计算结果为255
#include <stdio.h> int main() { char a = -128; char b = 128; printf("%u %u", a,b); return 0; }
想想这个代码的结果又是什么呢?
结果: 4294967168 4294967168
解析:
方法和上题类似,我们先写出他们的补码
-128:
原码:10000000000000000000000010000000
反码:1111111111111111111111111111011111111
补码:111111111111111111111111111110000000
char类型占8个bit,所以a中存的是10000000,
我们给他整型提升一下,高位补符号位11111111111111111111111110000000,
由于我们是按%u的形式打印,也就是我们认为a是一个无符号数,对于无符号数来说原码反码补码相同,最高位的1也是有效位,我们看看这么大的数字再十进制下是多少:
可以看出这就是我们的答案了
同理:
128:
原码:00000000000000000000000010000000
补码:00000000000000000000000010000000
此时b中存储的也是10000000,整型提升完以后还是1111111111111111111111111000000,
所以计算结果和上面相同
2.4 char类型详解
对于signed char
一个字节,8个bit位,那么char类型可以储存的数字的范围是多少呢?
00000000 0 //符号位为0,补码和原码相同
00000001 1
.........
01111111 127
10000000 -128
10000001 -127
....
111111110 -2
111111111 -1 //符号位为一,转化为原码,符号位不变其他位取反再加一
有上述分析可知,signed char类型可以表示的范围是 -128~127
当11111111再加1时,由于char类型只能保存8个bit,再加1后会丢失数据,加1后会变为 00000000
对于unsigned char
00000000 0
00000001 1
......
01111111 127
10000000 128
10000001 129
....
11111110 254
11111111 255 //每位都是有效位
signed char可以表示的范围是 0~255
例如:
#include <stdio.h> int main() { unsigned char i = 0; for (i = 0; i <= 255; i++) { printf("hello\n"); } return 0; }
我们思考一下这个代码为什么会死循环打印hello,本来当i加到256不符合条件时就应该终止循环,由于无符号字符型可以取到的范围是0~255,255即11111111,再加一后丢失数据变为00000000,即i会从255变为0,还会继续循环,所以会一直死循环
这样的思想可以类似于short int等等
short -2个字节-16个bit
signed short :-32768~32767
unsigned short :0~65535
注:所以当以后遇到无符号数的计算一定要仔细思考!!!
三:浮点型在内存中的存储
浮点数在内存中是怎样存储的呢?我们举个例子:
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main() { int n = 9; float* pFloat = (float*)&n; printf("n的值为:%d\n",n); printf("*pFloat的值为:%f\n", *pFloat); *pFloat = 9.0; printf("n的值为:%d\n", n); printf("*pFloat的值为:%f\n", *pFloat); return 0; }
答案是不是出乎你的意料呢?
从这也可以看出,浮点型和整形 数据的存储方式是不一样的,接下来我介绍一下浮点型数据在内存中的存储
根据国际标准IEEE(电子和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式:
- (-1)^S*M*2^E
- (-1)^S表示符号位,当S=0,V为正数,当S=1,V为负数
- M表示有效数字,大于等于1,小于2
- 2^E表示指数位
举例来说:
十进制的5.0,写成二级制为:101.0 ,相当于1.01×2^2.
按上面的形式,可以得出S=0,M=1.01,E=2
IEEE 754对有效数字M和指数E,还有一些特别规定。
前面说过, 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的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为
1.0*2^(-1),其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:0 01111110 00000000000000000000000
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,
有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
0 01111110 00000000000000000000000
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);
接下来我们就分析一下刚开始那个题:
9的原码:00000000000000000000000000001001
当我们以整形数据存储以float类型取的时候,0 00000000 00000000000000000001001
S=0 E=00000000 M=0. 00000000000000000001001
即取的结果:(-1)^0*(0. 00000000000000000001001)^-126 是一个无限趋近于0的数,
所以打印结果为0.000000
当我们以float类型存储以int类型取得时候,9写为二级制可以表示为1.001*2^3
存到内存中即为 0 10000010 00100000000000000000000当我们以整形数据看待他是他是一个很大的正数