学习目标:
剖析数据在内存中的存储,本章重点:
- 数据类型详细介绍
- 整形数据在内存中的存储
- 大小端字节序介绍及判断
- 浮点型数据在内存中的存储
学习内容:
①:
整形家族 | char(本质为ASCII的存储)、short、int、long、 long long;均有signed和unsigned.编译器默认为signed |
浮点型家族 | float、double |
②:何为有符号、无符号?
举个栗子:vs2019为例
char:默认为signed char,当有char a=1;先写32位原码,由于正数原、反、补码一致,所以int型的1的低位(8位)bit位内的数据被截断,然后存至char所申请的空间。此时的8为bit位的最高位为符号位。这对char、short类的缺省整型精度的数据类型来说,做表达式运算时,发生整型提升至关重要!上代码:
#include <stdio.h>
int main()
{
unsigned char c1 = 255;
printf("%d\n", c1);//作%d打印,char、short类型数据亦作整型提升
//8为bit存满1时,刚好为255:11 11 11 11
//由于是unsigned char,上述最高位为有效位,所以做整型提升时:
//按正数提升:00000000 00000000 00000000 11111111
//对上述补码(等价于原码)打印,直接翻译即可:255
char c2 = 255;
//上述8位bit位的最高位为符号位,故作负数的整型提升:
//11111111 11111111 11111111 11111111(补码)
//翻译为原码(打印):10000000 00000000 00000000 00000001
//上述结果为-1;
printf("%d\n", c2);
return 0;
}
对signed char和unsigned char类型内存空间所能存储的十进制数作总结:
(a):unsigned char:8位有效位:最大数为:2^8-1=255(因为有个0,所以减一);
(b):signed char:最高位为0或1,为0为正数,为1为负数,两者在256(绝对值)各占一半,即负数范围为:-1☞-128;而正数由于有个0,所以其范围为:0☞127.
综合上述可知:signed char 所能表示的范围为:-128☞127(-128为:10000000☞补码)
将上述推演规律拓展至short,类似,直接给出其范围:-32768☞32767(signed)
②:除了上述的整型、浮点型,这里还有一个构造类型(自定义)
数组类型(随着元素类型、元素个数变化) |
结构体类型 |
枚举类型 |
联合类型 |
③:指针类型
意义在于:解引用及访问字节数会受其影响。
④:整型在内存中的存储:
统一形式为补码(意义:可将符号位和数值域统一处理,其与原码相互转换,运算过程相同,无需额外电路)
⑤:字节序
顾名思义:以字节为单位来排序。
补码的低位放在低地址处:小端字节序。
补码的高位放在低地址处:大端字节序。
例题:设计一个程序判断当前机器的字节序:(利用指针总是指向“小字节”的特点):
#include <stdio.h>
int main()
{
int a = 1;
char* p = (char*)&a;//&a为int*,强转成char*再赋给p
if (*p == 1)
{
printf("小端\n");
}
else
printf("大端\n");
return 0;
}
note:%d和%u打印时,都是针对发生整型提升后的最高位而言的。
例题1:(打印结果时什么?)
#include <stdio.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
return 0;
}
原因:i>=0;i打印9-0没问题,再--变成-1时:补码全为1(32位),存进unsigned int申请的空间,此时的二进制位是没有符号位的,翻译成原码(无符号位时原反补全等):2^32-1=4294967295,这就导致在for循环的判断部分i>=0进行时, 为真,继续打印!
note:自己动手计算内存中的数是多少时遵循一个原则:内存中的都是补码,显示在屏幕上的都是原码!怎么把补码翻译成原码,关键就看补码的数据类型是有无符号位了。
例题2:(打印结果时什么?)
#include <stdio.h>
#include <string.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d",strlen(a));
return 0;
}
分析:a[i]依次为:-1、-2、...、-128(因为数组元素类型为char,负数的上限为-128,此时的补码为:10 00 00 00);再减一:01 11 11 11翻译成原码:2^7-1=127.之后再继续减减的操作直至0.
strlen从a进入,将元素当作unsigned char来翻译,只要不是‘\0’(ASCII为0(补码)),统统每进一位就加一,所以说:计数至0.strlen功能完成。那此时的字符个数是:128+127=255个(0不算一个)。
⑥:浮点型数据在内存中的存储
准则:IEEE754:任意一个二进制浮点数V可以表示成以下形式:
(-1)^S*M*2^E
s:表正负:0为正,1为负
M:有效数字,范围:1<=M<2
E:指数位(10^3在十进制中的指数位就是3,二进制就是2^指数位)
5.5写成二进制:101.1再科学计数法:1.011*2^2.再写成上述标准格式:(-1)^0*1.011*2^2
那s、m、e分别为:0、1.011、2
难以存储的二进制小数(浮点数):3.3
因为小数点后一位为:2^-1
小数点后两位为:2^-2
...
存储:
float:(4字节,32位bit):从低地址至高地址依次存S、E、M,各自分配的bit的数量为:1、8、23
double:(8字节,64位bit):亦依次存S、E、M,各自分配的bit数量为:1、11、52
对S/E/M存储规则的介绍:
S:0/1存进低地址首位bit中,无需多说
M:由于其范围为1<=M<2;即必为1.几,敢情1直接不存了,只存小数点后面的.几的“几”。等到读取时再把这个省略的1补上。这么做的目的是:多一位有效位!还原(读取)时记得加一。
E:首先它是一个unsigned int :有效位则为8位,范围则来到:0☞255,若是double,更是来到:0☞2047,由于科学计数法是允许E为负数的。对此,unsigned int与负数存在矛盾。IEEE754则对此作出规定:存入内存的E值须再加上0☞255或者0☞2047的一个中间数,即127和1023.再存进属于E的那块空间。如2^10,E是10,存的话,float:存的是10+127=137的补码,double存的是:10+1023=1033的补码。
举例:存float的0.5:
S:0
E:-1+127=126:补码(等价于原码):01 11 11 10
M:1.0:1不要就存23个0:00000000 00000000 0000000
总的:
0 01111110 00000000000000000000000
对存于内存中的二进制浮点数的读取:
分情况:
(a):E不全为0或不全为1(最一般的情况)
由上述:E的真实值则为内存中的补码翻译成原码后再减那个中间数(float还是double?),M再加个1。
(b):E全为0:上述可知E是加过中间数作存储的,加完还全是0,你说刚开始的E是有多小!?,所以说此时的浮点数,几乎接近于0;此时的M不必再加1还原了,直接打印吧。
(c):E全为1:这时:如果有效数字全为0表示±无穷大。
对浮点数存储理解的一个经典案例:
#include <stdio.h>
int main()
{
int n = 9;
//9的补码:00000000 00000000 00000000 00001001
float* pfloat = (float*)&n;
printf("n的值为:%d\n", n);//为9不难理解
printf("pfloat的值为:%f\n", *pfloat);
//以%f打印9的补码,此时的补码被视作浮点型数据的存储方式:
//0 00000000 00000000000000000001001
//此时属于E全为0的情况,E真实值为0-127=-127,M为0.00000000000000000001001
//还原出的数字非常接近于0;而%f只能精确到小数点后6位,故只可打印0.000000
*pfloat = 9.0;
//9.0写成二进制数:(-1)^0*1.001*2^3
//存:0 10000010 00100000000000000000000
printf("num的值为:%d\n", n);
//以%d的形式去解读上述补码,为一个正数,直接当做原码打印:1091567616
printf("*pfloat的值为:%f\n", *pfloat);
//9.000000
return 0;
}
运行结果也确实是:
学习时间:
2021.10.7
学习产出:
1、 技术笔记 2 遍
2、CSDN 技术博客 31篇
3、 gitee