• C语言进阶篇——深度解剖数据在内存中的存储(配练习)


    大家好!我是沐曦希💕

    💫数据类型介绍

    通过前面的学习得知了基本的内置类型(C语言本身就有的类型):

    char        //字符数据类型
    short       //短整型
    int         //整形
    long        //长整型
    long long   //更长的整形    //C99标准引入的
    float       //单精度浮点数
    double      //双精度浮点数
    //C语言有没有字符串类型?
    //答案是:否定的,因为C语言是不提供字符串类型的,但是它有字符串。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    以及他们所占存储空间的大小。

    char           //1个字节
    short         //2个字节
    int          //4个字节
    long        //4或者8个字节,在x86环境下是4个字节,在x64环境下是8个字节
    long long  //8个字节
    float     //4个字节
    double   //8给字节
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    类型的意义:

    1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)
    2. 如何看待内存空间的视角。

    🎇类型的基本归类

    🎈整型家族

    char
     unsigned char//无符号的字符类型
     //取值范围是0~255
     //无符号表示二进制的最高位不表示正负,该整型只为正数。
     //但可以储存负数,只是值会变成很大的正数
     signed char//有符号字符
     //取值范围是-128~127
     //因为字符的本质是ASCII码值,在内存中以ASCII码值进行存储,所以划分到整型家族
    short
     unsigned short [int]//无符号短整型
     signed short [int]//有符号短整型
    int
     unsigned int//无符号整型
     signed int//有符号整型
    long
     unsigned long [int]//无符号长整型
     signed long [int]//有符号整型
    long long
     unsigned long long [int]//无符号更长的整型
     signed long long [int]  //有符号更长的整型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    char到底是signed char (取值范围-128~127)还是unsigned char(取值范围0~255) 标准是为定义的,取决于编译器的实现,小沐所使用的VS2019环境的char是signed char。

    char a// signed char a 或者 unsigned char a
    
    • 1

    int 标准定义是 signed int ,有符号整型,4个字节,32个比特位。

    int a = 10//signed int a
    //转换成二进制是00000000000000000000000000001010
    
    • 1
    • 2

    二进制的最高一位是符号位,当最高位是0时表示改数为正数,当最高为是1时,表示该数为负数。

    unsigned int a;
    //11111111111111111111111111111111
    //1*2^32+1*2^31+...+1*2^2+1*2^1+1*2^0
    signed int a;//int a;
    //11111111111111111111111111111111
    //-(1*2^31+1*2^30+...+1*2^1+1*2^0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    🎆浮点数家族

    只要表示小数就可以使用浮点型

    float    //单精度浮点数
    double   //双精度浮点数
    
    • 1
    • 2

    float 的精度低,存储的数值范围较小
    double 的精度高,存储的数值范围大

    🎉构造类型

    > 数组类型    //arr[10];
    > 结构体类型 struct  //struct Peo p;
    > 枚举类型 enum
    > 联合类型 union
    
    • 1
    • 2
    • 3
    • 4
    int arr[5];//int [5];
    int arr2[8];//int [8];
    char arr3[5];//char [5];
    
    • 1
    • 2
    • 3

    🚗指针类型

    int *pi;//整型指针
    char *pc;//字符指针
    float* pf;//单精度浮点数指针
    void* pv;//空类型指针
    double* pd;//双精度浮点数指针
    long* pl;//长整型指针
    long long* pll;//更长的整型指针
    short* ps;//短整型指针
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注:不管指针所指的是变量是什么类型,指针的大小在x86环境下是4个字节;在x64环境下是8个字节。

    🎇空类型

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

    例如:

    #include<stdio.h>
    voide test(void)
    {
    	printf("hello world");
    }
    int main()
    {
    	test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    第一个void表示函数不会返回值,第二个表示函数不需要传如何参数。但是可以进行传参。

    🚩整形在内存中的存储

    一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。
    例如:

    int a = 10;
    int b = -10;
    
    • 1
    • 2

    我们知道为 a 分配四个字节的空间。
    那如何存储?
    计算机存储数值时时存储的该数值的二进制的补码的,而补码是通过原码和反码进行换算得到的。

    🎇原码、反码、补码

    计算机中的整数有三种2进制表示方法,即原码、反码和补码。
    三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位
    正数的原、反、补码都相同。
    负整数的三种表示方法各不相同,需要通过计算的。

    🎊原码

    直接将数值按照正负数的形式翻译成二进制就可以得到原码。

    🎊反码

    将原码的符号位不变,其他位依次按位取反就可以得到反码。

    💥补码

    反码+1就得到补码。

    int a = 10;
    //00000000000000000000000000001010 a的原码
    //00000000000000000000000000001010 a的反码
    //00000000000000000000000000001010 a的补码
    //0x0000000a
    int b = -10;
    //10000000000000000000000000001010 b的原码
    //0x8000000a
    //11111111111111111111111111110101 b的反码
    //0xfffffff5
    //11111111111111111111111111110110 b的补码
    //0xfffffff6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    //数值有不同的表示形式
    //10进制的 21   
    //2进制:0b10101
    //8进制:025
    //16进制:0x15
    
    • 1
    • 2
    • 3
    • 4
    • 5

    补码-1=反码,反码除了符号位不变,其他位按位取反是原码;
    补码除了符号位不变,其他位按位取反后+1就是原码。

    对于整形来说:数据存放内存中其实存放的是补码。

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

    我们看看在内存中的存储:
    在这里插入图片描述
    我们可以看到对于a和b分别存储的是补码。但是我们发现顺序有点不对劲。
    这是又为什么?

    🎄大小端介绍

    什么大端小端:

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

    例如:

    0x11223344
    
    • 1

    在这里插入图片描述

    为什么有大端和小端:

    因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
    例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

    ✨一道百度的面试题

    设计一个小程序来判断当前机器的字节序。

    在这里插入图片描述
    可以通过类型转换和不同类型指针的访问权限来实现。

    //代码1
    #include <stdio.h>
    int check_sys()
    {
    	int i = 1;
    	return (*(char*)&i);
    }
    int main()
    {
    	int ret = check_sys();
    	if (ret == 1)
    	{
    		printf("小端\n");
    	}
    	else
    	{
    		printf("大端\n");
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    //代码二
    #include <stdio.h>
    int check_sys()
    {
    	union
    	{
    		int i;
    		char c;
    	}un;
    	un.i = 1;
    	return un.c;
    }
    int main()
    {
    	int ret = check_sys();
    	if (ret == 1)
    	{
    		printf("小端\n");
    	}
    	else
    	{
    		printf("大端\n");
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述
    两个字节以上才有字节序可讲。

    🎡练习

    🎇练习一

    //输出什么?
    #include <stdio.h>
    int main()
    {
        char a= -1;//小沐使用的VS2019认定 char 是 signed char
        signed char b=-1;
        unsigned char c=-1;
        printf("a=%d,b=%d,c=%d",a,b,c);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    把整型的-1赋值给c,但是c是无符号的char,那么要截断和最高位不再是符号位。

    unsigned char c = -1;
    //11111111
    //即c=255
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    🔭练习二

    #include <stdio.h>
    int main()
    {
        char a = -128;
        printf("%u\n", a);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注:%u是打印无符号整型的意思,即unsigned int。
    由char类型变成int类型需要经过整型提升。(小沐前面的全面了解操作符的表达式求值写了相关整型提升的知识点)

    char a = -128;
    //10000000
    //整型提升后
    //11111111111111111111111110000000
    //因为是无符号的整数,所以上面的二进制是原码,反码和补码,为很大的数字
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    🎆练习三

    #include <stdio.h>
    int main()
    {
        char a = 128;
        printf("%u\n",a);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    128
    //10000000
    //整型提升
    //11111111111111111111111110000000
    //与练习二一样
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    🎇练习四

    #include<stdio.h>
    int main()
    {
    	int i = -20;
    	unsigned  int  j = 10;
    	printf("%d\n", i + j);
    	//按照补码的形式进行运算,最后格式化成为有符号整数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    🎆练习五

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

    因为i是无符号整型,所以i是恒大于等于0。
    在这里插入图片描述

    00000000000000000000000000000000
    -1
    //得到
    11111111111111111111111111111111
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述
    可以看到是一个死循环。

    🎇练习六

    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    strlen是求字符串的长度,关注的是字符串‘\0’(ASCII码值是0)之前出现多少个字符。
    而signed char 的取值范围是-128~127

    for (i = 0; i < 1000; i++)
        {
            a[i] = -1 - i;//-1;-2...,-128,127...1,0
            //-1;-2...,-128,127...1,0......
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    strlen(a)的值为255
    在这里插入图片描述

    🎆练习七

    #include <stdio.h>
    unsigned char i = 0;
    int main()
    {
        for(i = 0;i<=255;i++)
       {
            printf("hello world\n");
       }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    因为i是unsigned char,故i是很小于等于255的,即结果为死循环。
    在这里插入图片描述
    小知识:
    strlen的返回值是无符号整型。
    在这里插入图片描述
    在这里插入图片描述
    无符号整型相减得到的是无符号整型,有符号整型与无符号整型运算时得到的也是无符号整型。

    🌈写在最后

    在这里插入图片描述

    友友们觉得不错的可以给个关注,点赞或者收藏哦!😘感谢各位友友们的支持。

    你的❤️点赞是我创作的动力的源泉
    你的✨收藏是我奋斗的方向
    你的🙌关注是对我最大的支持
    你的✏️评论是我前进的明灯
    创作不易,希望大佬你支持一下小沐吧😘

  • 相关阅读:
    LCP 55.采集果实
    数据库设计
    PDF怎么转换成PPT
    React路由与导航
    GO实现Redis:GO实现Redis的AOF持久化(4)
    JAVA:实现检查字符串是否为 pangram 字符串算法(附完整源码)
    SpringMVC中异常处理详解
    【MySQL从入门到精通】【高级篇】(十一)Hash索引、AVL树、B树与B+树对比
    程序员的护城河-并发编程
    MMDetection系列 | 5. MMDetection运行配置介绍
  • 原文地址:https://blog.csdn.net/m0_68931081/article/details/124985272