• 【进阶C语言】数据在内存中的存储


    一、数据类型的介绍

    1.整形家族

    (1)char--字符型

    单位:一个字节,包括unsigned char和signed char

    (2)short--短整形

    单位:两个字节,包括unsigned short[int]和signed short[int]

    (3)int--整形

    单位:四个字节,包括unsigned int和signed int

    (4)long--长整型

    单位:4/8个字节,包括unsigned long和signed long

    2.浮点型家族

    (1)float--单精度浮点数

    单位:四个字节

    (2)double--双精度浮点数

    单位:八个字节

    3.构造类型

    (1)数组类型

    (2)结构体类型struct

    (3)枚举类型enum

    (4)联合类型union

    4.指针类型

    (1)int *pi--整形指针

    (2)char *pc--字符型指针

    (3)double *pf--双精度指针

    (4)void *pv--空类型指针

    5.空类型

    void 表示空类型(无类型)
    通常应用于函数的返回类型、函数的参数、指针类型

    二、整形数据在内存中的存储方式

    前言:整形在内存中存储的是补码的二进制序列(在初阶C语言的操作符1中有详解介绍原、反、补码)

    1.原、反、补码

    (1)概念介绍

    整数的二进制表示形式有原码、反码和补码三种,对于有符号的数据类型来说,这三种表示方法均可以分为符号位和数值位两部分,第一位数字(最高位)表示符号位,不差于数值大小的表示,正数用0表示,负数则是用1表示。若是无符号数据类型,则没有符号位,每一位都参与数值的表示。

    (2)原码

    直接将数值按照正负数的形式翻译成二进制就可以得到原码,有多少个比特位就有多少位二进制位。正数的原码、反码和补码都相同;但是负数则需要更具下面的定义去计算。
    (3)反码
    将原码的符号位不变,其他位依次按位取反就可以得到反码
    (4)补码
    反码 +1 就得到补码。

    2.补码在内存中的存储方法

    (1)使用补码存储的意义

    官方定义:

    在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统
    一处理;
    同时,加法和减法也可以统一处理( CPU 只有加法器 )此外,补码与原码相互转换,其运算过程
    是相同的,不需要额外的硬件电路。

    民间定义:方便转换和计算,如计算1-1的时候

    (2)存储顺序

    一个内存单元为一个字节,一般数据在内存中的显示形式都是十六进制,四个二进制数字=一个十六进制,八个二进制=一个字节。

    代码:

    1. #include
    2. int main()
    3. {
    4. unsigned int a=-10;
    5. int b = -10;
    6. return 0;
    7. }

    a的补码:11111111 11111111 11111111 11110110

    b的补码:11111111 11111111 11111111 11110110

    数据在内存中的表示图:

    分析图:

          为什么在内存中的存储顺序是反着的呢?所以下面的大小端介绍会告诉你答案!

    3.大小端介绍(*)

    (1)官方定义

    大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址
    中;
    小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位 , ,保存在内存的高地
    址中。

    (2)大小端民间定义

    我们知道:

         内存也是有顺序的,所以内存也就有高地址和低地址之分。

         数据的每位数字内容也是有大小之分的,如:个位、十位和百位。所以数据也就有了低字节位(最低位,如个位)和高字节位

    引出概念: 

    大端字节序储存:把一个数据的低字节位数据放在内存的高地址储存,数据的高字节位数据放在内存的低地址位储存。

    小端字节序存储:把一个数据的低字节位数据存放在内存的低地址中,数据的高字节位数据存储在内存的高地址处。

          大小端的存储顺序是由编译器决定;而且对一个字节的数据没有作用

    (3)简单实例操作

     代码:

    1. #include
    2. int main()
    3. {
    4. int a = 0x11223344;//十六进制数字
    5. return 0;
    6. }

    数据在内存中存储:

    (4)实战练习

    题目:设计程序判断该机器的字节序

    代码:

    1. #include
    2. int main()
    3. {
    4. int a = 1;
    5. char* p = (char*) & a;//只能访问一个字节
    6. if (*p == 1)
    7. printf("小端\n");
    8. else
    9. printf("大端\n");
    10. return 0;
    11. }

    运行结果:

    4.数据的存储与打印(*)--char类型

    前言:我们已经知道了整形数据是以二进制的补码在内存中存储的,接下来我们更进一步了解他的打印方式(包括有符号和无符号的深度刨析),前面在操作符2中我们已略微了解到了一种方法:整形提升

    signed char--8个比特位,所以最大可以存的数字是-128-127

    unsigned char--0-256

    (1)char类型存储整形范围

     引例:

    1. #include
    2. int main()
    3. {
    4. char a = -1;
    5. signed char b = -1;
    6. unsigned char c = -1;
    7. printf("a=%d\nb=%d\nc=%d\n",a,b,c);
    8. return 0;
    9. }

    运行结果:

    为什么同样都是存储-1,结果却大相径庭呢?接下来让我们走入char类型的世界


    char类型大介绍:

       首先,char内存占一个字节,一个字节=8个bit,所以就只能存放8个二进制位,所以可以存放的数据大小有范围。

    对于signed char(有符号的char):

    由此可知,存入char的整形数据的范围是:-128--127。

    当然超过这个范围的数据也可以存进去,但是实际的效果还是在:-128--127

    对于unsigned char(无符号char):

    现在你已经知道了char类型的范围,接下来一起进入实战模拟吧。

    (2)实战模拟

    (1)实战1 

    1. #include
    2. int main()
    3. {
    4. unsigned char a = -1;
    5. char b = 128;
    6. char c = 128;
    7. printf("a=%d\nb=%d\nc=%u\n",a,b,c);
    8. return 0;
    9. }

    运行结果:

     %d:以十进制的形式,打印有符号的整数

     %u:以十进制的形式,打印无符号的整数

     结果分析:

    第一步:分析存入各个变量中的数据

    第二步:分析打印的过程

    (2)实战2

    1. #include
    2. int main()
    3. {
    4. unsigned int i;
    5. for (i = 9; i >= 0; i--)
    6. {
    7. printf("%u\n",i);
    8. }
    9. return 0;
    10. }

    运行结果:

     这个程序不应该执行九次就停止运行了吗,怎么还能得出这么大的数字而且还死循环了。

    (3)实战3

    1. #include
    2. int main()
    3. {
    4. char a[1000];
    5. int i;
    6. for(i=0;i<1000;i++)
    7. {
    8. a[i] = -1 - i;
    9. }
    10. printf("%d",strlen(a));
    11. return 0;
    12. }

    运行结果:

    strlen是计算字符串长度的,当遇到\0的时候才会停下。结果显示该数组的大小为255

    三、浮点型数据在内存中的存储方式

          浮点数其实就是小数,精度越高,小数的位数越高。浮点数也是以二进制的形式存储的,但不是像整形数据一样以补码的形式存储。

    1.认识浮点数

    (1)浮点数类型分类

    float---单精度浮点数

    double---双精度浮点数

    long double---长精度浮点型

    (2)浮点数形式

    直接形式:3.14159

    科学计数法的形式:1E54==1.0*10^54

    2.浮点数转换规则

    (1)定义(表示规则)

    自我描述:任何一个二进制浮点数V都可以这么表示

    表达式:V=(-1)^S*M*2^E

    (-1)^S表示符号位,S为次方;当S=0,V为正数;当S=1,V为负数。

    M表示有效数字,1<=M<2

    2^E表示表示指数位

    (2)浮点数的二进制表示

    例如:十进制的:10.5;转化成二进制:1010.1

    (3)思路分解

    十进制:10.5

    二进制:1010.1

    转换成浮点数表示形式:V=(-1)^S*M*2^E

    3.浮点数存储规则

    弄清楚了浮点数是怎么转换成二进制的,接下来了解二进制浮点数是怎么存到内存中的。

    (1)存储位序

    根据公式:V=(-1)^S*M*2^E。我们只需要将S、M和E存入内存中即可(以二进制存入)

    规则:

          32位(bit)的浮点数(如:float),最高的一位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。

          64位(bit)的浮点数(如:double),最高的一位是符号位S,接着的11位是指数位E,剩下的52位为有效数字M。

    图示:

    32位:

    64位:

    (2)规定

    M:因为1<=M<2,所以M可以写成:1.xxxxxx的形式,其中xxxxxx表示小数部分。由于M的第一个数默认为1,所以1可以省略掉,只需要把xxxxxx存入内存中即可,后面不够自动补0。当读数的时候,再把1加回去。

    E:E是一个无符号整数,但是E会出现有负数的情况。所以:对于存入内存E的真实值,必须加上一个中间数。对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。

    例如:2^10,这个E=10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

    4.浮点数取出规则

    取出的时候注意两点,第一个就是M的值;重要的是第二个,取出时,指数E有三种情况(因为存进去的时候加了一个中间值),这些都是指在内存中的E。下面逐一介绍。

    (1)E不全为0不全为1(常规情况)

    这种情况比较简单,直接还原就可以。

    规则:指数E的计算值减去127(或1023),得到真实值;再将有效数字M前面加上第一位的1。

    (2)E全为0

    当存在内存中的E为0时,说明存入前指数E为-127(或-1023),表明此浮点数是一个特别小的数字。

    规则:此时的指数E=1-127(或者1-1023),即为真实值。有效数字M不再加上第一位的1,而是直接还原成0.xxxxxxx的小数。

    目的:为了表示±0,以及趋于0的很小的数字。

    (3)E全为1

    当内存中的E为全1的时候,说明原值是一个极大的值,趋于无穷。

    这时,如果有效数字M全为0,表示±无穷大,正负取决于S。

    5.实例与错误案例

     (1)代码展示

    1. #include
    2. int main()
    3. {
    4. int n = 9;
    5. float* p = (float*)&n;//将n的地址赋予p
    6. printf("%d\n",n);
    7. printf("%f\n", *p);
    8. *p = 9.0;//通过指针修改n的值
    9. printf("%d\n", n);
    10. printf("%f\n", *p);
    11. return 0;
    12. }

    结果展示:

    为什么会有作业的结果呢?这正是由于整数和浮点数的存储方式和取出不一样

    (2)结果分析

  • 相关阅读:
    企业工程项目管理系统源码(三控:进度组织、质量安全、预算资金成本、二平台:招采、设计管理)
    nRF52832——大量数据传输时导致蓝牙断开连接,且无法被搜索到的解决方案(广播参数的设置、程序设计方法)
    【Golang】gin框架入门
    Javascript知识【元素内容体实操案例】
    关于c#:displayname属性
    基数排序.
    Springboot配置WebMvcConfig解决Cors非同源访问跨域问题
    二次封装element select,通过computed计算属性解决v-model父子组件传递值问题
    VL53L5CX驱动开发(5)----运动阈值检测
    Vue中this指向问题
  • 原文地址:https://blog.csdn.net/2301_77053417/article/details/132816293