【C语言】一篇文章深入解析联合体和枚举且和结构体的区别

在这里插入图片描述

文章目录

  • 📝前言
  • 🌠 联合体类型的声明
    • 🌉联合体的特点
  • 🌠相同成员的结构体和联合体对⽐
    • 🌉联合体⼤⼩的计算
  • 🌠联合体应用
    • 🌉枚举类型的声明
  • 🌠枚举类型的优点
    • 🌉 枚举类型的使⽤
  • 🚩总结


📝前言

联合体(union)是允许一个变量通过不同的接口访问内存的一种数据类型,表示一个变量可以存储不同类型的值,而枚举是使用enum关键字定义一组相关且互斥的整形常量集合。本章阿森将和你学习联合体类型的声明,特点,有关大小的计算,还有枚举类型的声明,优点和使用。文章干货满满!学习起来吧😃!

🌠 联合体类型的声明

同结构体一样,声明结构体类型需要使用struct关键字,联合体则用union关键字。

  1. 包含对象名的声明方式:
union 联合体名
{
  类型 成员1;
  类型 成员2;
  ... 
  类型 成员n;
}对象名;
  • 代码理解:
#include <stdio.h>

union S
{
	char c;
	int a;
}s1;

int main()
{
	s1.c = 'a';
	printf("%c\n", s1.c);

	s1.a = 10;
	printf("%d\n", s1.a);

	return 0;
}

代码运行:
在这里插入图片描述

  1. 不包含对象名的声明格式:
union 类型名 
{ 
  类型 成员1;
  类型 成员2;
  ... 
  类型 成员n;
};
  • 代码实现:
#include <stdio.h>

union S
{
	char c;
	int a;
};

int main()
{
	union S s2;

	s2.c = 'b';
	printf("%c\n", s2.c);

	s2.a = 20;
	printf("%d\n", s2.a);

	return 0;
}

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

🌉联合体的特点

  1. 编译器只为最⼤的成员分配⾜够的内存空间。联合体的特点是所有成员共⽤同⼀块内存空间。所以联合体也叫:共⽤体。

例如:

union u
{
	char c;
	int u;
};

int main()
{
	union u uu;
	printf("联合体uu的大小为%zd\n", sizeof(uu));
	printf("   (uu)地址为%p\n", &uu);
	printf("&(uu.c)地址为%p\n", &(uu.c));
	printf("&(uu.u)地址为%p\n", &(uu.u));

	return 0;
}

输出:
在这里插入图片描述
图解:
在这里插入图片描述

  1. 联合的成员是共⽤同⼀块内存空间的,这样⼀个联合变量的⼤⼩,⾄少是最⼤成员的⼤⼩(因为联合⾄少得有能⼒保存最⼤的那个成员)
//联合类型的声明
union u
{
	char c;
	int i;
};
int main()
{
	//联合变量的定义
	union u uu = { 0 };
	un.i = 0x11223344;
	un.c = 0x55;
	printf("%x\n", uu.i);
	return 0;
}

输出:
在这里插入图片描述
图解:
在这里插入图片描述
union定义了intchar两个成员,共享同一块内存空间,int类型占4个字节,低地址在前,高地址在后,char类型只占1个字节,存储在int的低地址字节。当执行:uu.i = 0x11223344时,此时int的4个字节分别存储如图,然后执行: uu.c = 0x55,由于VS是小端存储,低字节放在低地址处char只占1个字节,它会覆盖int低地址的那个字节。所以int原来低地址字节0x44被覆盖为0x55

🌠相同成员的结构体和联合体对⽐

结构体和联合体的主要区别在于:

  • 结构体中每个成员占用自己独立的内存空间,可以同时访问每个成员。
  • 联合体中所有成员共享同一块内存空间,只能同时访问其中一个成员。
  1. 内存布局:
    结构体中每个成员都有固定的偏移地址,占用独立的内存空间。
    联合体中所有成员共享同一块内存,没有偏移地址,只能使用一个成员。

  2. 访问成员:
    结构体可以同时读取各个成员的值。
    联合体只能访问当前使用的成员,其他成员的值将被覆盖。

  3. 大小:
    结构体的大小是所有成员大小的和。
    联合体的大小至少是最大成员的大小。

  • 结构体:
struct S
{
 char c;
 int i;
};
struct S s = {0};
  • 联合体:
union u
{
	char c;
	int i;
};
union u uu = { 0 };

图解对比:
结构体S占用char + int+有可能开辟浪费的空间大小的内存,可以独立访问ci,联合体u只占用int大小的内存,访问ci时值会覆盖,结构体各成员独立,联合体成员共享同一内存空间。
在这里插入图片描述

🌉联合体⼤⼩的计算

点击可以查看结构体的内存对齐规则——>【C语言】自定义类型:结构体深入解析(二)结构体内存对齐&&宏offsetof计算偏移量&&结构体传参
联合体大小计算规则:

  1. 联合的⼤⼩⾄少是最⼤成员的⼤⼩。
  2. 当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍。
  • 来代码理解:
union Un1
{
    char c[5];
    int i;
};

union Un2
{
    short c[7];
    int i;
};

int main()
{
    printf("%zd\n", sizeof(union Un1));//8
    printf("%zd\n", sizeof(union Un2));//16

    return 0;
}

运行:
在这里插入图片描述
图解分析:
在这里插入图片描述
首先看union Un1如果联合体的大小是最大成员的最大成员的的大小,在联合体union Un1中,char[5]的大小理应是5,那计算的结果不是5。为什么是8呢?这是因为它完成了对齐的操作,如果是数组,是按元素类型大小来算他的对齐数的。char 元素的类型大小是1VS默认对齐数是8,对齐数是8i 的大小是4VS默认对齐数是8,对齐数是4,接下来(4>1)整个联合体的对齐数是4,根据当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍。此时最大成员大小是数组char [5]大小为5,5不是4的整数倍,84*2)是4的整数倍。是不是真的是这样呢?会不会是偶然呢?

接下来我们看第二组:union Un2首先short c[7]是数组,总大小为14,然后由于数组是按照元素的类型大小来算对齐数,类型为short类型大小为2VS默认对齐数为8,对齐数为22<8),i的大小是4VS默认对齐数是8,那么对齐数是44<8),然后整个联合体的对齐数是44>2),然后看成员最大对齐数的大小(short c[7]的大小是2*7=14)是不是整个联合体的对齐数(4)的整数倍,可见14不是4的整数倍, 根据第二条规则:当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍。因此还要多用2个字节,升到164*4)个字节才是整数倍。
联合体的对齐规则与结构体相似:
点击可以查看结构体的内存对齐规则——>【C语言】自定义类型:结构体深入解析(二)结构体内存对齐&&宏offsetof计算偏移量&&结构体传参

🌠联合体应用

使⽤联合体是可以节省空间的:
⽐如,我们要搞⼀个活动,要上线⼀个礼品兑换单,礼品兑换单中有三种商品:图书、杯⼦、衬衫。
每⼀种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。
图书:书名、作者、⻚数
杯⼦:设计
衬衫:设计、可选颜⾊、可选尺⼨

结构体表示:

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;//尺⼨
};

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

联合体应用:

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. 第一种方法:
int check_sys()
{
	int n = 1;//01 00 00 00     00 00 00 01
	return *(char*)&n;
}

int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

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

  1. 第二种联合体巧妙方法:
int check_sys()
{
	union
	{
		char c;
		int i;
	}u;
	u.i = 1;
	return u.c;//返回1是⼩端,返回0是⼤端
}

VS运行:

小端

图解:
在这里插入图片描述

大端存储:是指数据的低位字节内容保存在内存的高地址处,而数据的高位位字节内容,保存在内存的低地址处。
小端存储:是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的高地址处。

如果01是低位字节存储到低地址c时,是小端存储,如果01低位字节存储到高地址处,没有存储到c的位置,那么c的位置存储着00,返回为0,是大端存储。

🌉枚举类型的声明

枚举类型(enum)是一种特殊的类型,它可以为一组相关的常量值赋予用户定义的名称。
—>简单来说:枚举顾名思义就是⼀⼀列举。
枚举类型的声明语法:

enum 标识符 
{
   枚举常量1, 
   枚举常量2,
   ...
} 变量;
  1. enum 关键字声明这是一个枚举类型。

  2. 标识符是枚举类型的名称。

  3. 在大括号{}内列出枚举类型的多个枚举常量,用逗号分隔。

  4. 变量是枚举类型的变量,可以直接使用枚举类型名或枚举常量初始化。

例如:

enum Color //Color是枚举类型名
{
	RED,     // 枚举常量  
	GREEN,
	BLUE
} color;///color是Color类型的变量

int main()
{
	printf("%d\n", RED);
	printf("%d\n", GREEN);
	printf("%d\n", BLUE);
	return 0;
}

输出:
在这里插入图片描述

  • 枚举常量默认从0开始依次累加1。也可以手动为枚举常量赋值:

例如:

enum Color 
{
   RED = 1,
   GREEN = 2,
   BLUE = 4
}

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

  • 当然第一个元素未被赋值,给其它的常量赋值,该常量前面的值是默认值(0,1,2)后面递增1。

例如:

enum Color
{
	RED,
	white,
	GREEN = 8,
	BLUE ,
	BLACK,
};

int main()
{
	printf("%d\n", RED);
	printf("%d\n", white);
	printf("%d\n", GREEN);
	printf("%d\n", BLUE);
	printf("%d\n", BLACK);

	return 0;
}

输出:
在这里插入图片描述

🌠枚举类型的优点

为什么使⽤枚举?
我们可以使⽤ #define 定义常量,为什么⾮要使⽤枚举?

枚举的优点:

  1. 增加代码的可读性和可维护性
    如:之前的扫雷中可以这样定义用PLAY代替1EXIT代替0,更具有个性化:
#include <stdio.h> 
#include <string.h>

// 定义游戏选择枚举类型
enum Game_Selection
{
    EXIT, // 退出游戏
    PLAY  // 开始游戏
};

// 打印菜单
void menu()
{
    printf("********** Menu **********\n");
    printf("PLAY - Start the game\n");
    printf("EXIT - Exit the game\n");
    printf("********** Menu **********\n");
}

int main()
{
    enum Game_Selection input; // 声明游戏选择变量
    char choice[10]; // 声明选择输入缓冲区

    do
    {
        menu(); // 调用菜单函数

        printf("Please enter your choice: ");
        scanf("%s", choice); // 读取选择输入
        getchar(); // 清除输入缓冲区

        if (strcmp(choice, "PLAY") == 0)
        {
            input = PLAY; // 设置选择为开始游戏
        }
        else if (strcmp(choice, "EXIT") == 0)
        {
            input = EXIT; // 设置选择为退出游戏
        }
        else
        {
            printf("输入错误,请重新输入\n");
            continue; // 输入错误,继续循环
        }

        switch (input)
        {
        case PLAY:
            printf("扫雷游戏启动!\n");
            break;
        case EXIT:
            printf("不玩了,启动不了!\n");
            break;
        }

    } while (input != EXIT);

    return 0;
}

代码运行:
在这里插入图片描述

  1. #define定义的标识符⽐较枚举有类型检查,更加严谨。

  2. 便于调试,预处理阶段会删除 #define 定义的符号在这里插入图片描述

  3. 使⽤⽅便,⼀次可以定义多个常量

  4. 枚举常量是遵循作⽤域规则的,枚举声明在函数内,只能在函数内使⽤

🌉 枚举类型的使⽤

那是否可以拿整数给枚举变量赋值呢?在C语⾔中是可以的,但是在C++是不⾏的,C++的类型检查⽐较严格。

  • C语言中,枚举类型实际上就是整数类型,编译器会把枚举常量替换成对应的整数值。所以可以用整数直接给枚举变量赋值。

  • 而在C++中,枚举类型是完全独立的类型。编译器会检查类型是否匹配,不允许用整数直接给枚举变量赋值。

例如:

# define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
//C语言
enum Color//颜⾊
{
	RED = 1,
	GREEN = 2,
	BLUE = 4
};
int main()
{
	enum Color c;
	c = 1; // 可以直接赋值整数
	return 0;
}

输出:
在这里插入图片描述

// C++语言
enum Color//颜⾊
{
	RED = 1,
	GREEN = 2,
	BLUE = 4
};

Color c;
c = 1; // 错误,类型不匹配

输出:
在这里插入图片描述

总结:
C语言中枚举类型实际上就是整数,允许用整数直接赋值
C++中枚举类型是独立类型,不允许用整数直接赋值,需要强制类型转换


🚩总结

这次阿森和你一起学习联合体类型的声明,特点,然后进行相同成员的结构体和联合体对⽐,⼤⼩的计算,联合体应用,枚举类型的声明,优点和扫雷改造使⽤方法,阿森将下一节和你一起学习动态内存管理🚀 。

感谢你的收看,如果文章有错误,可以指出,我不胜感激,让我们一起学习交流,如果文章可以给你一个小小帮助,可以给博主点一个小小的赞😘
请添加图片描述

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

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

相关文章

PostgreSQL10数据库源码安装及plpython2u、uuid-ossp插件安装

PostgreSQL10数据库源码安装及plpython2u、uuid-ossp插件安装 1、环境2、安装包下载3、安装3.1 、解压3.2、配置3.3、编译安装3.4 、启动与关闭 4、安装 uuid-ossp 、plpython2u插件5、参考 1、环境 centos 7 、 postgresql 10.19 2、安装包下载 postgres 源码安装包 3、安…

Git基础学习_p1

文章目录 一、前言二、Git手册学习2.1 Git介绍&前置知识2.2 Git教程2.2.1 导入新项目2.2.2 做更改2.2.3 Git追踪内容而非文件2.2.4 查看项目历史2.2.5 管理分支&#x1f53a;2.2.6 用Git来协同工作2.2.7 查看历史 三、结尾 一、前言 Git相信大部分从事软件工作的人都听说过…

共享单车之数据存储

文章目录 第1关&#xff1a;获取工作簿中的数据第2关&#xff1a;保存共享单车数据 第1关&#xff1a;获取工作簿中的数据 相关知识 获取工作簿中的信息&#xff0c;我们可以使用Java POI&#xff08;POI是一个提供API给Java程序对Microsoft Office格式档案读和写的功能&#…

[数据结构]树与二叉树的性质

文章目录 0.二叉树的形态和基本性质1.完全二叉树的叶子节点个数2.树的叶子节点个数3.线索二叉树4.树和森林和二叉树5.平衡二叉树的最少结点数6.树/二叉树/森林的转换 0.二叉树的形态和基本性质 一棵二叉树具有5中基本形态n个结点可以构造的二叉树种数: C2n-n/n1 一棵树 n个结点…

GC6208国产5V摄像机镜头驱动IC ,可用于摄像机,机器人等产品中可替代AN41908

GC6208是一个镜头电机驱动IC摄像机和安全摄像机。该设备集成了一个直流电机驱动器的Iris的PID控制系统&#xff0c;也有两个通道的STM电机驱动器的变焦和对焦控制。 芯片的特点: 内置用于Iris控制器的直流电机驱动器 内置2个STM驱动程序&#xff0c;用于缩放和…

【SD】inpaint 模型 - 换脸术 ☑

文生图-局部重绘 涂抹脸部 关键词添加lora&#xff1a; <lora:Naruto_zilaiye:1.5>, 生成图&#xff1a;

【音视频 ffmpeg 学习】 跑示例程序 持续更新中

环境准备 在上一篇文章 把mux.c 拷贝到main.c 中 使用 attribute(unused) 消除警告 __attribute__(unused)/** Copyright (c) 2003 Fabrice Bellard** Permission is hereby granted, free of charge, to any person obtaining a copy* of this software and associated docu…

.NetCore NPOI 读取excel内容及单元格内图片

由于数据方提供的数据在excel文件中不止有文字内容还包含图片信息&#xff0c;于是编写相关测试代码&#xff0c;读取excel文件内容及图片信息. 本文使用的是 NPOI-2.6.2 版本&#xff0c;此版本持.Net4.7.2;.NetStandard2.0;.NetStandard2.1;.Net6.0。 测试文档内容&#xf…

基于 Linux 的批量上传本地 Git 仓库到 Github 的实践

基于 Linux 的批量上传本地 Git 仓库到 Github 的实践 一、需求二、上传本地 Git 仓库2.1 初始版本2.2 优化版本 三、 GitHub 创建空仓库3.1 初始版本3.2 优化版本 四、Gitee 创建空仓库 一、需求 app目录下的每个文件夹都是一个git仓库&#xff0c;如何使用shell脚本将所有gi…

Linux文件系统结构及相关命令1(man pwd ls ctrl +Shift +T ls /etc)

Linux的文件系统结构 某所大学的学生可能在一两万人左右&#xff0c;通常将学生分配在以学院-系班为单位的分层组织机构中。 如何查找一名学生&#xff1f; 最笨的办法&#xff1a;依次问询大学中的每一个学生&#xff0c;直到找到为止。 查询效率高的方法&#xff1a;按照从…

Eureka服务注册与发现

1. Eureka简介 Eureka采用了CS的设计架构&#xff0c;Eureka Server 作为服务注册功能的服务器&#xff0c;它是服务注册中心。而系统中的其他微服务&#xff0c;使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系…

微服务(1)

目录 1.什么是微服务&#xff1f;谈谈你对微服务的理解&#xff1f; 2.什么是Spring Cloud&#xff1f; 3.Springcloud中的组件有哪些&#xff1f; 3.具体说说SpringCloud主要项目&#xff1f; 5.SpringCloud项目部署架构&#xff1f; 1.什么是微服务&#xff1f;谈谈你对微…

idea配置docker推送本地镜像到远程私有仓库

目录 1&#xff0c;搭建远程Docker 私有仓库 Docker registry 2&#xff0c;Windows10/11系统上安装Docker Desktop 3&#xff0c;idea 配置远程私有仓库地址 4&#xff0c;idea 配置Docker 5&#xff0c;idea在本地构建镜像 6&#xff0c;推送本地Docker镜像到远程 Dock…

DotNet 命令行开发

DotNet 命令行开发 下载安装下载 SDK安装 SDK绿色版下载绿化脚本 常用命令创建 dotnet new运行 dotnet run发布应用 dotnet publish更多命令 VSCode 调试所需插件调试 CS 配置项目.csproj排除依赖关系 launch.jsontasks.json 参考资料 下载安装 下载 SDK 我们就下最新的好&am…

事实验证文章分类 Papers Category For Fact Checking

事实验证文章分类 Papers Category For Fact Checking By 2023.11 个人根据自己的观点&#xff0c;花了很多时间整理的一些关于事实验证领域证据召回&#xff0c;验证推理过程的文献综合整理分类&#xff08;不是很严谨&#xff09;。 引用请注明出处 欢迎从事事实验证Fact…

【开源】基于Vue+SpringBoot的就医保险管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 科室档案模块2.2 医生档案模块2.3 预约挂号模块2.4 我的挂号模块 三、系统展示四、核心代码4.1 用户查询全部医生4.2 新增医生4.3 查询科室4.4 新增号源4.5 预约号源 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVue…

基于ElementUI二次封装弹窗组件

效果&#xff1a; 一、自定义内容类型弹窗 <!-- title&#xff1a;对话框的标题confirmLoading&#xff1a;当前是否处于提交中titleCenter&#xff1a;对话框标题居中方式footerCenter&#xff1a;底部按钮的对其方式visible&#xff1a;是否显示弹窗width&#xff1a;设置…

重定向和转发的区别

重定向 1、定义 用户通过浏览器发送一个请求&#xff0c;Tomcat服务器接收这个请求&#xff0c;会给浏览器发送一个状态码302&#xff0c;并设置一个重定向的路径&#xff0c;浏览器如果接收到了这个302的状态码以后&#xff0c;就会去自动加载服务器设置的路径 一个页面跳转…

【测试开发与AIchat】它的思维跟大多数人还是一样的,都解决不了实际问题,可能是它也没有积累类似的经验[chatGPT]

分享一个人工智能{AI}解决问题的工具GPT(点我赶紧注册)&#xff0c;它是有GPT-4模型的。 它可以做很多事情&#xff0c;譬如问&#xff1a;开发平台功能 但是它仍然没有解决题主的问题。 源码如下&#xff1a; #....with smtplib.SMTP() as smtp:smtp.connect(smtp_server…

【两两交换链表中的节点】

Problem: 24. 两两交换链表中的节点 文章目录 思路解题方法Code 思路 把第一步的模拟过程的步骤记录下来 一共分为三个步骤 解题方法 创建虚拟头节点 循环什么时候结束&#xff0c;需要考虑问题 Q&#xff1a; 奇数链表结束条件&#xff1f;偶数链表结束条件&#xff1f;为什么…
最新文章