【C语言】自定义类型:联合体和枚举

server/2024/10/15 15:36:51/

在这里插入图片描述

文章目录

  • 一、联合体(共同体)
    • 1.联合体类型的声明
    • 2.联合体的特点
      • 测试1
      • 测试2
    • 3.联合体大小的计算
      • 例1
      • 例2
    • 4.联合体小练习
    • 5.结构体和联合体内存占用的对比
    • 6.联合体的应用
  • 二、枚举
    • 1.枚举类型的声明
    • 2.枚举类型的优点
    • 3.枚举类型的使用

一、联合体(共同体)

1.联合体类型的声明

   像结构体⼀样,联合体也是由⼀个或者多个成员构成,这些成员可以是不同的类型
   联合体的特点是所有成员共⽤同⼀块内存空间,所以联合体也叫共同体,由于所有成员共用一块空间,所以编译器只为最⼤的成员分配足够的内存空间 ,并且当给联合体其中⼀个成员赋值时,其他成员的值也跟着变化,我们后面也会讲到
   现在我们从联合体类型的声明开始学习,它的声明也和结构体的声明相似,结构体声明时使用struct关键字,而联合体声明时使用union关键字,如下:

union un
{char c;int i;
};

   它创建变量的方式和结构体都是类似的,如下:

union un
{char c;int i;
};int main()
{union un s;return 0;
}

   其它语法知识点和结构体都是类似的,这里就不再多讲了,接着学习联合体的特点,也可以说是结构体和联合体的区别之处
   如果还没有学习结构体,可以参考文章:【C语言】自定义类型:结构体

2.联合体的特点

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

测试1

   现在我们来做个测试,测试一下联合体成员的地址是否相同,以及联合体本身的地址和它成员地址的关系,如下代码:

#include <stdio.h>union Un
{char c;int i;
};
int main()
{union Un un = { 0 };printf("%p\n", &(un.i));printf("%p\n", &(un.c));printf("%p\n", &un);return 0;
}

   接着我们运行它,来看看它们三个地址的关系:
在这里插入图片描述
   可以看到,联合体成员的地址是相同的,并且联合体本身也和联合体成员的地址相同,基本上就可以说明,联合体开辟空间时,所有成员共用一块空间

测试2

   接下来我们再举一个例子来测试联合体的空间是否是共用的,如下:

#include <stdio.h>union Un
{char c;int i;
};int main()
{union Un un = { 0 };un.i = 0x11223344;un.c = 0x55;printf("%x\n", un.i);return 0;
}

   试着分析这个代码最后的运行结果,一定要先自己试着分析,然后再看答案
我们来看分析:首先前面声明了一个联合体类型Un,然后使用它创建了一个联合体变量un,随后将其初始化为了0
   然后进行了两次赋值,给i赋值了16进制数11223344,把c赋值为了16进制数55,如果i和c共用相同的空间,那么我们在更改c的时候,i应该也会跟着改变,我们来看看内存里的存储:
   这是初始化完i时,结构体内存的存储:
在这里插入图片描述
   可以看到,这里VS用小端字节序的方式将i存放到了内存中,如果不知道什么是小端字节序,可以参考该文章:【C语言】数据在内存中的存储(万字解析)
   现在已经把i存放进内存了,代码下一步就是将c改成16进制数55,那我们看看i会不会跟着一起改变:
在这里插入图片描述
   可以看到内存中i的第一个字节被修改为了55,说明更改c确实连带着把我们的i更改了,我们可以画一个图来更清楚的阐述这个变化过程:
在这里插入图片描述
   所以在这个联合体中,我们可以分析得到:整型变量i和字符变量c占据同一块空间,而c占据的就是i的第一个字节,当我们对c进行修改时,也就是对i第一个字节的修改,所以当我们把c修改为0x55时,i也就跟着改变了
   我们来看看代码的运行结果:
在这里插入图片描述

   可以看到,程序的运行结果与我们分析的一致,所以根据这个例子,我们再一次证明了联合体的成员共用一段相同的空间

3.联合体大小的计算

   我们首先来看联合体在存储时的两个规则:

  1. 联合体的大小至少是最⼤成员的大小,确保联合体的大小可以装下每一个单一成员
  2. 当最⼤成员大小不是最大对齐数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍

例1

   还是老方法,实践出真知,我们举一个例子来说明这两条规则:

#include <stdio.h>union Un1
{char c[5];int i;
};int main()
{//下⾯输出的结果是什么?printf("%d\n", sizeof(union Un1));return 0;
}

   我们首先看第一条规则:联合体的大小至少是最大成员的大小,而我们这里的联合体Un1它最大的成员是c,是一个字符数组,大小是5个字节,所以根据第一条规则,这个联合体的大小至少是5个字节
   然后就是第二条规则,这条规则需要查看这个联合体中的最大对齐数,联合体的大小必须是最大对齐数的倍数,而联合体成员c的对齐数为1,成员i的对齐数为4,所以可以知道该联合体最大对齐数为4
   根据第一条规则我们知道了联合体Un1的大小至少是5个字节,根据第二条规则我们知道了Un1的大小必须是4的倍数,所以综和这两点,答案已经呼之欲出:结构体Un1的大小是8个字节
   最后我们来看看代码运行结果:
在这里插入图片描述

例2

#include <stdio.h>union Un2
{short c[7];int i;
};int main()
{//下⾯输出的结果是什么?printf("%d\n", sizeof(union Un2));return 0;
}

   通过例1,我们已经知道两个规则具体的作用了,现在我们做这个题应该是比较简单的,但是我们还是像例1那样仔细分析一下:
   首先根据第一条规则,我们要看成员中,谁最大,很明显最大的就是c数组,占据14个字节,所以联合体Un2至少都有14个字节
   然后来看第二个规则,我们要看成员中的最大对齐数,第一个成员c的对齐数是2,第二个成员的对齐数是4,所以联合体Un2的最大对齐数是4,它的大小应该是4的倍数
   所以综上两个规则,联合体Un2的大小至少14个字节,还要是4的倍数,所以联合体Un2的大小为16个字节,我们来看看运行结果:
在这里插入图片描述

4.联合体小练习

   使用联合体写⼀个程序,判断当前机器是大端字节序还是小端字节序
   我们先复习一下之前采用的方法,方便我们思考这道题,是创建一个整型变量,赋值为1,然后将它的地址强制转换为字符类型存放起来,然后通过这个字符指针去访问整型变量的第一个字节,看看拿到的是否是1
   如果是1说明是小端字节序,是0就是大端字节序,这里再放一下它的代码,如下:

#include <stdio.h>int main()
{int a = 1;char* p = (char*)&a;if (*p == 1)printf("小端字节序\n");elseprintf("大端字节序\n");return 0;
}

运行结果:
在这里插入图片描述
   接下来我们就来看如何使用联合体实现这个功能
   其实很简单,上面案例的本质就是利用指针来访问整型a的第一个字节,而我们的联合体本身就可以做到这一点
   只要在联合体中创建一个整型成员a,创建一个字符型成员c,由于共用空间,那么c就可以直接访问a的第一个字节
   接着我们就照着上面那个案例的思路,用联合体实现一下,如下:

#include <stdio.h>union Un
{int a;char c;
};int main()
{union Un un;un.a = 1;if (un.c == 1)printf("小端字节序\n");elseprintf("大端字节序\n");return 0;
}

运行结果:
在这里插入图片描述

5.结构体和联合体内存占用的对比

   我们来简单对比一下同样的成员下,结构体和联合体内存占用的情况,如下例:

struct S
{char c;int i;
};union Un
{char c;int i;
};

   我们来画图看看它们在内存中的占用情况:
在这里插入图片描述
   可以看到,和结构体对比,联合体非常节省空间,那么联合体改一个成员另一个成员跟着变了,到底该用在什么时候呢?我们继续学习

6.联合体的应用

   联合体在使用时可以节省空间,所以我们要学习什么情况下使用联合体,而不是使用结构体
   由于它的特性,所以我们应该也能想到它的应用,那就是应用在整个联合体一次性只会出现一个成员的情况下,只要我们在同一时刻只使用一个成员,那么就算把其它成员改变了也没有影响
   ⽐如,我们要搞⼀个活动,要上线⼀个礼品兑换单,礼品兑换单中有三种商品:图书、杯⼦、衬衫。每⼀种商品都有:库存量、价格、商品类型和商品类型相关的其他信息,如下:

  • 图书:书名、作者、⻚数
  • 杯⼦:设计
  • 衬衫:设计、可选颜⾊、可选尺寸

   在不思考的情况下,我们可以直接写出以下结构:

struct gift_list
{//公共属性int stock_number;//库存量double price; //定价int item_type;//商品类型//特殊属性char title[20];//书名char author[20];//作者int num_pages;//⻚数char design[30];//设计int colors;//颜⾊int sizes;//尺⼨
};

   上述的结构其实设计的很简单,⽤起来也⽅便,但是结构的设计中包含了所有礼品的各种属性,这样使得结构体的⼤⼩就会偏⼤,比较浪费内存。因为对于礼品兑换单中的商品来说,只有部分属性信息是常⽤的,比如:
   商品是图书,就不需要design、colors、sizes,所以我们就可以把公共属性单独写出来,剩余属于各种商品本⾝的属性使⽤联合体起来,这样就可以介绍所需的内存空间,⼀定程度上节省了内存,如下:


struct gift_list
{int stock_number;//库存量double price; //定价int item_type;//商品类型union {struct{char title[20];//书名char author[20];//作者int num_pages;//⻚数}book;struct{char design[30];//设计}mug;struct{char design[30];//设计int colors;//颜⾊int sizes;//尺⼨}shirt;}item;
};

   这里我们将这个礼品兑换单整体用结构体存储,其中公共部分就直接当作成员定义进去,其它特殊属性就统一放在一个联合体里面,在联合体里面就把图书、杯子、衬衫分别弄成结构体
   在这个联合体里面的成员就是三个结构体,它们共用同一段空间,而在同一时刻我们只会使用其中一个进行描述,所以不会有影响,最后达到了节省空间的目的

二、枚举

1.枚举类型的声明

   枚举顾名思义就是⼀⼀列举,可以把所有可能的取值⼀⼀列举出来,⽐如我们现实⽣活中:

  • ⼀周的星期⼀到星期⽇是有限的7天,可以⼀⼀列举
  • 性别有:男、⼥,也可以⼀⼀列举
  • 三原⾊,也是可以一一列举

   所以枚举也就是一一列举的意思,而枚举类型的声明和结构体以及联合体的声明相似,但是关键字是enum,接下来我们就来把我们举出的枚举例子一一实现出来,如下:

enum Day//星期
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};enum gender//性别
{MALE,FEMALE,
}enum Color//三原色
{RED,GREEN,BLUE
};

   以上定义的 enum Day , enum Sex , enum Color 都是枚举类型,{}中的内容是枚举类型的可能取值,也叫 枚举常量
   这些枚举常量都是有值的,默认从0开始,依次递增1,我们可以打印出来看看:

#include <stdio.h>enum color
{RED,GREEN,BLUE
};int main()
{printf("%d %d %d", RED, GREEN, BLUE);return 0;
}

运行结果:
在这里插入图片描述
那么我们能不能在开始的时候就给它赋值呢?当然可以,如下:

enum Color
{RED = 2,GREEN = 4,BLUE = 8
};

接着我们再来打印一下它们的值
在这里插入图片描述
   这就是自定义结构:枚举,里面的成员又叫枚举常量,是无法更改的,一般用来将这些值赋值给其它变量

2.枚举类型的优点

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

  1. 增加代码的可读性和可维护性,比如我们想用数字0表示男,数字1表示女,那么写出来就不好理解,如果用枚举中MALE表示男,同时底层代表0,就具有更高的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨
  3. 便于调试,预处理阶段会删除 #define 定义的符号,这个在后面的预处理详解我们会讲到
  4. 使⽤⽅便,⼀次可以定义多个常量
  5. 枚举常量是遵循作⽤域规则的,枚举声明在函数内,只能在函数内使用,而#define定义的常量是全局变量

所以枚举也是有它自己的优点的

3.枚举类型的使用

   在使用枚举时,我们会创建一个枚举变量,然后用枚举类型中的枚举常量给它赋值,如下:

#include <stdio.h>enum Color
{RED = 1,GREEN = 2,BLUE = 4
};int main()
{enum Color clr = GREEN;return 0;
}

   我们今天要学习的联合体(共同体)以及枚举就到此结束了,是否是有很大收获呢?如果文章内容有误请及时联系我,非常感谢
   当然如果有什么关于这篇博客的疑问,可以在评论区或者私信提问,一定会及时回答
   那么今天就到这里,bye~


http://www.ppmy.cn/server/132248.html

相关文章

低代码工单管理app评测,功能与效率解析

预计到2030年&#xff0c;低代码平台市场将达1870亿美元。ZohoCreator助力企业构建定制化软件应用&#xff0c;以建筑行业工作订单管理app为例&#xff0c;简化流程&#xff0c;提升管理效率&#xff0c;降低成本。其用户友好界面、自动化管理、跨平台使用及全面报告功能受企业…

kubeadm 搭建k8s 1.28.2版本集群

kubeadm 搭建k8s 1.28.2版本集群 1、kubuadm介绍&#xff1a; kubeadm 是官方社区推出的一个用于快速部署kubernetes 集群的工具&#xff0c;这个工具能通过两条指令完成一个kubernetes 集群的部署&#xff1a; 创建一个Master 节点kubeadm init将Node 节点加入到当前集群中…

FLINK SQL 任务参数

在Flink SQL任务中&#xff0c;参数配置对于任务的性能和稳定性至关重要。以下是对运行时参数、优化器参数和表参数的详细解析&#xff1a; 一、运行时参数 运行时参数主要影响Flink作业在执行过程中的行为。以下是一些关键的运行时参数&#xff1a; 并行度&#xff08;Para…

[mysql]多表查询详解

我们如果要查询,我们就要用 SELECT .... FROM .... WHERE AND/OR/NOT #我们需要用过滤的条件来对数据进行筛选,不然会有很多多余数据 ORDER BY (ASC/DESC)#排序 LIMIT....,#是在几个有限的数据库管理系统里所以,PGsql,mysql,等 多表查询的意义 我们目前为止的查询语句…

【漏洞复现】万户OA-ezOFFICE upload.jsp 任意文件上传漏洞

免责声明: 本文旨在提供有关特定漏洞的信息,以帮助用户了解潜在风险。发布此信息旨在促进网络安全意识和技术进步,并非出于恶意。读者应理解,利用本文提到的漏洞或进行相关测试可能违反法律或服务协议。未经授权访问系统、网络或应用程序可能导致法律责任或严重后果…

数据结构-排序1

1.排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c;若经过排序…

巨日禄AI故事转漫画视频创作教程

【一】新建作品 添加剧本&#xff0c;从30多个画风中选择一个匹配文案的画风。如果拿不准可以参考样图。巨日禄可以一次性制作6000字&#xff0c;20分钟以上的视频。&#xff08;第一次限制了1000字&#xff0c;仅为新用户更完整体验整个流程&#xff09; 【二】选择画风 那文…

基础篇:带你打开Vue的大门(一)

学习目标&#xff1a; 理解Vue的基本概念&#xff1a;掌握Vue.js是什么&#xff0c;它的设计理念&#xff0c;以及它在现代Web开发中的应用。掌握Vue的基本语法&#xff1a;学习Vue的基础指令和语法&#xff0c;能够使用Vue构建简单的交互式界面。熟悉Vue组件化开发&#xff1…