【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));//8printf("%zd\n", sizeof(union Un2));//16return 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 01return *(char*)&n;
}int main()
{int ret = check_sys();if (ret == 1)printf("小端\n");elseprintf("大端\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.xdnf.cn/news/824129.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

MacBook查看本机IP

嘚吧嘚 其实这也不是什么困难的问题&#xff0c;但是今年刚刚入坑Mac&#xff0c;外加用的频率不是很高&#xff0c;每次使用的时候都查&#xff0c;用完就忘&#xff0c;下次用的时候再查&#x1f92e;。真的把自己恶心坏了&#x1f648;。 所以写篇文章记录一下&#x1f92…

软件设计师——软件工程(三)

&#x1f4d1;前言 本文主要是【软件工程】——软件设计师——软件工程的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304…

Rust开发⼲货集(1)--迭代器与消费器

本内容是对 Rust开发干货集[1] 的实践与扩展. iter() 不转移所有权 先简单解释下什么叫"转移所有权": 在 Rust 中&#xff0c;"转移所有权"&#xff08;Ownership Transfer&#xff09;是一种核心概念&#xff0c;它涉及变量和数据的所有权从一个实体转移…

基于AM62x的ARM+FPGA+Codesys低成本软PLC解决方案

GPMC并口简介 GPMC(General Purpose Memory Controller)是TI处理器特有的通用存储器控制器接口&#xff0c;支持8/16bit数据位宽&#xff0c;支持128MB访问空间&#xff0c;最高时钟速率133MHz。GPMC是AM62x、AM64x、AM437x、AM335x、AM57x等处理器专用于与外部存储器设备的接口…

2023年03月22日_腾讯2022年财报解读

文章目录 1 - 腾讯营收增长停滞2 - 腾讯游戏业务低迷3 - 小程序和视频号拉动广告增长4 - 腾讯云和金融科技表现不佳5 - 营销费用减半6 - 裁员但福利上涨 2023年03月22日 今天晚上呢 腾讯披露了2022年第四季度和全年的财报 看过之后呢不禁要说 腾讯在2022年真的是过得不容易啊…

【Java开发岗面试】八股文—操作系统

声明&#xff1a; 背景&#xff1a;本人为24届双非硕校招生&#xff0c;已经完整经历了一次秋招&#xff0c;拿到了三个offer。本专题旨在分享自己的一些Java开发岗面试经验&#xff08;主要是校招&#xff09;&#xff0c;包括我自己总结的八股文、算法、项目、HR面和面试技巧…

3D视觉-结构光测量-线结构光测量

概述 线结构光测量中&#xff0c;由激光器射出的激光光束透过柱面透镜扩束&#xff0c;再经过准直&#xff0c;产生一束片状光。这片光束像刀刃一样横切在待测物体表面&#xff0c;因此线结构光法又被成为光切法。线结构光测量常采用二维面阵 CCD 作为接受器件&#xff0c;因此…

【Web】vulhub-httpd apache解析漏洞复现(1)

目录 ①CVE-2017-15715 ②apache_parsing_vulnerability ①CVE-2017-15715 贴出源码&#xff1a; <?php if(isset($_FILES[file])) {$name basename($_POST[name]);$ext pathinfo($name,PATHINFO_EXTENSION);if(in_array($ext, [php, php3, php4, php5, phtml, pht]))…

python+django大自然环境保护宣传网站62r9b

本课题使用Python语言进行开发。基于web,代码层面的操作主要在PyCharm中进行&#xff0c;将系统所使用到的表以及数据存储到MySQL数据库中 本系统由后台管理子系统&#xff0c;登录子系统&#xff0c;按登陆角色及权限划分为管理员:个人中心&#xff0c;用户管理&#xff0c;文…

macos 打开终端提示 You have new mail. 去除方法

这个提示信息是macos里面的mail消息提示, 如果需要查看详细的信息可以在终端输入 mail 命令即可查看所有信息, 这些信息都保存在 /private/var/mail/xxx 文件中 xxx 是你的macos的登录用户名, 要去除这些提示,只需要删除这个文件即可 # 删除mail信息存储文件 sudo rm -rf /…

androidStudio 没有新建flutter工程的入口?

装了flutter dart 插件 执行了 flutter doctor 也执行了 flutter doctor --android-license 最后重启了 androidStudio 还是没发现在哪新建flutter项目工程 原来 plugins 下的 Android APK Support没有勾选

Apache-ActiveMQ 反序列化漏洞(CVE-2015-5254)复现

CVE-2016-3088 一、环境搭建 Java:jdk8 影响版本 Apache ActiveMQ < 5.13.0 二、用docker搭建漏洞环境 访问一下web界面 然后进入admin目录登录 账号:admin 密码:admin 三、工具准备 cd /opt wget https://github.com/matthiaskaiser/jmet/releases/download/0.1.0/jmet-0…

大模型推理部署:LLM 七种推理服务框架总结

自从ChatGPT发布以来&#xff0c;国内外的开源大模型如雨后春笋般成长&#xff0c;但是对于很多企业和个人从头训练预训练模型不太现实&#xff0c;即使微调开源大模型也捉襟见肘&#xff0c;那么直接部署这些开源大模型服务于企业业务将会有很大的前景。 本文将介绍七中主流的…

计算机网络【EPoll原理】

预备知识&#xff1a;内核poll钩子原理 内核函数poll_wait 把当前进程加入到驱动里自定义的等待队列上 &#xff1b; 当驱动事件就绪后&#xff0c;就可以在驱动里自定义的等待队列上唤醒调用poll的进程&#xff1b; 故poll_wait作用&#xff1a;可以让驱动知道事件就绪的时…

CSS之元素转换

我想大家在写代码时有一个疑问&#xff0c;块级元素可以转换成其他元素吗&#xff1f; 让我为大家介绍一下元素转换 1.display:block(转换成块元素) display&#xff1a;block可以把我们的行内元素或者行内块元素转换成块元素 接下来让我为大家演示一下&#xff1a; <!DO…

Tomcat与Servlet是什么关系

Tomcat与Servlet是什么关系 Apache Tomcat和Servlet之间存在密切的关系&#xff0c;可以说它们是一对密切合作的组件。下面是它们的关系&#xff1a; Tomcat是Servlet容器&#xff1a; Tomcat是一个开源的、轻量级的Servlet容器。Servlet容器是一个Web服务器扩展&#xff0c;用…

<JavaEE> 协议格式 -- 应用层协议 HTTP

目录 一、HTTP的概念 1&#xff09;什么是HTTP协议&#xff1f; 2&#xff09;什么是超文本&#xff1f; 二、HTTP协议格式 三、请求&#xff08;request&#xff09; 1&#xff09; 方法&#xff08;Method&#xff09; 1> GET方法 2> POST方法 3> GET和POS…

sklearn学习的一个例子用pycharm jupyter

环境 运行在jupyter 进行开发。即一个WEB端的开发工具。能适时显示开发的输出。后缀用的是ipynb.pycharm也可以支持。但也要提示按装jupyter. 或直接用andcoda 这里我们用pycharm进行项目创建 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple jupyterlab pip ins…

uni-app API接口扩展组件(uni-ui)

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

Redis:原理速成+项目实战——Redis的Java客户端

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;Redis&#xff1a;原理速成项目实战——Redis常见命令&#xff08;数据结构、常见命令总结&#xff09; &#x1f4da;订阅专栏&…