C语言第18节:自定义类型——联合和枚举
1. 联合体
C语言中的联合体(Union)是一种数据结构,它允许在同一内存位置存储不同类型的数据。不同于结构体(struct),结构体的成员各自占有独立的内存空间,而联合体的所有成员共享同一块内存区域。这意味着在同一时间,联合体中只能存储一个成员的值,其他成员会被覆盖。
1.1 联合体的基本语法
联合体的声明与结构体相似,使用关键字union来定义。
编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用同一块内存空间。所以联合体也叫:共用体。
一个简单的联合体例子如下:
#include <stdio.h>union Data {int i;float f;char str[20];
};int main() {union Data data;data.i = 10;printf("data.i = %d\n", data.i);data.f = 220.5;printf("data.f = %.2f\n", data.f);// 注意,data.i的值会被覆盖printf("data.i = %d\n", data.i); // 这个值会发生变化return 0;
}

1.2 联合体的内存分配与大小计算
在C语言中,联合体的所有成员都共享同一块内存区域。当你定义一个联合体时,它的内存空间并不会为每个成员分配独立的内存,而是为所有成员分配一块共享的内存区域。这样,联合体的内存大小至少等于成员中最大类型的成员大小。
举个例子:
#include <stdio.h>union Data {int i; // 4 字节float f; // 4 字节char str[20]; // 20 字节
};int main() {printf("Size of union Data: %lu\n", sizeof(union Data));return 0;
}
解释:
int i通常占用 4 字节。float f通常也占用 4 字节。char str[20]占用 20 字节(每个字符占 1 字节)。
由于联合体的成员共享内存,它的大小等于其中最大成员的大小。在这个例子中,char str[20] 的大小是 20 字节,因此联合体的大小会是 20 字节。换句话说,联合体的内存分配通常是由它的最大成员决定的,且内存中只能保存一个成员的数据。
输出:
Size of union Data: 20
1.2.1 联合体的内存对齐
除了最大成员的大小外,还要注意内存对齐(memory alignment)。C语言中,对于每个数据类型,通常都有对齐要求。具体对齐方式与系统架构、编译器有关(在C语言第17节:自定义类型——结构体已经讲过了,点击链接即可查看)。内存对齐的目的是为了提高访问效率,因此编译器往往会将数据类型按一定的字节边界对齐(例如,4字节对齐、8字节对齐等)。
内存对齐的示例:
假设我们使用的是32位或64位的架构,它可能要求对齐到4字节边界。我们来观察一下一个包含不同类型成员的联合体的内存分配。
// VS2022 MSVC
#include <stdio.h>union Example {char c; // 1 字节int i; // 4 字节double d; // 8 字节
};int main() {printf("Size of union Example: %lu\n", sizeof(union Example));return 0;
}
解释:
char c占用 1 字节。int i占用 4 字节。double d占用 8 字节。
但由于内存对齐的原因,联合体的实际大小可能会比这些单独成员的大小之和要大。通常,为了提高访问速度,编译器会插入一些填充字节(padding),使得联合体的内存大小是最大对齐数的倍数。
输出:
Size of union Example: 8
为什么是8字节呢?因为联合体中最大成员是double d,它的大小是8字节,而且对齐数也是8。因此,整个联合体的大小会是8字节。
1.2.2 联合体内存分配的详细说明
- 成员共享内存:
- 联合体中的所有成员共享同一块内存区域。在任何时刻,联合体的内存中只会保存一个成员的值。
- 联合体的大小通常由最大成员的大小决定,因为它必须能够容纳最大成员的数据。
- 内存对齐:
- 编译器为了提高数据访问的效率,会根据平台的对齐要求插入填充字节(padding)。内存对齐确保数据按适当的字节边界存放(例如,4字节对齐、8字节对齐),从而使得CPU可以更快速地访问这些数据。
- 在一些平台上,数据类型可能有特定的对齐要求。
- 联合体的大小计算:
- 联合体的大小通常等于其最大成员的大小,但是,为了满足内存对齐的要求,联合体的实际大小可能会大于最大成员的大小。它会被填充到最接近对齐要求的倍数。
- 内存对齐填充通常是由编译器自动管理的,但了解这一点对于理解联合体的内存分配非常重要。
1.2.3 进一步的例子:多成员联合体与内存对齐
假设我们有一个更复杂的联合体,其中包含不同类型的数据,并且考虑到内存对齐的影响:
#include <stdio.h>union Complex {char c[21]; // 21 字节int i; // 4 字节double d; // 8 字节short s; // 2 字节
};int main() {printf("Size of union Complex: %lu\n", sizeof(union Complex));return 0;
}
分析:
char c[21]:这是一个字符数组,它占用21字节(每个字符占1字节)。没有对齐要求。int i:整数类型,通常占用4字节。要求 4 字节对齐,即它会被存储在4字节对齐的位置。double d:双精度浮点类型,通常占用8字节。要求 8 字节对齐,因此它会被存储在8字节对齐的位置。short s:短整型,通常占用2字节。要求 2 字节对齐,它会被存储在2字节对齐的位置。
联合体的实际大小
- 联合体的大小由最大对齐数决定。在这个例子中,最大成员是
double d(虽然char c[21]占用了 21 字节,但是其对齐数取决于存储的元素,即其对齐数为char的对齐数1),它的大小是 8 字节,其对齐数为 8 。因此,联合体的大小将是 8 字节的倍数。 - 即使
char c[21]占用了 21 字节,但联合体的总大小会由于对齐要求被填充到适合最大成员对齐的大小。
因此,联合体的实际大小会是 24 字节。 这是因为:
double d会占用 8 字节,并且需要 8 字节对齐,因此联合体的总大小将被填充到最接近8字节对齐的倍数,即 24 字节。
1.2.4 总结
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
1.3 联合体的特点
-
共享内存:联合体的所有成员共享同一块内存区域,因此一个联合变量的大小,至少是最大成员的大小。
例子:
#include <stdio.h>union Data {int i;float f;char str[20]; };int main() {union Data data;printf("&data: %p\n", &data);printf("&data.i: %p\n", &data.i);printf("&data.f: %p\n", &data.f);printf("data.str: %p\n", data.str);return 0; }
-
只能存储一个成员的值:每次只能访问联合体中的一个成员。给一个成员赋值时,其他成员的值会被覆盖。
-
节省内存空间:联合体在节省内存方面非常有用,尤其是当你需要存储多种不同类型的数据,但在任何时刻只需要其中一个类型的数据时。
例子:
#include <stdio.h> #include <string.h> union Data {int i;float f;char str[20]; };int main() {union Data data;strcpy(data.str, "abcdefgh");data.i = 0x11223344;return 0; }
| |
1.4 访问联合体成员
联合体成员可以通过.(点)操作符访问。例如:
union Data data;
data.i = 100;
printf("data.i = %d\n", data.i); // 输出100data.f = 98.6;
printf("data.f = %.2f\n", data.f); // 输出98.6
1.5 联合体的使用场景
1.5.1 场景①
在网络通信中,我们经常需要处理不同类型的数据包。这些数据包的内容可能会根据协议的不同而有所不同。例如,有些协议可能传输整数数据,有些可能传输浮动数数据,还有些可能传输字符串数据。在这种情况下,我们可以使用联合体来处理这些不同的数据格式。
场景描述:
假设你正在开发一个通信协议处理程序,该程序需要解析网络传输过来的数据包。每个数据包的类型和内容可能会有所不同,但在任何时刻,每个数据包只会包含一种类型的数据。为了节省内存,你可以使用联合体来存储不同类型的数据。
- 协议A 可能会传输一个 整数(比如用户ID)。
- 协议B 可能会传输一个 浮动数(比如温度传感器的数据)。
- 协议C 可能会传输一个 字符串(比如设备状态信息)。
通过联合体,你可以为这些数据包定义一个结构,使得它们共享同一块内存。每次你收到一个数据包时,根据协议类型,你可以决定是存储整数、浮动数,还是字符串,但内存中始终只有一种数据。
优势:
- 节省内存:由于不同数据类型共享内存,只有在需要时,才会使用最大类型的内存(例如字符串可能占用更多的字节)。
- 灵活性:能够处理不同协议的数据格式,只需要用一个联合体存储数据。
这个场景在 网络通信、嵌入式系统、文件格式解析 等领域非常常见,特别是在需要处理多种类型数据的系统中。
1.5.2 场景②
| 姓名 | 性别 | 年龄 | 婚姻状况 | 婚姻状况标记 | |||||
| 未婚 | 已婚 | 离婚 | |||||||
| 结婚日期 | 配偶姓名 | 子女数量 | 离婚日期 | 子女数量 | |||||
struct Person // 定义职工个人信息结构体类型
{char name[20]; // 姓名char sex; // 性别int age; // 年龄union MaritalState marital; // 婚姻状况int marryFlag; // 婚姻状况标记
};union MaritalState // 定义婚姻情况共用体
{int single; // 未婚struct MarriedState married; // 已婚struct DivorceState divorce; // 离婚
};struct MarriedState // 定义已婚结构体类型
{struct Date marryDay; // 结婚日期char spouseName[20]; // 配偶姓名int child; // 子女数量
};struct DivorceState // 定义离婚结构体类型
{struct Date divorceDay; // 离婚日期int child; // 子女数量
};struct Date
{int year;short month;short day;
};
1.6 联合体判断大小端
int check_sys()
{union{int i;char c;}un;un.i = 1;return un.c;//返回1是小端,返回0是大端
}
在C语言第16节:数据在内存中的存储已经讲过,这里不再赘述。
2. 枚举类型
C语言中的枚举类型(enum)是一种用户自定义的数据类型,用于表示一组具名的常量。枚举类型将一组相关的常量组合在一起,并赋予它们有意义的名字。使用枚举可以使程序的代码更加清晰、易于理解和维护。
一周的星期一到星期日是有限的7天,可以一 一列举
性别有:男、女、保密,也可以一 一列举
月份有12个月,也可以一 一列举
三原色,也是可以一 一列举
2.1 枚举类型的基本语法
定义枚举类型的语法如下:
enum 枚举名 {常量1 = 值1,常量2 = 值2,常量3 = 值3,...
};
枚举名是枚举类型的名称。常量是枚举中的各个值,通常是一些具名常量,默认情况下,枚举常量从0开始,依次递增,除非你为它们指定了不同的值。
2.1.1 例子:基本枚举类型
#include <stdio.h>enum Day {Sunday, // 默认值为 0Monday, // 默认值为 1Tuesday, // 默认值为 2Wednesday, // 默认值为 3Thursday, // 默认值为 4Friday, // 默认值为 5Saturday // 默认值为 6
};int main() {enum Day today;today = Wednesday; // today 被赋值为 3printf("Today is day number: %d\n", today); // 输出:Today is day number: 3return 0;
}
2.2 枚举常量的默认值
如果你没有为枚举常量指定值,默认情况下,第一个常量的值为 0,后续常量的值依次递增 1。例如,在上面的代码中,Sunday 默认值为 0,Monday 为 1,依此类推。
2.3 枚举常量的自定义值
你可以手动为枚举常量指定值。这意味着你可以设置任意值,而不依赖于默认的递增规则。
#include <stdio.h>enum Day {Sunday = 1, // 设定为 1Monday = 2, // 设定为 2Tuesday = 5, // 设定为 5Wednesday = 7, // 设定为 7Thursday, // 默认递增,从 8 开始Friday, // 9Saturday // 10
};int main() {enum Day today;today = Thursday; // today 被赋值为 8printf("Today is day number: %d\n", today); // 输出:Today is day number: 8return 0;
}
在这个例子中,Sunday 和 Monday 的值分别被设置为 1 和 2,而其他常量则从 Wednesday 开始自动递增。
2.4 枚举类型的实际应用
枚举常常用于表示一些固定的状态或选项,比如星期几、颜色、方向、状态码等。它能够使代码更加清晰,减少硬编码的数字。
2.4.1 例子:使用枚举表示交通信号灯的状态
#include <stdio.h>enum TrafficLight {Red, // 0Yellow, // 1Green // 2
};int main() {enum TrafficLight signal;signal = Green;if (signal == Green) {printf("Go!\n");} else if (signal == Yellow) {printf("Caution!\n");} else {printf("Stop!\n");}return 0;
}
在这个例子中,TrafficLight 枚举表示了交通信号灯的三个状态:红灯、黄灯和绿灯。每个状态有一个默认的整数值:Red 为 0,Yellow 为 1,Green 为 2。通过这种方式,代码更易理解,避免了使用数字来表示信号灯的状态。
2.5 枚举类型的大小
在 C 语言中,枚举类型的大小与编译器的实现有关。通常,编译器会根据枚举常量的取值范围来决定枚举类型的存储大小。如果枚举的值在 int 类型的范围内,编译器通常会选择 int 类型来存储枚举值。但有些编译器可能根据需要进行优化,使用较小的存储类型。
可以使用 sizeof 来查看枚举类型的大小:
#include <stdio.h>enum Color {Red = 1,Green,Blue
};int main() {printf("Size of enum Color: %lu\n", sizeof(enum Color)); // 输出枚举类型的大小return 0;
}
2.6 枚举类型的转换
枚举常量本质上是整数,因此可以将它们转换为整数类型,或者将整数值赋给枚举变量。但要注意,这种做法可能会导致不符合预期的结果。
#include <stdio.h>enum Day {Sunday = 1,Monday = 2,Tuesday = 3
};int main() {enum Day today;today = 2; // 可以将整数赋给枚举变量printf("Today is day number: %d\n", today); // 输出:Today is day number: 2return 0;
}
拿整数给枚举变量赋值在C语言中是可以的,但是在C++是不行的,C++的类型检查比较严格。
2.7 枚举的优势
- 提高代码可读性:枚举常量有具名的标识符,使代码更具语义。
- 减少硬编码数字:避免了在代码中使用没有含义的数字常量。
- 防止非法值:枚举确保变量只能取定义好的值。
- 简化调试:具名常量方便在调试时辨识。
- 增强维护性:修改枚举值时,只需要更改枚举定义,无需修改多个代码位置。
- 与
switch语句结合使用:枚举常量使得switch语句的条件判断更加清晰。 - 支持位域:与位运算结合使用,可以用来表示多个标志位。
—完—
相关文章:
C语言第18节:自定义类型——联合和枚举
1. 联合体 C语言中的联合体(Union)是一种数据结构,它允许在同一内存位置存储不同类型的数据。不同于结构体(struct),结构体的成员各自占有独立的内存空间,而联合体的所有成员共享同一块内存区域…...
Python的元组和列表的区别是什么?
1. 定义和语法形式 列表(List):列表是一种可变的序列类型,使用方括号 [] 来定义。例如:my_list [1, 2, 3] 。列表中的元素可以是不同的数据类型,并且可以包含嵌套的列表、元组等其他数据结构。元组&#x…...
解锁网络安全:穿越数字世界的防护密码
个人主页:java之路-CSDN博客(期待您的关注) 目录 网络安全:数字时代的基石 网络安全面面观 (一)定义与范畴 (二)发展历程 网络安全面临的威胁 (一)恶意软件肆虐 (二…...
利用二分法+布尔盲注、时间盲注进行sql注入
一、布尔盲注: import requestsdef binary_search_character(url, query, index, low32, high127):while low < high:mid (low high 1) // 2payload f"1 AND ASCII(SUBSTRING(({query}),{index},1)) > {mid} -- "res {"id": payloa…...
GPT-SWARM和AgentVerse的拓扑结构和交互机制
GPT-SWARM和AgentVerse的拓扑结构和交互机制 拓扑结构区别 GPT-SWARM:采用图结构,将语言智能体系统描述为可优化的计算图。图中的每个节点代表一个操作,如语言模型推理或工具使用等特定功能,边则描述了操作之间的信息流,代表智能体之间的通信渠道。多个智能体连接形成的复…...
python爬虫解决无限debugger问题
方法一 关闭定时任务 关闭断点执行代码打开断点 # 无限debugger产生原因 # 1. web开发者工具打开 # 2. js代码中有debugger # 3. js有定时处理[推荐] for(let i0;i<99999;i){window.clearInterval(i)}方法二 关闭breakpoint 方法三 修改JS代码 使用fiddler,抓…...
使用rknn进行facenet部署
文章目录 开源仓库pth转onnxnetron可视化onnx转rknnC++实现开源仓库 https://github.com/bubbliiiing/facenet-pytorch pth转onnx 修改facenet网络的forward函数代码 修改前 def forward(self, x, mode = "predict"):if mode ==...
C# 两种方案实现调用 DeepSeek API
目录 序 开发运行环境 访问API的一个通用方法 原生官网实现 申请 API key 调用实现 调用示例 腾讯云知识引擎原子调用 申请 API key 调用示例 小结 序 DeepSeek(深度求索) 最近可谓火爆的一塌糊涂,具体的介绍这里不再赘述&#x…...
Linux下的进程切换与调度
目录 1.进程的优先级 优先级是什么 Linux下优先级的具体做法 优先级的调整为什么要受限 2.Linux下的进程切换 3.Linux下进程的调度 1.进程的优先级 我们在使用计算机的时候,通常会启动多个程序,这些程序最后都会变成进程,但是我们的硬…...
图神经网络是什么,有什么实际应用
图神经网络是什么 图神经网络(Graph Neural Network,GNN)是一种专门用于处理图结构数据的神经网络,它能对图中的节点、边和整个图进行学习和推理,在社交网络分析、生物信息学、推荐系统等领域应用广泛。以下是其原理及示例说明: 图神经网络原理 节点表示学习:为图中每…...
Debezium日常分享系列之:解码逻辑解码消息内容
Debezium日常分享系列之:解码逻辑解码消息内容 示例配置选项 DecodeLogicalDecodingMessageContent SMT将PostgreSQL逻辑解码消息的二进制内容转换为结构化形式。当Debezium PostgreSQL连接器捕获逻辑解码消息时,它会将消息事件记录发送到Kafka。默认情况…...
anolis os 8.9安装jenkins
一、系统版本 # cat /etc/anolis-release Anolis OS release 8.9 二、安装 # dnf install -y epel-release # wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo # rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.…...
java8、9新特性
JAVA8 Lambda 表达式 (parameters) -> expression 或 (parameters) ->{ statements; } 提供了一种更为简洁的语法,尤其适用于函数式接口。相比于传统的匿名内部类,Lambda 表达式使得代码更为紧凑,减少了样板代码的编写。 它允许将函…...
利用kali linux 进行自动化渗透测试
本方案旨在自动化创建渗透测试全流程 一、架构 1.智能信息收集体系 class IntelligentOSINT:def __init__(self, target):self.target targetself.intelligence_sources [OSINT_Platforms,DeepWeb_Crawlers, SocialMedia_Trackers,ML_Correlation_Engine]def advanced_col…...
Java基础知识总结(四十八)--TCP传输、TCP客户端、TCP服务端
**TCP传输:**两个端点的建立连接后会有一个传输数据的通道,这通道称为流,而且是建立在网络基础上的流,称之为socket流。该流中既有读取,也有写入。 **tcp的两个端点:**一个是客户端,一个是服务…...
【python】http.server内置库构建临时文件服务
需要从linux开发机上下载一个文件到本地,约700M比较大,通过sz命令下载较慢且传输过程不稳定连续失败,后采用下面方式解决。 cd到一个目录下执行python -m http.server port,port为服务的端口号: 启动后浏览器中访问…...
如何从0开始将vscode源码编译、运行、打包桌面APP
** 网上关于此的内容很少,今天第二次的完整运行了,按照下文的顺序走不会出什么问题。最重要的就是环境的安装,否则极其容易报错,请参考我的依赖版本以及文末附上的vscode官方指南 ** 第一步:克隆 VSCode 源码 首先…...
亚冬会绽放“云端”,联通云如何点亮冰城“科技之光”?
科技云报到原创。 35年前,中国第一次承办亚运会,宣传曲《亚洲雄风》红遍大江南北,其中有一句“我们亚洲,云也手握手”。如今回看,这句话仿佛有了更深的寓意:一朵朵科技铸就的“云”,把人和人连…...
网络安全ids是什么意思
1、 简述IPS和IDS的异同点; 入侵检测系统(IDS) IDS(Intrusion Detection Systems,入侵检测系统),专业上讲就是依照一定的安全策略,对网络、系统、运行状况进行监视,尽可能…...
ASP.NET Core程序的部署
发布 不能直接把bin/Debug部署到生产环境的服务器上,性能低。应该创建网站的发布版,用【发布】功能。两种部署模式:“框架依赖”和“独立”。独立模式选择目标操作系统和CPU类型。Windows、Linux、iOS;关于龙芯。 网站的运行 在…...
优选驾考小程序
第2章 系统分析 2.1系统使用相关技术分析 2.1.1Java语言介绍 Java语言是一种分布式的简单的 开发语言,有很好的特征,在安全方面、性能方面等。非常适合在Internet环境中使用,也是目前企业级运用中最常用的一个编程语言,具有很大…...
42.水果销售系统(springbootvue的Java项目[含微信小程序])
目录 1.系统的受众说明 2.开发环境与技术 2.1 MYSQL数据库 2.2 Java语言 2.3 微信小程序技术 2.4 SpringBoot框架 2.5 B/S架构 2.6 Tomcat 介绍 2.7 HTML简介 2.8 MyEclipse开发工具 3.系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2 经济可行性 3.1.3 操作…...
ffmpeg所有版本下载地址
地址如下:Index of /releaseshttps://ffmpeg.org/releases/...
记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-效果展示(4)
如果有人给你一串JSON数据,你需要编辑他,对于有开发基础的人来说,可能会好处理下,而对于没有开发基础的人来说,那就是灾难了! 那么有没有一个东西,可以让这个编辑更顺畅呢? 贴代码案例中的DynamicForm你值得拥有!本次展示作者在本机上操作IIS的示例,如下 IIS展示 先…...
Docker 网络的配置与管理
目录 查看所有网络 查看网络详细信息 创建新的网络 删除网络 清理未使用的网络 将容器连接到网络 将容器从网络中断开 将容器端口映射到宿主机 绑定到特定 IP 地址 为容器设置自定义 DNS 查看所有网络 docker network ls 功能:列出所有 Docker 网络。 工…...
3D文档控件Aspose.3D实用教程: 在 Java 中创建 FBX 文件并无缝将圆柱体转换为网格
概述 创建FBX文件并将圆柱体转换为网格是 3D 建模和动画中的基本任务。这些过程在游戏、电影和建筑等行业中至关重要。通过使用Aspose.3D for Java ,开发人员可以高效地管理 3D 场景和对象。这个强大的 Java 3D API 简化了 3D 模型的创建和操作。它的易用性和灵活性…...
软考高级《系统架构设计师》知识点(一)
计算机硬件 校验码 码距:就单个编码A:00而言,其码距为1,因为其只需要改变一位就变成另一个编码。在两个编码中,从A码到B码转换所需要改变的位数称为码距,如A:00要转换为B:11,码距为2。一般来说,…...
HTML 学习记录
HTML 学习记录 html是超文本标记语言,是一种标记语言 超文本:链接 标记:也叫标签,带尖括号的文本 标签语法 1.标签成对出现,中间包裹内容 2.<>里面放英文字母 3.结束标签比开始标签多一个 / 例如 <s…...
Mac之JDK安装
Mac之JDK安装 一.安装 jdk 打开终端输入命令:java -version 查看是否已安装 JDK Oracle 官方下载地址 根据自己Mac 系统安装 查看 Mac 系统,打开中断命令,输入: uname -a Compressed Archive 是压缩文档,下载的是一个 .tar.gz 压缩包 D…...
centos 10 离线安装dnf 和 设置dnf镜像源
离线安装dnf可用kimi搜索, centos 使用curl 下载dnf 的rpm包 mkdir ~/dnf_packages cd ~/dnf_packages# CentOS 7 示例 curl -O http://springdale.math.ias.edu/data/puias/unsupported/7/x86_64/dnf-0.6.4-2.sdl7.noarch.rpm curl -O http://springdale.math.ias.edu/data/pu…...
