【C语言进阶】结构体、位段、枚举和联合
👦个人主页:@Weraphael
✍🏻作者简介:目前是C语言学习者
✈️专栏:C语言航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注
目录
- 一、结构体
- 1.1 前言
- 1.2 结构体的概念
- 1.3 结构的声明
- 1.4 结构的特殊声明
- 1.5 结构的自引用
- 1.6 变量的定义、初始化、访问和传参
- 1.7 ✨结构体内存对齐
- 1.8 修改默认对齐数
- 二、位段
- 2.1 什么是位段
- 2.2 位段的大小
- 2.3 位段的内存分配
- 2.4 位段跨平台问题。
- 三、枚举
- 3.1 枚举的概念
- 3.2 枚举的定义、初始化及使用
- 3.3 枚举的优点
- 3.4 枚举的大小
- 四、联合
- 4.1 联合类型的定义及初始化
- 4.2 联合大小计算
- 4.3 联合的特点
一、结构体
1.1 前言
在《C语言初阶》中(初阶结构体传送门),我们浅浅学习了结构体的声明、结构体成员的访问、结构体嵌套和结构体传参。今天这篇博客将带领大家深入学习有关结构体的知识。闲言少叙,开快车🚝🚝
1.2 结构体的概念
- 结构是一些值的集合,这些值可以称为成员变量,结构的每个成员可以是不同类型的变量。
- 类比数组。数组也是一些值的集合,但类型是相同的。
1.3 结构的声明
//结构体的声明
struct tag
{member_list; //成员变量
}variable_list;//variable_list - 变量列表
//注意:后面的分号不可缺
举个例子,假设要描述一名学生
可以描述一个学生的名字、性别、学号、成绩、年龄等等。
【第一种声明】
//描述一位学生
struct Stu //Stu - 标签
{char name[10]; //名字int age; //年龄char sex[5]; //性别}s1; //s1 - 结构体变量(全局变量)
//分号不可缺int main()
{struct Stu s2;//结构体变量(局部变量)
}
【第二种声明typedef】
//描述一位学生
typedef struct Stu //Stu - 标签
{char name[10]; //名字int age; //年龄char sex[5]; //性别}Stu; //Stu - 将结构体类型struct Stu重新命名为Stu
//分号不可缺int main()
{Stu s1; //创建结构体变量(全局)
}
1.4 结构的特殊声明
在声明结构的时候,也可以不完全的声明。
【匿名结构体类型(缺少标签)】
注意:
- 若要使用结构体类型,在创建类型马上在后面跟上变量
- 如果两个匿名结构体中的成员变量完全一样也会被编译器当成两个完全不同的类型
1.5 结构的自引用
首先先想一个问题:在结构中包含一个类型为该结构本身的成员是否可以?
【举个例子】
假设要存储1、2、3。可以用链表来存储。一个节点中存储一个数,并且当前的节点能够找到下一个节点。所以我们可以把节点定义成一个结构体
【错误代码】
struct Node
{int data;struct Node next;
};
但其实上面的代码是错误的,假如说要计算结构体的大小,data是4个字节,next包含date和下一个节点,下个next又包含date和下一个节点…,这样下来会发现它的大小其实是very very大的。
【正确代码】
我们可以放一个指向下一个节点的指针,也就是结构体指针
struct Node
{int data; //4个字节struct Node* next; //4/8个字节
};
那么接下来我把代码改成这样是否也是正确的?
【错误代码】
typedef struct Node
{int data;Node* next;
}Node;
这其实是错误的!因为代码编译是从上到下的,当走到
Node* next
时,struct Node
还未被typedef
重命名
【正确代码】
typedef struct Node
{int data;struct Node* next;
}Node;
1.6 变量的定义、初始化、访问和传参
详情见《初阶结构体》-> 传送门
1.7 ✨结构体内存对齐
在了解结构体内存对齐之前,我们先想想如何计算结构体的大小
为什么
Stu1
和Stu2
的成员变量一样,就仅仅交换了位置,结构体大小却不一样,这是为什么呢?这就要涉及到结构体内存对齐
🎈结构体内存对齐的规则
- 第一个成员在与结构体变量偏移量为0的地址处
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
- 对齐数 = 编译器默认的一个对齐数与该成员类型大小的较小值。(VS中默认的值为8,Linux环境下无对齐数)
- 结构体总大小为结构体成员最大对齐数的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
现在解释开头的代码
以下解析默认是在visual studio环境下
【Stu1】
【Stu2】
如果大家不相信第一个成员在与结构体变量偏移量为0的地址处,我们可以来验证。
C语言有一个宏叫offsetof
,它的功能是计算一个结构体成员相较起始位置的偏移量(头文件:#include <stddef.h>
)
1.8 修改默认对齐数
在《结构体内存对齐》中提到了默认对齐数,那么默认对齐数能否修改呢?答案是当然可以!
可以用#pragma
这个预处理指令
那么接下来问题来了,怎么恢复呢?
二、位段
2.1 什么是位段
位段的声明和结构其实是类似的,但有两个不同:
- 位段的成员必须是
int
、unsigned int
或signed int
,但也可以是char
类型,因为char
是属于整型家族的- 位段的成员名后边有一个冒号和一个数字
- 位段里的成员一般都是同类型=的
- 位段的位其实表示二进制位
【举个例子】
#include <stdio.h>
struct segment
{int a : 2; //表示a只占内存的2个二进制位int b : 5;//表示b只占内存的5个二进制位int c : 10;//表示c只占内存的10个二进制位int d : 30;//表示d只占内存的30个二进制位
};
segment
就是一个位段类型。
2.2 位段的大小
结构体的大小计算和位段的计算是一样的吗?答案其实是不一样的
位段的计算过程其实是这样的
int
是4个字节,有32个比特位,a
占内存2个二进制位,还剩下30个比特位b
占内存5个二进制位,还剩下25个比特位c
占内存10个二进制位,还剩下15个比特位d
占内存30个比特位,上一步还剩下15个比特位,假设把这15个比特位舍弃,继续向内存申请32个比特位给d
用,所以还剩余2个比特位
从上过程中,类型在创建的时候向内存一共申请了32+32
个比特位,也就8
字节。也能看出位段其实比结构体更加节省空间,有多少用多少。
2.3 位段的内存分配
- 位段的成员可以是
int
、unsigned int
、signed int
或者是char
类型- 位段的空间上是按照需要以4和字节(
int
)或者1个字节(char
)的方式来开辟的- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
【举个例子】
我们一起看看下面这一串代码在内存中是如何分配的
#include <stdio.h>
struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};int main()
{struct S s = { 0 };s.a = 10;s.b = 12;s.c = 3;s.d = 4;
}
首先先定制一些规则:
- 假设位段中的成员在内存中是从低位向高位分配
- 当第二个位段成员占内存二进制位比较大时,无法容纳前一个剩余位段时,直接把前一个剩余的舍弃
- 在visual studio测试
分配过程如下:结构体初始化为000000000
a
被赋值成10,转化为二进制:00001010,而a
只占内存3个二进制位,也就是010(最高位的1被截断),分配:00000010- b被赋值成12,转化为二进制:00001100,b占内存4个二进制位,恰好能容纳上一个剩余位段。分配01100010
- c被赋值成3,转化为二进制:00000011,c只占内存5个二进制位,也就是:00011,然后这次的位段成员无法容纳上一个剩余位段,我们就舍弃,继续向内存申请:00000000,分配:00000011
- d被赋值成4,转化为二进制:00000100,d只占内存4个二进制位,然而上一个剩余位段还是无法容纳这次的成员位段,继续像内存申请:00000000,分配:00000100
- 所以现在内存里为:0110010 00000011 00000100
转化为十六进制也就是 62 03 04,我们F10调试,在内存中看看
这结果恰好就是我们分析出来的结果!
2.4 位段跨平台问题。
int
位段被当成有符号数还是无符号数是不确定的- 位段中最大位的数目不能确定。16位机器最大16,32位机器最大32,如果
int
位段成员占内存27个二进制位,那么在16位机器会出现问题- 位段中的成员咋子内存中从左向右分配,还是从右向左分配标准还未确定。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余位时,是舍弃剩余的位还是利用,这也是未确定的。
总结:
跟结构体相比,位段可以达到同样的效果,但是位段可以很好的节省空间,但是有跨平台的问题存在
三、枚举
3.1 枚举的概念
枚举顾名思义就是一一列举
比如在我们生活中:
- 星期一到星期天是有限的7个,可以一一列举
- 性别,男和女,也可以一一列举
- 一年有12个月,也可以一一列举
3.2 枚举的定义、初始化及使用
枚举和结构体其实是非常类似的,它的关键字:
enum
【举个例子】
//枚举星期一道星期天
enum Week
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};
//分号不能丢
//成员以逗号结尾int main()
{enum Week w = Mon;//初始化return 0;
}
- 上面定义的
enum Week
是枚举类型{}
中的内容是枚举类型的可能取值,也叫枚举常量- 初始化最好用枚举常量
- 枚举常量默认是从0开始,一次递增1
- 枚举常量在定义的时候也可以赋初值
- 枚举常量在初始化之后是不能被修改的
3.3 枚举的优点
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较,枚举有类型检查,更加严谨
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
3.4 枚举的大小
枚举的大小是4个字节
四、联合
联合也是一种特殊的自定义类型,其关键字是:
union
,这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间,所以联合也叫共用体
4.1 联合类型的定义及初始化
//定义一个联合类型
union UN
{char c;int a;
};int main()
{union UN x; //初始化return 0;
}
4.2 联合大小计算
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
- 最小对齐数 = 编译器默认的一个对齐数与该成员类型大小的较小值。(VS中默认的值为8,Linux环境下无对齐数)
【举个例子】
char c[5]
自身大小是5,对齐数(成员类型大小):1,默认对齐数是(vs环境):8,其最小对齐数为1int a
自身大小是4,对齐数(成员类型大小):4,默认对齐数是(vs环境):8,最小对齐数为4- 通过比较它们两个的最小对齐数,发现4为最大对齐数,而最大成员大小是5,5不是4的整数倍,因此要浪费空间到,对齐到8,所以它们大小是8
4.3 联合的特点
联合的成员是共用同一块内存空间,这样一个联合变量的大小,至少是最大成员的大小。
【证明】
我们发现,它们3个的地址都相等,就是因为它们共用了同一块空间。
相关文章:

【C语言进阶】结构体、位段、枚举和联合
👦个人主页:Weraphael ✍🏻作者简介:目前是C语言学习者 ✈️专栏:C语言航路 🐋 希望大家多多支持,咱一起进步!😁 如果文章对你有帮助的话 欢迎 评论💬 点赞&a…...
markdown和latex常用部分参考@注脚@链接跳转@csdn
文章目录refmarkdown和latex常用部分参考typora文档基础语法扩展语法链接内联链接的方式将链接提取出来链接示例typora的支持LinksInline LinksInternal Links🎈Reference LinksURLs文章内部跳转(Heading IDs)🎈My Great Heading注脚(Footnotes)…...

Java 在二叉树中增加一行
623. 在二叉树中增加一行中等给定一个二叉树的根 root 和两个整数 val 和 depth ,在给定的深度 depth 处添加一个值为 val 的节点行。注意,根节点 root 位于深度 1 。加法规则如下:给定整数 depth,对于深度为 depth - 1 的每个非空树节点 cur…...

kubernetes(k8s) 知识总结(第2期)
1. “控制器”思想 kube-controller-manager 是一系列控制器的集合,这些控制器被放在 Kubernetes 项目的 pkg/controller 目录,这些控制器都以独有的方式负责某种编排功能。它们都遵循一个通用的编排模式——控制循环。 以 Deployment 为例介绍它对控…...

windows-Mysql的主从数据库同步设置
复制原有的mysql修改my.ini配置文件 修改端口号修改从数据的地址和从数据库的数据存放地址安装从数据库进入从数据库的bin目录,打开命令窗口输入命令:mysqld.exe install mysql-back --defaults-file "C:\ProgramData\MySQL\MySQL Server 5.7-back\…...

Docker逃逸
文章目录原理环境搭建Docker 环境判断Docker 容器逃逸特权模式逃逸如何判断是否为特权模式逃逸docker.sock挂载逃逸逃逸Remote API未授权访问未授权访问逃逸容器服务缺陷逃逸影响版本环境搭建逃逸脏牛漏洞逃逸参考原理 docker其实就是一个linux下的进程,它通过Name…...

k8s项目部署
k8s命令k8s项目部署部署流程实现导出相应的yaml文件 kubectl create deployment 名字--image镜像-o yaml --dry-runclient > 文件名 例: kubectl create deployment nginx --imagenginx -o yaml --dry-runclient > m1.yaml导出已经部署后的yaml文件 kubectl g…...

Modbus通信协议学习笔记
Modbus主从设备 主控设备(Modbus Master):工控机、PLC、触摸屏等等 从设备(Modbus Slave):PLC、Modbus采集模块、带485通讯的传感器、仪器仪表等等 Modbus物理接口:串口(RS232、RS4…...

ubuntu重启、关机命令
// // // //之前用linux系统, 一键解决也是可以的,反正我每次用命令(泪目…),中间崩了好几次,换回win,此篇也做记录 // // // 重启命令 以下所有命令在root根目录下输入(普通用户&…...

Xshell 7 连接云服务器的步骤和出现的错误
一、工具准备云服务器Xshell 7二、使用 Xshell 7 连接数据库三、新建会话属性后,没有自动弹出 SSH 用户名要求输入四、SSH 用户身份验证不能输入 Password五、Xshell 连接 centos 7 服务器 报错提示 “ssh服务拒绝了密码,请再试一次“,但是密…...

Python多进程同步——文件锁
多个进程共享同一份资源(共享内存、文件等)时,会涉及到资源竞争问题。为了解决这种问题,一般采取的措施是进程在访问资源前加锁保护,避免多个进程同时读写。本文介绍的Python文件锁可以用来解决多进程的同步问题。 目录…...

实现 element-plus 表格多选时按 shift 进行连选的功能
前言 element-plus表格提供了多选功能,可单击勾选一条数据,可全选。 现在有个很合理的需求,希望实现类似于文件系统中shift连续选择功能,并且在表格排序后,依照排序后的顺序连选。 一、el-table 多选表格基本使用 1、…...

华为OD机试真题JAVA实现【考古学家】真题+解题思路+代码(20222023)
🔥系列专栏 华为OD机试(JAVA)真题目录汇总华为OD机试(Python)真题目录汇总华为OD机试(C++)真题目录汇总华为OD机试(JavaScript)真题目录汇总文章目录 🔥系列专栏题目输入输出示例一输入输出说明示例二输入输出说明...

Spring3之基于Aspect实现AOP
简介 使用 Aspect 搭配 Spring 可轻松实现 AOP;本章将通过一个完整示例演示如何实现这一功能 实现步骤 修改 beans.xml 配置文件的 schema 部分;可以在 spring-framework-reference.html 文件通过搜索关键字 “/aop” 找到配置 schema,然后…...

buctoj-寒假集训进阶训练赛(二十二)
问题 A: Stones 题目描述 由于自行车状态错误,森普尔开始每天早上从东到西走,每天晚上走回去。走路可能会有点累,所以森普这次总是玩一些游戏。 路上有很多石头,当他遇到一块石头时,如果是他遇到的奇数石头࿰…...

华为OD机试真题JAVA实现【静态扫描最优成本】真题+解题思路+代码(20222023)
🔥系列专栏 华为OD机试(JAVA)真题目录汇总华为OD机试(Python)真题目录汇总华为OD机试(C++)真题目录汇总华为OD机试(JavaScript)真题目录汇总文章目录 🔥系列专栏题目输入输出描述示例一输入输出说明示例二输入输出说明...

汽车装配工厂立库物料运送线PLC无线应用
一、应用背景此次项目地在比亚迪的西安工厂,需要实现PLC无线通讯的地方是汽车厂的立体仓库物料运输线。生产物流担负运输、存储、装卸物料等任务。汽车制造业是典型的多工种、多工艺、多物料的大规模生产过程,因此原材料与零部件必需及时准确送至工位&am…...

Python雪花代码
前言 用python画个雪花玩玩,源码在文末公众号哈。 雪花类 class Snow(): #雪花类 def __init__(self): self.r 6 #雪花的半径 self.x ra.randint(-1000,1000) #雪花的横坐标 self.y ra.randint(-500,5…...

Numpy基础与实例——人工智能基础
文章目录一、Numpy概述1. 优势2. numpy历史3. Numpy的核心:多维数组4. 内存中的ndarray对象4.1 元数据(metadata)4.2 实际数据二、numpy基础1. ndarray数组2. arange、zeros、ones、zeros_like3. ndarray对象属性的基本操作3.1 修改数组维度3…...

MQTT的工作原理
介绍MQTT协议的消息模型,消息传输过程,消息发布和订阅。 一、介绍MQTT协议的消息模型 MQTT协议的消息模型被称为“主题”模型。在这种模型中,服务器接收到的消息将通过主题进行分类。客户端可以通过订阅一个或多个主题来接收所需的消息。 1.1 消息主题 1.2 消息内容 1.…...

iOS开发:UINavigationController自定义返回按钮,系统导航支持侧滑返回
当你使用系统导航想拦截用户返回事件时,无法拦截侧滑返回 当你自定义导航或者隐藏导航后,iOS系统导航的侧滑返回就失效了,那么用户体验将大打折扣 网上大部分自定义导航的解决方案是:给页面添加全局的轻扫手势,那么又区别于原生系统,改变了用户的操作习惯 在开发过程中,…...

【Kafka进阶】-- unclean.leader.election.enable参数的内涵
一、背景近期,我们的kafka 消息队列集群(1.x版本)经过了一次事故。某节点意外宕机,导致 log 文件损坏,重启 kafka 失败,最后导致某个 topic 的分区不可用,本文对此做了简单的分析、解决和复现参考,以此为记…...

基于redis实现分布式锁
前言 我们的系统都是分布式部署的,日常开发中,秒杀下单、抢购商品等等业务场景,为了防⽌库存超卖,都需要用到分布式锁。 分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或…...

C#开发的OpenRA动态加载插件DLL里的类实现
C#开发的OpenRA动态加载插件DLL里的类实现 由于这款游戏的设计是为了开源设计, 并且可以让不同个人或团体实现自己的游戏, 那么每个人实现的代码是不一样的,算法也是不一样的。 并且可能也拿不到代码一起编译生成一套运行的代码。 这时候,就要考虑使用动态加载类的功能。 意…...

网站代理是什么?有什么需要注意的?
如今,网站代理已经成为一种不可或缺的经营方式。无论是企业还是个人,都需要通过代理来获得更多的流量和市场份额。 一、网站代理的优势 网站代理的优势在于能够为您提供更加专业、周到的服务。这些优势包括:1.丰富的内容资源,能…...

动态库和静态库的区别
什么是库文件 一般来说,一个程序,通常都会包含目标文件和若干个库文件。经过汇编得到的目标文件再经过和库文件的链接,就能构成可执行文件。库文件像是一个代码仓库或代码组件的集合,为目标文件提供可直接使用的变量、函数、类等…...

C/C++路径去除前缀
在做一些日志输出的工作时,想要获取当前文件名,而不是冗长的文件路径。路径获取往往和各家os底层函数优化。C/C标准中定义了一些预处理宏,可以帮助我们获取文件路径。我们希望能够在编译期而不是在运行期做这个事情,避免额外的性能…...

Vue2之Vue-cli应用及组件基础认识
Vue2之Vue-cli应用及组件基础认识一、Vue-cli1、单页面应用程序2、vue-cli介绍3、安装和使用4、创建项目4.1 输入创建项目4.2 选择第三项,进行自主配置,按回车键即可4.3 选择自己需要的库4.4 选择Vue的版本4.5 选择CSS选择器4.6 选择Babel、ESLint、etc等…...

C 学习笔记 —— 声明、定义、初始化
文章目录声明定义初始化定义和初始化的区别静态变量初始化自动变量初始化声明 说明符表达式列表 int a; char j, k l;定义 一般的情况下,我们把建立空间的声明称之为定义,而把不需要建立存储空间的声明称之为声明。 int tern 1; //定义int main() {…...

机械狗控制算法
一. MIT Cheetah特点 1.驱动器 Cheetah 2采用了定制的本体感受驱动器设计,具有高冲击缓解、力控制和位置控制能力。这种设计使其能够自主跳过障碍物,并以6m/s的高速跳跃,但其运动范围有限,只能进行矢状面运动。 Cheetah 3采用高扭…...