我们在存储数据时首先是声明数据类型,如int,char,long等,声明类型的是向内存申请对应类型大小的一块内存空间,以对数据进行存储。
以下为不同数据类型的空间大小
- char 1个字节
- short 2个字节
- int 4个字节
- long long 8个字节
- float 4个字节
- double 8个字节
而一个字节为八个bit位,对应八个二进制位,因此解释每个数据类型对应的数据范围。
整数类型又有有符号数和无符号数的类型,简单来说,有符号数和无符号数的数据长度范围是一样的,区别在于无符号类型不区分二进制的数据正负,统一转化为正数。
如char类型,unsigned char的范围是-128~127 ,char的范围是0-255。区间长度都是256
首先说整形的存储
整数的转码有三种:原码,反码,补码。
先贴一下鹏哥对这里的解释
他后面又解释到使用时的数据提升等等
但是在我的思考里,这种说法有两点不能对存储的现象进行很好的解释
- 对于超出范围的负数,数据的二进制位数如果超出类型的范围,符号位的储存和数据冲突,怎么规定符号位?
- 解释数据在范围里循环时比较生硬,比如char类型里(bin)1000 0000如果按照符号位的说法解释为什么是-128是只能说是规定。
因此在思考并验证多次之后,写出我对C语言里数据的存储和不同类型使用方式的思考。(我并没有深度学习原理,这里只是我的思考,并不一定正确,如果之后的学习里能够探得真相,我会有解释。)
首先仍然是三码
- 原码:根据原来的数据直接转化为二进制得到
- 反码:将原码按位取反(此时没有符号位)
- 补码:反码加1
对于正数,存储到内存中三码相同,直接得到原码的二进制序列。
对于负数,并不会储存符号位,在有负号的时候,编译器会将数据去掉符号直接进行原码,反码,和补码的转换,得到二进制序列。
之后,根据申请数据类型的空间大小,储存二进制位数。从低位向高位存储(由于windows的小端字节序的存储方式)。
使用时,根据数据类型,再进行相应的转换
- 如果是无符号数,直接进行数据的翻译
- 如果是有符号数,将现有的最高位看成符号位,如果是0,则表示为正数,直接翻译;如果是1,则表示位负数,将数据按照补码转化为原码,再打印加负号的数值。
- 如果有类型提升,高位补符号位
这种说法,既解释了为什么超出数据范围的大数为什么常常显示负数,也能很好的解释char类型里(bin)1000 0000为什么是-128
举例:
int main()
{
char a = -1;
unsigned char c = -1;
printf("%d %d",a,c);
//结果是-1 255
return 0;
}
首先-1有负号,则进行原反补的转化0000 0001 -> 1111 1110 -> 1111 1111 分别存入a,c的内存
然后使用时a 首位是1 打印负号,取反为0000 0000-> 0000 0001 打印为1,所以为-1
c 由于c是无符号数,直接翻译1111 1111 为255
补充,这里%d使用是整形,要对数据进行提升,不过一个符号位是0,一个没有符号位所以省略
int main()
{
char n = -128;
printf("%u ", n);
printf("%d", n);
//结果是4294967168 -128
return 0;
}
首先仍然128进行原反补转换,1000 0000 -> 0111 1111 -> 1000 0000
使用时为整形,数据提升1111 1111 1111 1111 1111 1111 1000 0000
第一行为无符号数直接转化为4294967168
第二行为有符号数转化 为0000 0000 0000 0000 0000 0000 0111 1111
加1 0000 0000 0000 0000 0000 0000 1000 0000 打印为-128
int main()
{
char arr[1000];
for (int i = 0; i < 1000; i++)
{
arr[i] = -1 - i;
}
printf("%d", strlen(arr));//结果为255
return 0;
}
这里有一点,数据持续自减,到下限之后会循环
这里从-1开始减到-128 补码为1000 0000
再减1为 0111 1111 首位是0,则为正数,转换为127
再继续减小,到0时存在char类型的数组里,由ASCII表翻译为‘\0’
strlen计算长度为255,正好为char的数据区间
思路捋下来,也没明显的解决什么其实,但是这样对个人来说更方便理解
最让我感到惊奇的是数据加减处理时用到的补码,真是个神奇的东西,对于能把正数负数统一加法处理,这就是理科的魅力吧。
浮点数的储存
首先需要注意一点,浮点数和整数的内存使用视角不一样
int main()
{
int n = 9;
float* pn = (float*)&n;
printf("%d\n", n);//结果是9
printf("%f\n", *pn);//结果是0.000000
*pn = 9.0;
printf("%d\n", n);//结果是1091567616
printf("%d\n", *pn);//结果是0
printf("%f\n", *pn);//结果是9.000000
//以浮点数的方式拿数据和存数据的方式和整形不一样
return 0;
}
首先申请空间时的命名n 为整形,其使用内存中数据的角度时整形的存放
当我们取指针pn将其强制转化为浮点型的指针。*pn得到使用浮点型的视角
这里要和数据类型转换 float m = (float)n;区分开,这是数据层面的转换,而不是内存视角
存储:
首先转化为二进制
小数点后面的位数的转换是2的(-n)次方
比如 5.5 为 2^2 + 2^0 + 2^(-1) 则其二进制表示为101.1
其次,根据IEEE规定,任意一个二进制浮点数可以表示称下面的形式:
如5.5,即101.1,首先为整数 s = 0;然后转化为1.011*2^2,则E为2;M为1.011
M总归可以进行移位到1.xxx所以可以把小数点前的1省略掉,直接存储后面
由于E可能为负数,因此为了确保E为正数,需要将E加上一个中间数
这是以浮点型视角进行内存的存储
我们回头继续看
int main()
{
int n = 9;
float* pn = (float*)&n;
printf("%d\n", n);//结果是9
printf("%f\n", *pn);//结果是0.000000
*pn = 9.0;
printf("%d\n", n);//结果是1091567616
printf("%d\n", *pn);//结果是0
printf("%f\n", *pn);//结果是9.000000
//以浮点数的方式拿数据和存数据的方式和整形不一样
return 0;
}
当存入9的时候,整形的视角存入补码为0000 0000 0000 0000 0000 0000 0000 1001
*pn 时为-1^0 * 2^(-127)*(1.000 0000 0000 0000 0000 1001) = 0.000000
*pn = 9.0时,存入的为-1^0 * 2^(127+3) * 1.001
内存中为 0100 0001 0001 0000 0000 0000 0000 0000
因此为这个数
对于浮点数精度的解释,我现在并没有想的非常清楚,很明显它同时受e和m的限制,但是之后的使用中,小数点前后位数的权重,还要继续学习。