009+limou+C语言深入知识——(1)整形数据和浮点数据的存储

1、数据类型的介绍

  • 类型的意义:
    • 使用对应类型能开辟对应内存空间的大小(使用范围)
    • 还有C语言对待不同类型,会采用不同的内存空间视角来看待一个数据
  • C语言中类型的基本归类:
    • 整型(内置类型)
    • 浮点型(内置类型)
    • 空类型(void)
    • 指针型
    • 构造型
      • 数组类型
      • 结构体类型struct
      • 枚举类型enum
      • 联合类型union

2、整型在内存中的存储

(1)原码、反码、补码

①原码的概念

将整数表示为一个二进制数(注意负数最前面的符号位为1),即可得到原码

②反码的概念

原码的符号位不变,其他位0->1,1->0,即可得到反码

③补码的概念

在反码的二进制数基础上+1,即可得到补码

  • 三种表示方法均有符号位和数值位之分
  • 其中符号位0表示“正”,1表示“负”
  • 正整数的原、反、补码相同;负整数的原、反、补码不同
  • 先对原码进行反码再补码<=>先对原码进行补码再反码

④整型以补码的形式存储在内存中

  • 在计算机中,整数类型数值统一用补码表示和存储,原因在于可以将符号位和数值位统一处理
  • 加法和减法也可以统一处理(CPU中只有加法器)
  • 补码和原码相互转化,其运算过程是相同的,不需要额外的硬件电路
  • 注意在内存窗口查看的时候会发现不是二进制而是十六进制,这个只是方便我们查看而已

(2)大小端的概念

①在内存中查看存储的数值

在这里插入图片描述
会发现00 00 00 64是被倒着放置的(小端模式)

②大端模式和小端模式

  • 大端字节序存储模式:数据低位字节存在内存的高地址,数据高位字节存在内存的低地址
  • 小端字节序存储模式:数据低位字节存在内存的低地址,数据高位字节存在内存的高地址

③出现大小端的原因

对于位数大于8位的处理器(如16位或者32位的处理器),由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题,因此就导致了大端存储模式和小端存储模式的出现。

(3)检验当前机器的大小端

//利用指针类型的转化可以检验大小端模式
int check_sys(void)
{
    int i = 1;
    return (*((char*)&i));
}
int main()
{
    int ret = check_sys();
    if (ret == 1)
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }
    return 0;
}
//另外一种写法,以后学习也可
int check_sys()
{
    union
    {
        int i;
        char c;
    }un;
    un.i = 1;
    return un.c;
}

(4)一些练习代码

①第一题

//输出什么?
#include <stdio.h>
int main()
{
    char a= -1;
    signed char b=-1;
    unsigned char c=-1;
    printf("a = %d,b = %d,c = %d", a, b, c);
    return 0;
}

这里就涉及到char的取值范围:
1、有符号char的取值范围[0 ~ 127]或者[ -128 ~ -1]即[-128 ~ 127],这里要注意-128的情况要理解成9位,不要把最高位理解成符号位,因为char只有8位被挤掉了……
2、无符号char的取值范围[0 ~ 255]

②第二题

#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\n", a);
    return 0;
} 
  • 原码:1000 0000 0000 0000 0000 0000 1000 0000
  • 反码:1111 1111 1111 1111 1111 1111 0111 1111
  • 补码:1111 1111 1111 1111 1111 1111 1000 0000
  • 截断:1000 0000
  • 提升:1111 1111 1111 1111 1111 1111 1000 0000
  • 打印4294967168
#include <stdio.h>
int main()
{
    char a = 128;
    printf("%u\n", a);
    return 0;
}
  • 原码:0000 0000 0000 0000 0000 0000 1000 0000
  • 反码:0111 1111 1111 1111 1111 1111 0111 1111
  • 补码:0111 1111 1111 1111 1111 1111 1000 0000
  • 截断:1000 0000
  • 提升:1111 1111 1111 1111 1111 1111 1000 0000
  • 还是打印4294967168

③第三题

int i= -20;
unsigned int j = 10;
printf("%d\n", i + j);//这里即使是i是int类型的会被提升为无符号int,但是还是存得下,只不过无符号的int会把符号位当成有效位,%d输出的时候依旧是会把这个位当成符号位来看
//按照补码的形式进行运算,最后格式化成为有符号整数
  • 原码:1000 0000 0000 0000 0000 0000 0001 0100(-20)
  • 反码:1111 1111 1111 1111 1111 1111 1110 1011
  • 补码:1111 1111 1111 1111 1111 1111 1110 1100
  • 截断:1111 1111 1111 1111 1111 1111 1110 1100(int)
  • 原、补、反码:0000 0000 0000 0000 0000 0000 0000 1010(10)
  • 相加后就是:1111 1111 1111 1111 1111 1111 1111 0110
  • 转化为补码:1111 1111 1111 1111 1111 1111 1111 0101
  • 转化为原码:0000 0000 0000 0000 0000 0000 0000 1010
  • 故打印出-10

④第四题

unsigned int i;
for(i = 9; i >= 0; i--)//由于无符号是没有负数的,就会导致unsigned int的类型是不存在负数的
{
    printf("%u\n",i);
}

⑤第五题

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]=-1000的情况,在a数组中只有值-1、-2、-3…-128、127、126…0、-1…而strlen会找到\0(即0)停止下来

3、浮点数在内存中的存储

(1)浮点型表示的范围和精度被定义在<float.h>中

(2)演示浮点数的存储例子

int main()
{
    int n = 9;
    float *pFloat = (float *)&n;
    printf("n的值为:%d\n",n);
    printf("*pFloat的值为:%f\n",*pFloat);

    *pFloat = 9.0;
    printf("num的值为:%d\n",n);
    printf("*pFloat的值为:%f\n",*pFloat);
    return 0;
}
//结果如下
//n的值为:9
//*pFloat的值为:0.000000
//num的值为:1091567616
//*pFloat的值为:9.000000
//这一行代码揭示了浮点类型和整形的存储是完全不同的

(3)浮点数的存储

①先得到SME的真实值

根据国际标准IEEE(电子和电子工程协会)754,任意一个二进制浮点数V可以表示为下面的形式:

  • V= (-1)S * M * 2E
    • (-1)S表示符号位,S=0,V为正数;S=1,V为负数。
    • M表示有效数字,1<=M<2
    • 2E表示指数位

对于例子5.5来说,写成二进制为101.1,相当于1.011*22,相当于(-1)0*1.011*22,即:S=0,M=1.011,E=2

②再得到SME的计算值

在这里插入图片描述
在这里插入图片描述

①对于存储32位浮点数的话就会1bit存储S,8bit存储E,23bit存储M(即1+8+23=32)
②对于存储64位浮点数的话就会1bit存储S,11bit存储E,52bit存储M(1+11+52=64)
因此双精度浮点数比单精度浮点数要来的准确。

  • S:只用一个bit位存储,比较简单,直接进行存储
  • M:由于1<=M<2,因此M总是写成1.xxxxxxx的形式(二进制没有2,只有1和0),所以这个1可以被忽略舍去,只保存后面的小数位即可,等到读取的时候,再把这个1加上去。这样做的目的:一是节省1位有效数字,可以保存24位(原本23位),多了一位就提高了精度。另外,如果M填不完就会在后面补0(同理64位也是这么操作的)
  • E:而E的存储情况会比较复杂
    • 首先E是一个无符号十进制整数,它的bit位为8时,取值0 ~ 255;它的bit为11位时,取值为0 ~ 2047,然而实际上科学计数法是有负指数的情况出现的(存在E<0的情况),因此需要设置一个中间数
    • 用8bit存储E时,中间数为127(32位);用11bit存储E时,中间数位1023(64位)。比如210的E是10,存储进浮点数后就会变成E=10+127=137再存放到计算机里(如果值太小,加上127还是为负数,这就已经不是float能表示得了)
    • 再将十进制的计算值的E转化为二进制
  • 将S、M、E的值按照SEM的顺序连接存储起来即可

(4)浮点数的取出

浮点数被存储进了内存,现在计算机要把这个浮点数重新取出来,而取出来的话,主要的难点还是体现在E的问题上

  • 当计算值E不全为0或不全为1
    • 把E的计算值减去127(或者1023)得到原来的真实值
    • 再将有效数字M前加上被忽略的1
  • 当计算值E全为0
    • 说明原本的E真实值是-127(或者-1023),这个数实在是太小了。因此直接定义这时E的真实值就是1-127(或者1-1023)
    • 有效数字M也不再还回1,而是直接还原为0.xxxxxxx这样的小数
    • 以上做法是为了表示+/-0,以及接近于0的很小的数字
  • 当计算值E全为1
    • 说明原本的E真实值是128(或者1024),这个数就是+/-无穷大了

(5)利用规则解释前面的例子

  • 下面,让我们回到一开始的问题:为什么9还原成浮点数,就成了 0.000000 呢?
    • ①首先,将9转化为二进制进行拆分,在32位0 00000000 000000000000000 00001001得到第一位符号位s=0,后面8位的指数 E=00000000,
      最后23位的有效数字M=000 0000 0000 0000 0000 1001
    • ②由于指数E全为0,所以符合上一节的第二种情况。因此,浮点数V就写成:V=(-1)0×0.00000000000000000001001×2(-126)=1.001×2(-146)
    • ③显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000
  • 再看例题的第二部分。请问浮点数9.0,如何用二进制表示?还原成十进制又是多少?
    • ①首先,浮点数9.0等于二进制的1001.0,即1.001×23。9.0 -> 1001.0 ->(-1)0×1.00123 -> s=0, M=1.001,E=3+127=130
    • ②那么,第一位的符号位s=0,有效数字M等于001后面再加20个0,凑满23位,指数E等于3+127=130,即10000010
    • ③所以,写成二进制形式,应该是S+E+M,即:0 10000010 00100000000000000000000
    • ④这个32位的二进制数,还原成十进制,正是 1091567616

噢对了,要注意补码的问题。我就老是忘记呢……上面的9是正数,原码就是其补码,故讨论起来少了转化成补码的一步。如果是负数,就一定要先转化成补码再讨论

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

limou3434

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值