【C语言进阶】结构体、位段、枚举和联合

news2024/11/22 21:05:01

在这里插入图片描述

👦个人主页:@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 ✨结构体内存对齐

在了解结构体内存对齐之前,我们先想想如何计算结构体的大小

在这里插入图片描述

为什么Stu1Stu2的成员变量一样,就仅仅交换了位置,结构体大小却不一样,这是为什么呢?这就要涉及到结构体内存对齐
🎈结构体内存对齐的规则

  1. 第一个成员在与结构体变量偏移量为0的地址处
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
  3. 对齐数 = 编译器默认的一个对齐数与该成员类型大小的较小值。(VS中默认的值为8,Linux环境下无对齐数)
  4. 结构体总大小为结构体成员最大对齐数的整数倍
  5. 如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

现在解释开头的代码

以下解析默认是在visual studio环境下

【Stu1】

在这里插入图片描述

【Stu2】

在这里插入图片描述

如果大家不相信第一个成员在与结构体变量偏移量为0的地址处,我们可以来验证。
C语言有一个宏叫offsetof,它的功能是计算一个结构体成员相较起始位置的偏移量(头文件:#include <stddef.h>
在这里插入图片描述

1.8 修改默认对齐数

在《结构体内存对齐》中提到了默认对齐数,那么默认对齐数能否修改呢?答案是当然可以!

可以用#pragma这个预处理指令

在这里插入图片描述

那么接下来问题来了,怎么恢复呢?

在这里插入图片描述

二、位段

2.1 什么是位段

位段的声明和结构其实是类似的,但有两个不同:

  1. 位段的成员必须是intunsigned intsigned int,但也可以是char类型,因为char是属于整型家族的
  2. 位段的成员名后边有一个冒号和一个数字
  3. 位段里的成员一般都是同类型=的
  4. 位段的其实表示二进制位

【举个例子】

#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 位段的内存分配

  1. 位段的成员可以是intunsigned intsigned int或者是char类型
  2. 位段的空间上是按照需要以4和字节(int)或者1个字节(char)的方式来开辟的
  3. 位段涉及很多不确定因素,位段是不跨平台的注重可移植的程序应该避免使用位段

【举个例子】

我们一起看看下面这一串代码在内存中是如何分配的

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

首先先定制一些规则:

  1. 假设位段中的成员在内存中是从低位向高位分配
  2. 当第二个位段成员占内存二进制位比较大时,无法容纳前一个剩余位段时,直接把前一个剩余的舍弃
  3. 在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 位段跨平台问题。

  1. int位段被当成有符号数还是无符号数是不确定的
  2. 位段中最大位的数目不能确定。16位机器最大16,32位机器最大32,如果int位段成员占内存27个二进制位,那么在16位机器会出现问题
  3. 位段中的成员咋子内存中从左向右分配,还是从右向左分配标准还未确定。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余位时,是舍弃剩余的位还是利用,这也是未确定的。

总结:

跟结构体相比,位段可以达到同样的效果,但是位段可以很好的节省空间,但是有跨平台的问题存在

三、枚举

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 枚举的优点

  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符比较,枚举有类型检查,更加严谨
  3. 防止了命名污染(封装)
  4. 便于调试
  5. 使用方便,一次可以定义多个常量

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,其最小对齐数为1
  • int a自身大小是4,对齐数(成员类型大小):4,默认对齐数是(vs环境):8,最小对齐数为4
  • 通过比较它们两个的最小对齐数,发现4为最大对齐数,而最大成员大小是5,5不是4的整数倍,因此要浪费空间到,对齐到8,所以它们大小是8

4.3 联合的特点

联合的成员是共用同一块内存空间,这样一个联合变量的大小,至少是最大成员的大小。

【证明】

在这里插入图片描述

我们发现,它们3个的地址都相等,就是因为它们共用了同一块空间。
在这里插入图片描述

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

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

相关文章

顶刊MS论文解读|数据驱动的动态定价和订购策略

作者&#xff1a;白静 金凯瑞 马玺渊 钟子俊编者按 本次解读的文章为“Data-Driven Dynamic Pricing and Ordering with Perishable Inventory in a Changing Environment”&#xff0c;于2022年发表在期刊 Management Science, 作者 N. Bora Keskin, Yuexing Li, Jing-Sheng …

推荐5款极大提高工作效率的办公软件!每个都是我精挑细选的

关于提高办公效率的相关软件&#xff0c;答主要把工作5年的独家秘方都拿出来了。分享的都是用过的且体验不错的软件&#xff0c;大多数是免费软件&#xff01; 按照使用场景&#xff0c;简单先分个类&#xff0c;分别是时间利用利器&#xff0c;流程优化软件&#xff0c;办公美…

论文投稿指南——中文核心期刊推荐(工程材料学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

重生之我是赏金猎人-SRC漏洞挖掘(一)-某SRC测试系统无脑Getshell

0x01 前言 https://github.com/J0o1ey/BountyHunterInChina 欢迎大佬们点个star 0x02 资产收集到脆弱系统 在某src挖掘过程中&#xff0c;本人通过ssl证书对域名资产进行了收集&#xff0c;通过计算域名对应ip段的权重 整理出其C段资产&#xff0c;进行了批量目录扫描 查看…

Spring Boot两种格式的配置文件,你都了解吗?

目录 前言 一、properties配置文件 1.1、语法格式 1.2、读取配置文件 1.3、缺点分析 2、yml配置文件 2.1、yml语法 2.1.1、注意&#xff1a;value值的单双引号 2.2、yml配置读取 2.3、yml配置的不同数据类型 2.4、yml配置对象 2.5、yml配置集合 3、面试&#xff1a…

2023年浙江道路运输安全员考试真题题库及答案

百分百题库提供道路运输安全员考试试题、道路运输安全员考试预测题、道路运输安全员考试真题、道路运输安全员证考试题库等,提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 1.关于道路运输企业车辆技术管理机构的主要职责&#xff0c;在下列表述中最…

anaconda3文件夹被移动之后,如何操作可以复用原有conda环境

anaconda3文件夹被移动A-调整conda PATH地址B-更改.conda/environments.txt中的地址C-修改conda内的变量和每个环境的pip目录A-调整conda PATH地址 B-更改.conda/environments.txt中的地址 a. 优先切换到用户根目录 b. 查看隐藏conda目录 c. 编辑 vi .conda/environments.txt…

【服务器数据恢复】NetApp存储无法访问的数据恢复案例

服务器数据恢复环境&#xff1a; NetApp某型号存储&#xff1b; 配备SAS硬盘&#xff0c;该硬盘520字节一个扇区&#xff1b; 所有的lun映射到小型机使用&#xff0c;存放Oracle数据库文件&#xff0c;采用ASM裸设备存储方式。 服务器故障&#xff1a; 管理员误操作删除NetApp…

介电常数常用测量方法综述

张扬1&#xff0c;徐尚志1&#xff0c;赵文晖2&#xff0c;龚增2&#xff0c;赵晓群1 1同济大学&#xff0c;上海 2上海市计量测试技术研究院&#xff0c;上海 在设计电路、天线、电容器等过程中经常会涉及所用材料的介电常数, 所以深入了解介电常数的相关概念对实际工作有重…

【RuoYi-Vue-Plus】学习笔记 48 - 数据加密功能 Encrypt 源码分析

文章目录前言参考目录功能实现的准备知识1、目录结构说明2、一些准备知识2.1、自定义插件如何实现&#xff1f;2.2、Mybatis 拦截器的拦截点&#xff1f;2.3、关于 Intercepts 注解&#xff1f;2.4、关于拦截器中的 Interceptor() 方法和 plugin() 方法&#xff1f;功能调用流程…

企业数字化转型中信息化是什么

数字化转型带给企业的价值无疑是能够支撑起这股热潮的&#xff0c;这种流行趋势在数字化时代也并不意外。不过有一点值得注意&#xff0c;那就是数字化转型毕竟发展时间还不太长&#xff0c;虽然有未来加持&#xff0c;但又有多少企业能够在当下坚持数字化转型&#xff0c;顺利…

MYSQL的第四次索引视图

题目&#xff1a; 学生表&#xff1a;Student (Sno, Sname, Ssex , Sage, Sdept) 学号&#xff0c;姓名&#xff0c;性别&#xff0c;年龄&#xff0c;所在系 Sno为主键 课程表&#xff1a;Course (Cno, Cname,) 课程号&#xff0c;课程名 Cno为主键 学生选课…

[数据库迁移]-MySQL常见问题

[数据库迁移]-MySQL常见问题 森格 | 2023年2月 介绍&#xff1a;记录在MySQL数据库迁移过程中遇到的问题&#xff0c;以及解决方案。 文章目录[数据库迁移]-MySQL常见问题一、背景二、常见问题2.1 ERROR 20032.2 ERROR 12732.3 ERROR 10712.4 视图权限2.5 ERROR 1062三、总结一…

idea提交git时过滤掉不必要的文件 mes

.ignore插件的安装非常简单&#xff0c;如下图所示&#xff1a; 1.添加.gitignore 在项目上创建 在打开的面板中&#xff0c;执行如下图所示的选择&#xff1a; 3.在本地项目下文件上创建需要过滤文件后缀名 ### Example user template template ### Example user templa…

算法笔记(五)—— 二叉树

链表练习题&#xff1a; 1. 查两个链表的第一个入环结点&#xff0c;loop1,loop2。&#xff08;快慢指针&#xff0c;相交后&#xff0c;快指针放头节点&#xff09; 2. 当loop1null , loop2null时 如果两个链表最后一个结点不相等&#xff0c;那么一定不相交。 最后一个结点地…

Allegro如何设置最大撤回步数操作指导

Allegro如何设置最大撤回步数操作指导 在做PCB设计的时候,偶尔会出现误操作或者简单评估需要撤回到原来的状态,避免返工。Allegro支持撤回操作,用菜单的Undo命令即可实现 同样还支持设置最大的撤回步数,具体操作如下 点击Setup点击User Preferences

C语言(ctype.h系列的字符函数)

1.字符测试函数 函数名 如果时下列函数时&#xff0c;返回值为真 Isalnum&#xff08;&#xff09; 字母或数字 isalpha&#xff08;&#xff09; 字母 isblank&#xff08;&#xff09; 标准的空白字符&#xff08;空格&#xff0c;水平制表符或换行符&#xff09;或任…

AMD发布23.2.1 新驱动 支持开年新作《魔咒之地》

如果说2023年有什么新作&#xff0c;《魔咒之地&#xff08;Forspoken&#xff09;》当属开年大作之一&#xff0c;1月25日才在steam平台发售。虽然开售后的表现似乎不如想象中优秀&#xff0c;加之价格相对昂贵&#xff0c;令不少玩家望而却步&#xff0c;但如果只是想尝鲜&am…

面试攻略,Java 基础面试 100 问(二)

五大基本原则 单一职责原则SRP(Single Responsibility Principle) 是指一个类的功能要单一&#xff0c;不能包罗万象。如同一个人一样&#xff0c;分配的工作不能太多&#xff0c;否则一天到晚虽然忙忙碌碌的&#xff0c;但效率却高不起来。 开放封闭原则OCP(Open&#xff0…

ACM第一周---周训---题目合集.

&#x1f680;write in front&#x1f680; &#x1f4dd;个人主页&#xff1a;认真写博客的夏目浅石.CSDN &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​ &#x1f4e3;系列专栏&#xff1a;ACM周训练题目合集.CSDN &#x1f4ac;总结&#xff1a…