• 【C】自定义类型(二)位段,枚举,联合



    🙈个人主页 对de起日子
    👉系列专栏【C语言–大佬之路】
    🎈今日心语:谨慎的选择自己的生活,不要轻易让自己迷失在各种诱惑里。

    前一章我们介绍了结构体,这一章我们来介绍一下内容:

    • 结构体实现位段(位段的填充&可移植性)
    • 枚举
      • 枚举类型的定义
      • 枚举的优点
      • 枚举的使用
    • 联合
      • 联合类型的定义
      • 联合的特点
      • 联合大小的计算


    1. 位段

    结构体学完我们就得拥有结构体实现位段的能力。

    1.1 什么是位段

    位段的声明和结构是类似的,有两个不同:
    1.位段的成员必须是int、unsigned int 或signed int。
    2.位段的成员名后边有一个冒号和一个数字。

    直接上代码:

    #include 
    
    struct S//结构体
    {
    	int a;
    	int b;
    	int c;
    	int d;
    };
    
    struct A//位段
    {
    	int _a : 2;
    	int _b : 5;
    	int _c : 10;
    	int _d : 30;
    	
    };
     int main()
    {
    	printf("%d\n", sizeof(struct S));//16
    	printf("%d\n", sizeof(struct A));
    
    	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

    上面代码中struct S是结构体,struct A是位段
    而通过前面的结构体的对齐的学习,我们很容易得出该结构体的大小为16个字节,那么该位段的大小为多少呢?
    运行结果:

    在这里插入图片描述
    为什么struct A的大小为8个字节呢?

    在这里插入图片描述
    通过上面的分析,我们得出了位段成员共需要6个字节的空间,而实际结果却是8个字节,说明其在内存中有自己的分配方式,这就需要我们了解以下内容了


    1.2 位段的内存分配

    1. 位段的成员可以是int unsigned int signed int或者是char(属于整形家族)类型
    2. 位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
    3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

    在这里插入图片描述
    如上图,解释了我们之前的疑问,
    但是这里成员d可以有两种选择:
    1.选择先使用成员c剩余的15个空间,再使用新开辟的空间
    2.选择直接使用新开辟的空间
    这里c语言中并没有明确的规定,所以这里涉及到了位段的不确定性,后面我们会介绍位段的跨平台性

    • 位段在vs2019中的测试:
    #include
    struct S
    {
    	char a : 3;
    	char b : 4;
    	char c : 5;
    	char d : 4;
    };
    
    int main()
    {
    	struct S s = { 0 };
    	s.a = 10;
    	s.b = 12;
    	s.c = 3;
    	s.d = 4;
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    1.3 位段的跨平台问题

    1. int 位段被当成有符号数还是无符号数是不确定的。
    2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
    3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
    4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
      总结:
      如我们刚开始的代码,同样的
      跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间但是有跨平台的问题存在。
      在这里插入图片描述

    1.4 位段的应用

    在这里插入图片描述


    2. 枚举

    枚举顾名思义就是一一列举。
    把可能的取值一一列举。
    比如我们现实生活中:
    一周的星期一到星期日是有限的7天,可以一一列举。
    性别有:男、女、保密,也可以一一列举。
    月份有12个月,也可以一一列举

    2.1 枚举类型的定义

    #include
    enum Day//星期
    {
    	Mon,//默认0
    	Tues,//默认1
    	Wed,//默认2
    	Thur,//默认3
    	Fri,//默认4
    	Sat,//默认5
    	Sun//默认6
    };
    //枚举类型
    
    enum Color//类型
    {
    	//枚举的可能取值
    	//每一个可能的取值是常量-枚举常量
    	RED = 1,   //常量在这里是可以赋值的,
    	GREEN = 2,
    	BLUE = 4
    };
    enum Sex//性别
    {
    	MALE = 2,
    	FEMALE,//默认3
    	SECRET//默认4   如果只给第一个常量赋值,下面的会+1
    };
    int main()
    {
    	//enum Color color = BLUE;//创建了一个变量
    
    	printf("%d\n", Mon);
    	printf("%d\n", Tues);
    	printf("%d\n", Wed);
    	printf("%d\n", Thur);
    	printf("%d\n", Fri);
    	printf("%d\n", Sat);
    	printf("%d\n", Sun);
    
    	printf("\n");
    
    	printf("%d\n", RED);
    	printf("%d\n", GREEN);
    	printf("%d\n", BLUE);
    	
    	printf("\n");
    
    	printf("%d\n", MALE);
    	printf("%d\n", FEMALE);
    	printf("%d\n", SECRET);
    	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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    在这里插入图片描述

    以上定义的enum Day,enum Sex,enum Color都是枚举类型。
    {}中的内容是枚举类型的可能取值,也叫枚举常量。
    这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。

    2.2 枚举的优点

    为什么使用枚举?
    我们可以使用#define 定义常量,为什么非要使用枚举?
    枚举的优点:

    1. 增加代码的可读性和可维护性
    2. 枚举有类型检查和#define定义的标识符比较,更加严谨。
    3. 防止了命名污染(封装)
    4. 便于调试
    5. 使用方便,一次可以定义多个常量

    2.3 枚举的使用

    enum Color//颜色
    {
    	RED = 1,
    	GREEN = 2,
    	BLUE = 4
    };
    enum Color clr = GREEN;
    //只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
    clr = 5;
    //这种赋值方法是错误的,类型不匹配
    //在有严格的类型检查环境中是会报错的
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3. 联合(共用体

    3.1 联合类型的定义

    联合也是一种特殊的自定义类型
    这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)。
    比如:

    //联合类型的声明
    union Un
    {
        char c;
        int i;
    };
    //联合变量的定义
    union Un un;
    //计算连个变量的大小
    printf("%d\n", sizeof(un));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.2 联合的特点

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

    //联合体(共用体)
    //联合类型的声明
    union Un
    {
    	char c;//1
    	int i;//4
    	double d;//8
    };
    
    int main()
    {
    	//联合变量的定义
    	union Un un;
    	printf("%d\n", sizeof(un));//计算连个变量的大小
    	printf("%p\n", &un);
    	printf("%p\n", &un.c );
    	printf("%p\n", &un.i);
    	printf("%p\n", &un.d);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    3.3联合体的应用

    • 判断当前计算机的大小端存储

    在这里插入图片描述

    //代码1
    int main()
    {
    	int num = 1;
    	char* p = (char*)#
    	if (*p == 1)
    		printf("小端\n");
    	else
    		printf("大端\n");
    	return 0;
    }
    
    //代码2
    //封装函数
    int check_sys()
    {
    	int num = 1;
    	char* p = (char*)#
    	if (*p == 1)
    		return 1;
    	else
    		return 0;
    }
    int main()
    {
    	int ret = check_sys();
    	if (ret == 1)
    		printf("小端\n");
    	else
    		printf("大端\n");
    	return 0;
    }
    
    //代码3
    int check_sys()
    {
    	int num = 1;
    	//char* p = (char*)#
    	/*if (*(char*)&num == 1)
    		return 1;
    	else
    		return 0;*/
    	return *(char*)#
    }
    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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    上面的代码每一个都比前面的代码有所改进,而下面我们要利用联合体来实现大小端的测试:

    //代码4
    //联合体
    int check_sys()
    {
    	
    	union Un
    	{
    		char c;//1
    		int i;//4
    	}u;
    	u.i = 1;
    	return u.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

    在这里插入图片描述
    这里如果改变 i ,c也会随之改变,因为联合体的成员是共用一个空间的

    3.4 联合大小的计算

    • 联合的大小至少是最大成员的大小,但不一定是最大成员的大小。
    • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
      比如:
    union Un
    {
    	char arr[5];//5
    	int i;//4
    };
    
    int main()
    {
    	printf("%d\n", sizeof(union Un));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    运行结果:
    在这里插入图片描述
    我们可以看到,上面代码中最大成员的大小为5个字节,但输出的结果却是8,下面我们就来介绍联合的对齐规则:

    在这里插入图片描述
    关于对齐数的概念我们在上一篇文章 【C】自定义类型(一)结构体中已经介绍,这里就不作介绍。

    联合的大小至少是最大成员的大小。
    上面我们最大对齐数的整数倍可能是4,8,16
    上面最大成员的大小为5
    当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
    而这时8既是最大对齐数的整数倍,又大于最大成员的大小,所以联合体的大小为8。


    结语:

    这里我们关于【C】自定义类型(二)结构体的内容就介绍完了,
    文章中某些内容我们之前有介绍,所以只是一笔带过,还请谅解。
    希望以上内容对大家有所帮助👀,如有不足望指出🙏

    在这里插入图片描述


    加油!!

  • 相关阅读:
    AIOps指标异常检测之无监督算法
    flask SQLAlchemy
    Dubbo源码解析一服务暴露与发现
    【C语言从入门到放弃 4】字符串,结构体,共用体,位域,typedef详解
    Day1跟李沐学AI-深度学习课程00-04【预告、课程安排、深度学习介绍、安装、数据操作+数据预处理】
    分布式系统中的一些问题
    JVM内存泄漏分析的demo
    身份证读卡器Qt语言实现Linux系统开发集成
    系统架构技能之设计模式-工厂模式
    Codeforces 346B 不包含子串的LCS
  • 原文地址:https://blog.csdn.net/qq_72069067/article/details/127614767