前言:我们在写代码的过程中,你是否会产生以下疑问:有符号数据和无符号数据的区别在哪里?为什么signed char的范围是-128~127?浮点数的存储是怎么样的呢?浮点数的存储和整型是一样的吗?如果你也有以上疑问,那么就来看看这篇文章吧。
这里是OMGmyhair的博客
如果你想要了解更多的内容可以看看我的主页OMGmyhair-CSDN
我将讲解一下三种数据类型:
目录
一、整数在内存中的存储
首先,整数类型分为两类:有符号类型和无符号类型
在C语言中,整数类型又有不同的大小,有long长整型、short短整型等。
整数类型表示范围可以在<limits.h>中查看。
接下来我们来具体看看整数在内存中存储:
整数在内存中是以二进制的补码形式存放。
1.正负数在内存中的存储形式
例如数字4,我们来看看它的原码、反码、补码。
正数的原码、反码、补码相同。
原码:00000000 00000000 00000000 00000100
反码:00000000 00000000 00000000 00000100
补码:00000000 00000000 00000000 00000100
则00000000 00000000 00000000 00000100就是数字4在内存中的表示形式。
我们再来看看负整数在内存中的表示形式:
例如数字-4,我们来看看它的原码、反码、补码。
因为其是负数,所以符号位也就是最高位为1,值得注意的一点是:负数在取反时,要注意符号位不变,其它位按位取反。
原码:10000000 00000000 00000000 00000100
反码:11111111 11111111 11111111 11111011
补码:11111111 11111111 11111111 11111100
则11111111 11111111 11111111 11111100就是数字-4在内存中的表示形式。
2.大小端
了解了整数在内存中的存储形式之后,我们再来看一个问题,即大小端问题。
在存储数据的时候,是将数据的高位字节存放在高位地址还是存放在低位地址呢?
大端字节序:高位字节放在低地址,低位字节放在高地址。
小端字节序:低位字节放在低地址,高位字节放在高地址。
我们来用图片讲解的形式来进一步理解,这里为了方便,我使用十进制数字进行举例。
例如1234,1为高位,4为低位:
大端字节序:
小端字节序:
这里,我们也可以写代码对电脑大小端进行确认:
这里我将给出两种方法(都在VS上实现,VS采用的是小端字节序):
第一种方法:
int main()
{
int n = 0x01020304;
char a = *((char*)&n);
printf("%d", a);
return 0;
}
运行后:
第二种方法:
union end
{
int a;
char b;
};
int main()
{
union end u;
u.a = 0x01020304;
printf("%d", u.b);
return 0;
}
运行后:
二、字符型在内存中的存储
现在我们已经明了了大小端以及如何去确认。接下来我们来看看字符型数据:
char类型 | 取值范围 |
signed char | -128~127 |
unsigned char | 0~255 |
注:char是有符号的char还是无符号的char,取决于编译器。
这里我们来探讨一下unsigned char和signed char的取值范围。
1.unsigned char
首先char是一个字节,8个比特位,我们来列出unsigned char的取值范围:
2.signed char
接下来我们再来看看signed char,首先signed char是有符号的类型,则首位成了符号位:
注:-128->原码:11000 0000 反码:10111 1111 补码:11000 0000
但由于char只能存储8个比特位,发生截断,则-128在char中是1000 0000
三.浮点数在内存中的存储
浮点数表示范围可以在<float.h>中查看。
类型 | 最小正值 | 最大值 | 精度 |
float | 1.17549x10^(-38) | 3.40282x10^38 | 6个数字 |
double | 2.22507x10^(-308) | 1.79769x10^308 | 15个数字 |
这里我们有两种主要的浮点数格式:单精度(32位)和双精度(64位)。数值以科学计数法的形式存储,每一个数都由三部分组成:符号、指数和小数。
1.浮点数的存储形式
我们来具体查看这三部分:
浮点数在二进制中被分为了三部分:V=(-1)^s*M*2^E
首先来看符号位(-1)^s:
当浮点数为正数的时候,S=0;当浮点数为负数的时候,S=-1;
M是二进制浮点数的有效数字。
2^E是二进制浮点数的指数位。
对于32位浮点数float,最高1位存储S,接着8位存E,剩下23位存储有效数字M。
对于64位浮点数double,最高1位存储S,接着11位存E,剩下52位存储有效数字M。
我们来拿9.5这个数字进行举例转换。
9.5在二进制中是1001.1
而1001.1->(-1)^0*1.0011*2^3.
默认M1.XXXX的数字,所以存储的时候我们只存小数点后面的数字,即XXXX,读取的时候再加上首位的1,这样可以提高精度。
而E会出现有负数的情况,为了解决这一问题,存入时E的真实值必须加上一个中间值,8位的E的中间值是127;11位的E的中间值是1023。
注意:还有一些特殊情况,
当E全为0的时候,则E=1-127或者1-1023,有效M不再加上第一位的1,而是还原为0.XXXX的小数。
当E为全1的时候,这时若有效M全为0,则表示±无穷大。
2.浮点数的存取
接下来我们用VS进行举例讲解:
int main()
{
int a = 5;
float* ptf = (float*)(&a);
printf("a的值为:%d\n", a);
printf("*ptf的值为:%f\n", *ptf);
*ptf = 5.0;
printf("a的值为:%d\n", a);
printf("*ptf的值为:%f\n", *ptf);
return 0;
}
拿这个代码来分析:
a在内存中的存储为00000000 00000000 00000101;
而ptf指针认为自己指向的是一个浮点数,于是它会将上面的二进制形式以浮点数存储来看待:
0 00000000 000000000000101
此时S=0,E为全0,所以读取的时候,E=1-127,有效数字M不加1,就是0.000000000000101的形式。
还原为浮点数科学计数法的形式为:
V=(-1)^0*1.01*2^(-139)
这是一个非常接近0的数字,而%f默认打印小数点后面6位,所以此时输出*ptf会是0.000000;
当*ptf=5.0时,a的地址存储的是浮点数在二进制中的表现形式:
5.0->101->101=(-1)^(0)*1.01*2^2;
S=0,M=01,E=2+127->10000001;
则它在内存中的存储形式如下:
即01000000101000000000000000000000
此时打印a的话,a会认为自己内存中存的是整数二进制的补码,即将01000000101000000000000000000000看作一个整数的二进制,此时输出为1084227584;
这时候我们的分析结束,走一遍代码,查看是否正确:
至此,我们已经了解了这些数据在内存中是怎么存储的。
如果这篇文章有帮助到你,请留下您珍贵的点赞、收藏+评论,这对于我将是莫大的鼓励!学海无涯,共勉!