• 梦开始的地方 —— C语言(枚举+位段+联合体)



    位段

    1. 什么是位段?

    要想了解位段就得先学会使用结构体。文章链接——>详解结构体

    位段的声明和结构体是十分类似的,它们有两个不同之处

    1. 位段的成员必须是intunsigned intsigned int
    2. 位段的成员名后面有一个冒号和数字。

    定义一个名为test的位段

    struct test {
    	int a : 2;
    	int b : 5;
    	int c : 10;
    	int d : 30;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 冒号后面的数字表示该成员占几个比特位(注意有些平台有符号数的符号位也算一个比特位)
    • 比如在VS2019环境,a只有2个比特位00,第一位就是符号,那它只能表示-2~1的数字

    那么这个位段test的大小是多少?

    printf("%d\n", sizeof(struct test));
    
    • 1

    输出是8,也就是8个字节。

    • a占2个比特位,b占5个比特位,c占10个比特位,加起来也就是17个比特位,4个字节就够了
    • 而d需要30个比特位,不够则再开辟一个4个字节的空间
    • 但d是使用前面剩余的一部分加上后面新开辟的空间,还是前面剩余的空间直接浪费使用新开辟的空间这就不得而知了
    • C语言标准并没有规定剩余的空间是否一定要使用

    2. 位段的内存分配

    1. 位段的成员变量可以是intunsigned intsigned intchar
    2. 位段的空间上按照需要以4个字节(int),或者是1个字节(char)的方式来开辟的。
    3. 位段存在着很多不确定因素,它是不夸平台的,如果考虑可移植性就要避免使用位段。

    举个列子,下面这位段是如何开辟内存的呢?

    #include 
    
    struct test {
    
    	char a : 3;
    	char b : 4;
    	char c : 5;
    	char d : 4;
    };
    int main()
    {
    	struct test t = { 0 };
    	t.a = 10;
    	t.b = 12;
    	t.c = 3;
    	t.d = 4; 
    	printf("%d\n", sizeof(struct test));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 把10赋值给a,a只有3个比特位,存放010
    • 把12赋值给b,b有4个比特位,存放1100
    • 把3赋值给c,c有5个比特位,存放00011
    • 把4赋值给d,d有4个比特位,在内存中存放0100

    在这里插入图片描述

    VS2019调试环境

    在这里插入图片描述

    通过上面的调试发现

    • 在VS2019平台
    • 前一个空间剩余的比特位不够时,直接浪费,使用新开辟空间的比特位
    • 一个字节内部,是从低位向高位使用的。

    3. 位段跨平台问题

    前面说过位段的可移植性差,就是因为它的一些不确定因素

    1. int位段被当做有符号数还是无符号数是不确定的
    2. 位段中最大位的数目不能确定(6位机器最大16,32位机器最大32,写成27,在16位机器会出问题 )
    3. 位段中的成员变量在内存中分配空间,是从左向右分配还是从右向左分配,C语言标准也未定义。
    4. 当一个结构体包含两个位段,第二个位段成员比较大,前一个位段开辟空间剩余的比特位不够时,是浪费剩余的位,还是用掉剩余的再加上新开辟的空间,C语言标准也是未定义的。

    4. 位段的应用

    这是ipv4首部的一张图,网络传输要保证正确的同时还要保证速度,把每一个字段精确的设计,可以避免没有必要的浪费,就能让数据包尽可能的小,提高传输效率。

    在这里插入图片描述

    联合(共用体)

    1. 联合类型的定义

    联合是一种特殊的自定义类型,它也和结构体类似,也可以包含一系列成员,特征是这些成员共用同一块内存空间,所以叫做联合也可以叫做共用体。

    通过union关键字定义一个联合类型

    #include 
    
    union User
    {
    	int id;
    	char ch;
    };
    int main()
    {
    	union User u;
        
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2. 联合的特点

    联合的成员是共用一块内存空间的,这样以一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)

    来看一段代码

    #include 
    
    union User
    {
    	int id;
    	char ch;
    };
    int main()
    {
    	union User u;
    	printf("%p\n", &(u.id));
    	printf("%p\n", &(u.ch));
    
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    打印结果

    006FF8B0
    006FF8B0
    
    • 1
    • 2

    发现这两个成员变量果然用的是同一块内存空间。

    再来看一段代码验证一下

    #include 
    
    union User
    {
    	int id;
    	char ch;
    };
    int main()
    {
    	union User u;
    	u.id = 0x11223344;
    	u.ch = 0x55;
    	printf("%x\n", u.id);
    
    
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    打印结果

    11223355
    
    • 1
    • 因为我们这里是VS2019-X86环境,是小端存储
    • 联合的成员变量共用一块内存,那么ch在赋值的时候就会覆盖掉id的值

    通过联合也可以判断大小端

    • 如果是小端,ch拿 的是低位的地址就能拿到1
    • 如果是大端,高位存低位,低位存高位,拿么ch拿低位拿到的就是0
    #include 
    
    union User
    {
    	int id;
    	char ch;
    };
    int main()
    {
    	union User u;
    	u.id = 1;
    	if (u.id)
    	{
    		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

    3. 联合大小的计算

    • 联合的大小至少是最大成员的大小
    • 当最大成员大小不是最大对齐数整数倍的时候,就要对齐到最大对齐数的整数倍

    来看一个列子

    #include 
    
    union u1
    {
    	char arr[5];//5
    	int a;//4
    };
    union u2
    {
    	short arr[7];//7
    	int a;//4
    };
    int main()
    {
    	printf("%d\n", sizeof(union u1));//
    	printf("%d\n", sizeof(union u2));//
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    打印结果

    8
    16
    
    • 1
    • 2
    • 联合中,数组大对齐数是它的成员对齐数,所以char arr[5]的对齐数是1
    • a是一个整形它的对齐数是4,所以该联合的最大对齐数是4
    • 但arr数组大小是5,当最大成员大小不是最大对齐数整数倍的时候,就要对齐到最大对齐数的整数倍
    • 所以对齐到最大对齐数4的整数倍8

    联合类型u2

    • short arr[7],该数组大小是14,它的对齐数是数组中的成员也就是2
    • a的对齐数是4,而14不是4的倍数,要对齐到4的倍数16

    所以联合的大小至少是最大成员的大小,但不一定是最大成员的大小,还要考虑到最大对齐数对齐

    枚举

    枚举:顾名思义就是一个一个列举。当我们有些类型,不适合用基本类型来,描述的时候就可以使用枚举。

    1. 枚举的定义

    通过enum关键字来定义枚举类型,用枚举来描述一个三原色

    enum RGB
    {
    	RED,
    	GREEN,
    	BLUE
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    RGB就是自定义的枚举类型,枚举类型的成员是有初始值的,默认从0开始

    #include 
    
    enum RGB
    {
    	RED,
    	GREEN,
    	BLUE
    };
    
    int main()
    {
    	printf("%d\n",RED);
    	printf("%d\n",GREEN);
    	printf("%d\n",BLUE);
    
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    打印结果

    1
    2
    3
    
    • 1
    • 2
    • 3

    当然也可以修改默认值

    #include 
    
    enum RGB
    {
    	RED = 10,
    	GREEN = 15,
    	BLUE
    };
    
    int main()
    {
    	printf("%d\n",RED);
    	printf("%d\n",GREEN);
    	printf("%d\n",BLUE);
    
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    打印结果

    10
    15
    16
    
    • 1
    • 2
    • 3

    2. 为什么要使用枚举常量

    枚举可以做到事情#define也可以做到,为什么要使用枚举?

    枚举的一些优点

    • 增加代码的可维护性和可读性
    • 枚举具有类型检查,而#define没有
    • 定义方便,一次可以定义多个常量,而#define一次只能定义一个
    • 方便调试,#define是不能调试的,#define在预编译阶段就把对应的值给替换了
    #include 
    
    enum RGB
    {
    	RED = 10,
    	GREEN = 15,
    	BLUE
    };
    
    int main()
    {
    	enum RGB color = RED;
    	printf("%d\n", color);
    
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

  • 相关阅读:
    31、Java——JDBC实现账号密码登录
    浅谈 —— AAA认证(认证+授权)详解+配置
    带你了解MySQL数据库(三)
    【PTA】《数据结构与算法》线性结构复习题
    shell变量的五种赋值方式
    Python:实现perfect cube完全立方数算法(附完整源码)
    CSP登机牌条码202112-3
    程序员开发必备,开发资源资料分享【4】
    传感器融合与自动驾驶
    Mocha + Chai 测试环境配置,支持 ES6 语法
  • 原文地址:https://blog.csdn.net/weixin_53946852/article/details/127860876