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 联合体内存分配的详细说明

  1. 成员共享内存
    • 联合体中的所有成员共享同一块内存区域。在任何时刻,联合体的内存中只会保存一个成员的值。
    • 联合体的大小通常由最大成员的大小决定,因为它必须能够容纳最大成员的数据。
  2. 内存对齐
    • 编译器为了提高数据访问的效率,会根据平台的对齐要求插入填充字节(padding)。内存对齐确保数据按适当的字节边界存放(例如,4字节对齐、8字节对齐),从而使得CPU可以更快速地访问这些数据。
    • 在一些平台上,数据类型可能有特定的对齐要求。
  3. 联合体的大小计算
    • 联合体的大小通常等于其最大成员的大小,但是,为了满足内存对齐的要求,联合体的实际大小可能会大于最大成员的大小。它会被填充到最接近对齐要求的倍数。
    • 内存对齐填充通常是由编译器自动管理的,但了解这一点对于理解联合体的内存分配非常重要。

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 联合体的特点

  1. 共享内存:联合体的所有成员共享同一块内存区域,因此一个联合变量的大小,至少是最大成员的大小。

    例子:

    #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;
    }
    

    在这里插入图片描述

  2. 只能存储一个成员的值:每次只能访问联合体中的一个成员。给一个成员赋值时,其他成员的值会被覆盖。

  3. 节省内存空间:联合体在节省内存方面非常有用,尤其是当你需要存储多种不同类型的数据,但在任何时刻只需要其中一个类型的数据时。

    例子:

    #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); // 输出100

data.f = 98.6;
printf("data.f = %.2f\n", data.f); // 输出98.6

1.5 联合体的使用场景

1.5.1 场景①

在网络通信中,我们经常需要处理不同类型的数据包。这些数据包的内容可能会根据协议的不同而有所不同。例如,有些协议可能传输整数数据,有些可能传输浮动数数据,还有些可能传输字符串数据。在这种情况下,我们可以使用联合体来处理这些不同的数据格式。

场景描述:

假设你正在开发一个通信协议处理程序,该程序需要解析网络传输过来的数据包。每个数据包的类型和内容可能会有所不同,但在任何时刻,每个数据包只会包含一种类型的数据。为了节省内存,你可以使用联合体来存储不同类型的数据。

  1. 协议A 可能会传输一个 整数(比如用户ID)。
  2. 协议B 可能会传输一个 浮动数(比如温度传感器的数据)。
  3. 协议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,    // 默认值为 0
    Monday,    // 默认值为 1
    Tuesday,   // 默认值为 2
    Wednesday, // 默认值为 3
    Thursday,  // 默认值为 4
    Friday,    // 默认值为 5
    Saturday   // 默认值为 6
};

int main() {
    enum Day today;
    today = Wednesday;  // today 被赋值为 3

    printf("Today is day number: %d\n", today);  // 输出:Today is day number: 3
    return 0;
}

2.2 枚举常量的默认值

如果你没有为枚举常量指定值,默认情况下,第一个常量的值为 0,后续常量的值依次递增 1。例如,在上面的代码中,Sunday 默认值为 0,Monday 为 1,依此类推。

2.3 枚举常量的自定义值

你可以手动为枚举常量指定值。这意味着你可以设置任意值,而不依赖于默认的递增规则。

#include <stdio.h>

enum Day {
    Sunday = 1,    // 设定为 1
    Monday = 2,    // 设定为 2
    Tuesday = 5,   // 设定为 5
    Wednesday = 7, // 设定为 7
    Thursday,      // 默认递增,从 8 开始
    Friday,        // 9
    Saturday       // 10
};

int main() {
    enum Day today;
    today = Thursday;  // today 被赋值为 8

    printf("Today is day number: %d\n", today);  // 输出:Today is day number: 8
    return 0;
}

在这个例子中,SundayMonday 的值分别被设置为 1 和 2,而其他常量则从 Wednesday 开始自动递增。

2.4 枚举类型的实际应用

枚举常常用于表示一些固定的状态或选项,比如星期几、颜色、方向、状态码等。它能够使代码更加清晰,减少硬编码的数字。

2.4.1 例子:使用枚举表示交通信号灯的状态

#include <stdio.h>

enum TrafficLight {
    Red,        // 0
    Yellow,     // 1
    Green       // 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: 2
    
    return 0;
}

拿整数给枚举变量赋值在C语言中是可以的,但是在C++是不行的,C++的类型检查比较严格。

2.7 枚举的优势

  • 提高代码可读性:枚举常量有具名的标识符,使代码更具语义。
  • 减少硬编码数字:避免了在代码中使用没有含义的数字常量。
  • 防止非法值:枚举确保变量只能取定义好的值。
  • 简化调试:具名常量方便在调试时辨识。
  • 增强维护性:修改枚举值时,只需要更改枚举定义,无需修改多个代码位置。
  • switch 语句结合使用:枚举常量使得 switch 语句的条件判断更加清晰。
  • 支持位域:与位运算结合使用,可以用来表示多个标志位。

—完—

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/968859.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习框架探秘|PyTorch:AI 开发的灵动画笔

前一篇文章我们学习了深度学习框架——TensorFlow&#xff08;深度学习框架探秘&#xff5c;TensorFlow&#xff1a;AI 世界的万能钥匙&#xff09;。在人工智能领域&#xff0c;还有一个深度学习框架——PyTorch&#xff0c;以其独特的魅力吸引着众多开发者和研究者。它就像一…

springcloud集成gateway

本篇文章只介绍gateway模块的搭建步骤&#xff0c;并无gateway详细介绍 gateway详解请查看&#xff1a;SpringCloudGateway官方文档详解 前置处理 父模块中已指定版本 不知道如何选择版本看这篇&#xff1a; 手把手教你梳理springcloud与springboot与springcloudalibaba的版本…

计算机网络(1)基础篇

目录 1.TCP/IP 网络模型 2.键入网址--->网页显示 2.1 生成HTTP数据包 2.2 DNS服务器进行域名与IP转换 2.3 建立TCP连接 2.4 生成IP头部和MAC头部 2.5 网卡、交换机、路由器 3 Linux系统收发网络包 1.TCP/IP 网络模型 首先&#xff0c;为什么要有 TCP/IP 网络模型&a…

PyInstaller在Linux环境下的打包艺术

PyInstaller是一款强大的工具&#xff0c;能够将Python应用程序及其所有依赖项打包成独立的可执行文件&#xff0c;支持Windows、macOS和Linux等多个平台。在Linux环境下&#xff0c;PyInstaller打包的可执行文件具有独特的特点和优势。本文将详细介绍PyInstaller在Linux环境下…

寒假2.12

题解 web&#xff1a;XYCTF2024-牢牢记住&#xff0c;逝者为大 打开环境&#xff0c;是源代码 看到了熟悉的preg_match函数 代码解析&#xff1a; 输入的cmd长度不能超过13&#xff0c;可以使用GET[‘cmd’]躲避长度限制 使用正则表达式过滤的一系列关键字 遍历get数组&…

如何构建有效的人工智能代理

目录 什么是 AI 代理? 何时应使用 AI 代理? 人工智能代理的构建模块 构建 AI 代理的常用方法 1. 提示链接(分步说明) 2.路由(将任务发送到正确的地方) 3.并行处理(同时做多件事) 4. 协调者和工作者 AI(团队合作) 5. 评估器和优化器(修复错误) 如何让人工…

华为云+硅基流动使用Chatbox接入DeepSeek-R1满血版671B

华为云硅基流动使用Chatbox接入DeepSeek-R1满血版671B 硅基流动 1.1 注册登录 1.2 实名认证 1.3 创建API密钥 1.4 客户端工具 OllamaChatboxCherry StudioAnythingLLM 资源包下载&#xff1a; AI聊天本地客户端 接入Chatbox客户端 点击设置 选择SiliconFloW API 粘贴1.3创…

mysql读写分离与proxysql的结合

上一篇文章介绍了mysql如何设置成主从复制模式&#xff0c;而主从复制的目的&#xff0c;是为了读写分离。 读写分离&#xff0c;拿spring boot项目来说&#xff0c;可以有2种方式&#xff1a; 1&#xff09;设置2个数据源&#xff0c;读和写分开使用 2&#xff09;使用中间件…

吊舱响应波段详解!

一、响应波段技术 可见光波段&#xff1a;通过高分辨率相机捕捉地面或空中目标的清晰图像&#xff0c;适用于白天或光照条件良好的环境下进行观测。 红外波段&#xff1a;利用红外辐射探测目标的温度分布&#xff0c;实现夜间或恶劣天气条件下的隐蔽目标发现。红外波段通常分…

AI驱动的直播带货电商APP开发:个性化推荐、智能剪辑与互动玩法

时下&#xff0c;个性化推荐、智能剪辑、互动玩法等AI技术的应用&#xff0c;使得直播电商平台能够精准触达用户、提升观看体验、提高转化率。对于希望在直播电商领域占据一席之地的企业来说&#xff0c;开发一款AI驱动的直播带货APP&#xff0c;已经成为提升竞争力的关键。 一…

ComfyUI流程图生图原理详解

一、引言 ComfyUI 是一款功能强大的工具&#xff0c;在图像生成等领域有着广泛应用。本文补充一点ComfyUI 的安装与配置过程遇到的问题&#xff0c;并深入剖析图生图过程及相关参数&#xff0c;帮助读者快速入门并深入理解其原理。 二、ComfyUI 的安装与配置中遇到的问题 &a…

本地部署DeepSeek集成VSCode创建自己的AI助手

文章目录 安装Ollama和CodeGPT安装Ollama安装CodeGPT 下载并配置DeepSeek模型下载聊天模型&#xff08;deepseek-r1:1.5b&#xff09;下载自动补全模型&#xff08;deepseek-coder:1.3b&#xff09; 使用DeepSeek进行编程辅助配置CodeGPT使用DeepSeek模型开始使用AI助手 ✍️相…

硬件学习笔记--40 电磁兼容试验-4 快速瞬变脉冲群试验介绍

目录 电磁兼容试验-快速瞬变脉冲群试验介绍 1.试验目的 2.试验方法 3.判定依据及意义 电磁兼容试验-快速瞬变脉冲群试验介绍 驻留时间是在规定频率下影响量施加的持续时间。被试设备&#xff08;EUT&#xff09;在经受扫频频带的电磁影响量或电磁干扰的情况下&#xff0c;在…

c++ 多线程知识汇总

一、std::thread std::thread 是 C11 引入的标准库中的线程类&#xff0c;用于创建和管理线程 1. 带参数的构造函数 template <class F, class... Args> std::thread::thread(F&& f, Args&&... args);F&& f&#xff1a;线程要执行的函数&…

XSS 常用标签及绕过姿势总结

XSS 常用标签及绕过姿势总结 一、xss 常见标签语句 0x01. 标签 <a href"javascript:alert(1)">test</a> <a href"x" onfocus"alert(xss);" autofocus"">xss</a> <a href"x" onclickeval(&quo…

基于SSM的农产品供销小程序+LW示例参考

1.项目介绍 系统角色&#xff1a;管理员、农户功能模块&#xff1a;用户管理、农户管理、产品分类管理、农产品管理、咨询管理、订单管理、收藏管理、购物车、充值、下单等技术选型&#xff1a;SSM&#xff0c;Vue&#xff08;后端管理web&#xff09;&#xff0c;uniapp等测试…

未授权访问成因与防御

1、未授权访问根因 2、检查步骤 3、修复建议 1、更新组件至安全版本 2、加强访问策略限制&#xff0c;限制用户访问 3、定期进行漏扫和渗透测试发现威胁及时修复 4、漏洞概览 Elasticsearch未授权访问漏洞 Hadoop未授权访问漏洞 Jenkins未授权访问 MongoDB未授权访问 Zoo…

策略模式-小结

总结一下看到的策略模式&#xff1a; A:一个含有一个方法的接口 B:具体的实行方式行为1,2,3&#xff0c;实现上面的接口。 C:一个环境类&#xff08;或者上下文类&#xff09;&#xff0c;形式可以是&#xff1a;工厂模式&#xff0c;构造器注入模式&#xff0c;枚举模式。 …

16.React学习笔记.React更新机制

一. 发生更新的时机以及顺序## image.png props/state改变render函数重新执行产生新的VDOM树新旧DOM树进行diff计算出差异进行更新更新到真实的DOM 二. React更新流程## React将最好的O(n^3)的tree比较算法优化为O(n)。 同层节点之间相互比较&#xff0c;不跨节点。不同类型的节…

SpringBoot通过文件监听实现MQ加密数据异步转发

一、前言 假设在两个局域网中&#xff0c;生产者和消费者进行通信 使用同步方式&#xff0c;mq偶尔会因为网络策略等问题导致消息发送失败&#xff0c;那么这条数据就丢失了 这时可以使用异步方式&#xff0c;将数据在生产端存一份&#xff0c;网通时发&#xff0c;网断时存 …