C语言进阶——自定义类型:结构体

🌇个人主页:_麦麦_
📚今日名言:生活不可能像你想象的那么好,也不会像你想象的那么糟。——莫泊桑《羊脂球》

目录
一、前言
二、正文
1结构体
1.1结构体的基础知识
1.2结构的声明
1.3特殊的声明
1.4结构体变量的定义和初始化
1.5结构的自引用
1.6结构体内存对齐
1.7修改默认对齐数
1.8结构体传参
2.位段
2.1什么是位段
2.2位段的内存分配
2.3位段的跨平台问题
2.4位段的应用
三、结语
一、前言
好久不见,今天为小伙伴们带来C语言中有关结构体的详细知识,干货满满,图文并茂一定要看到底哦!
二、正文
1结构体
1.1结构体的基础知识
结构是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量
1.2结构的声明
struct tag
{
member—list;
} ;
注:分号不能丢
//结构的声明演示:描述一个学生
struct Stu
{char name[20]; //名字char sex[5]; //性别int age; //年龄char id[20]; //学号
};
1.3特殊的声明
在声明结构的时候,可以不完全声明【匿名结构体类型】,由于匿名,所以声明后就得在后面直接创建变量。
//匿名结构体类型
struct
{int a;char b;float c;
}x; //创建变量x
注:哪怕两个匿名结构体中的内容完全一样也会被编译器当成两个完全不同的类型
//匿名结构体类型1
struct
{int a;char b;float c;
}x; //匿名结构体类型2
struct
{int a;char b;float c;
}*p;//非法操作
p = &x;
1.4结构体变量的定义和初始化
结构体变量的定义共分为三类:
①全局变量定义
②局部变量定义
③在结构的声明的同时定义变量
//结构体变量的三种定义方法
struct Stu
{char name[20]; //名字char sex[5]; //性别int age; //年龄char id[20]; //学号
}Stu1; //声明结构的同时定义变量struct Stu Stu2; //全局变量定义int main()
{struct Stu Stu3; //局部变量定义return 0;
}
结构体变量的初始化即定义变量的同时赋初值。不过结构体的初始化也分为正常的初始化和嵌套初始化。
//结构体变量的初始化
struct Peo
{char name[20]; //名字char sex[5]; //性别int age; //年龄
};struct Peo Peo1 = { "陈书婷","女",35}; //结构体正常初始化struct Node
{char movie[20];struct Peo p;
}Peo2 = { "狂飙",{"高志强","男",40 }}; //结构体嵌套初始化struct Node Peo3 = { "狂飙",{"高启盛","男","28"}}; //结构体嵌套初始化
注:结构体的初始化其实可以更加灵活——乱序,按照自己想法来初始化。
struct Peo
{char name[20]; //名字char sex[5]; //性别int age; //年龄
};struct Peo Peo4 = { .sex = "男",.name = "安欣",.age = 30 };
1.5结构的自引用
采取指针的形式
在结构中包含一个类型为该结构本身的成员
//自引用1(错误示范)
struct Node
{int date;struct Node next;
};//自引用2(正确示范)
struct Node
{int date;struct Node* next;
};
1.6结构体内存对齐
在小伙伴们掌握了结构体的基本使用之后,接下来让我们深入讨论一个问题:如何计算结构体的大小?为了解决这个问题,我们就必须掌握结构体的对齐规则。
● 第一个成员在与结构体变量偏移量为0的地址处
●其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数=编译器默认的一个对齐数与该成员大小的较小值
★VS中默认的值为8
●结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
●如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
#include<stdio.h>
//结构体内存对齐代码1
struct s1
{double d; //对齐数8char c; //对齐数1int i; //对齐数4
};//结构体内存对齐代码2
struct s2
{char c1; //对齐数1struct s1 s1; //对齐数8double d; //对齐数8
};int main()
{printf("%d", sizeof(struct s1)); //16printf("%d", sizeof(struct s2)); //32return 0;
}

结构体内存对齐代码1:首先我们将"d"成员放入内存中,由于它是第一个成员,且对齐数为8,所以从偏移量为0的地址一直放到偏移量为7的地址。其次是"c"成员,它的对齐数为1,因此可以放在偏移量为8的地址处,最后是"i"成员,它的对齐数是4,但是偏移量为9的地址并不是对齐数4的倍数,所以我们只好跳到偏移量为12的地址处,直至偏移量为15的地址处才放下"i"成员。在将所有的成员放入后,就是计算结构体s1的大小,由于15并不是最大对齐数8的倍数,所以结构体s1的大小为16个字节。
结构体内存对齐代码2:首先我们将"c1"成员放入内存中,由于它是第一个成员,且对齐数为1,所以放入偏移量为0的地址处。其次是"s1"成员,该嵌套结构体内的最大对齐数为8,因此跳到偏移量为8的地址处开始存放double类型成员,直至偏移量为15的地址处才存放完毕。接着是char类型成员,对齐数为1,存放在偏移量为16的地址处。继而是整型成员,对齐数为4,从偏移量为20的地址存放,直至偏移量为23的地址处存放完毕。最后是"d"成员,对齐数为8,从偏移量为24的地址存放,直至偏移量为31的地址处存放结束。在将所有的成员放入后,就是计算结构体s2的大小,由于31并不是最大对齐数8的倍数,所以结构体s2的大小为32个字节
最后总结一下计算结构体大小的步骤:
①计算出所有成员的对齐数并得出结构体的最大对齐数
②根据每个成员的对齐数依次存放每个成员
③所有成员存放完毕后,依据结构体的最大对齐数得出结构体大小
那么如何证明我们对结构体成员的偏移量计算是否正确呢?C语言中提供了一个宏来计算结构体成员的偏移量?(无需掌握,只是证明我们上述计算的思路无误)
#include<stddef.h>
int main()
{printf("%d\n", offsetof(struct s1,d));printf("%d\n", offsetof(struct s1,c));printf("%d\n", offsetof(struct s1,i));printf("%d\n", offsetof(struct s2, c1));printf("%d\n", offsetof(struct s2, s1.d));printf("%d\n", offsetof(struct s2, s1.c));printf("%d\n", offsetof(struct s2, s1.i));printf("%d\n", offsetof(struct s2, d));return 0;
}

在了解完结构体的内存,可能有的小伙伴会发出如下的疑问:为什么会存在内存对齐呢,这到底有什么用呢?
大部分的参考资料都是如是说的:
1.平台原因(移植原因):
不是所有的硬件平台都能任意访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些 特定类型的数据,否则就会抛出硬件异常
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总的来说:结构体的内存对齐是拿空间来换取时间的做法
那么在设计结构体的时候,我们既要满足对齐,又要节省空间,该如何做呢?
让占用空间小的成员尽量集中在一起
1.7修改默认对齐数
那么默认对齐数可以修改吗?答案是肯定的。在C语言中存在#pragma这个预处理指令,通过这个我们就可以改变默认对齐数了。
//修改默认对齐数
#include <stdio.h>#pragma pack(2)
struct s1
{char c1;int i;char c2;
};

注:如果将默认对齐数设置为1,则不存在对齐效果。在平常的使用中,小伙伴们一定要根据实际需求修改默认对齐数
1.8结构体传参
在之前的指针学习中我们了解到了"传值调用"和"传址调用"这两个概念,那么在结构体传参时依旧存在以上两种方式,那么那种方式是最优选择呢?
//结构体传参
#include <stdio.h>struct s
{int date[1000];int num;
};struct s s1 = { {1,2,3,4},666 };void print1(struct s s1)
{printf("%d",s1.num);
}void print2(struct s* s1)
{printf("%d", s1->num);
}
int main()
{print1(s1); //传结构体print2(&s1); //传地址
}
其实,在结构体传参这一步中传结构体的地址是一个更好的选择。因为函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。
2.位段
在结构体讲完之后就要向小伙伴们介绍结构体实现位段的能力
2.1什么是位段
位段的声明和结构是类似的,却能更加的节省空间,但是有两个地方存在差异:
①位段的成员必须是int、unsigned int 或 signed int
②位段的成员名后边有一个冒号和数字【表示占几个二进制位】
#include <stdio.h>
struct A
{int _a : 2; //a只占2个二进制位int _b : 5; //b只占5个二进制位int _c : 10; //c只占10个二进制位int _d : 30; //d只占30个进制位
};int main()
{printf("%d\n", sizeof(struct A)); //打印为8个字节,是不是更节省空间了呢return 0;
}
2.2位段的内存分配
1.位段的成员可以是int 、unsigned int、signed int、或者是char(属于整形家族)类型
2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的
3.位段设计很多不确定因素,位段是不跨平台的,注意可移植的程序应该避免位段
4.位段是不存在内存对齐的
注:在不同的编译器中,同一位段的大小也是不确定的,接下来我们以VS2019的环境下解释上述位段的大小为何为8个字节。

在理解完位段在VS下的内存开辟,那么内存是如何使用的呢?在VS的环境下,内存开辟后是从右向左使用的且为小端存储,依旧以上面的代码为例:


2.3位段的跨平台问题
①int位段被当成有符号数还是无符号数是不确定的
②位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)
③位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
④当一个结构包含两个位段,第二个位段成员比较大,无法容纳与第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
总结:跟结构相比,位段可以达到同样的效果,但是可以很好地节省空间,但是有跨平台的问题存在
2.4位段的应用
大家都知道彼此之间相互的交流看似简单,其实文字交流的背后是大量的网络数据,而位段的使用恰好可以对数据进行压缩,减少网络的压力和负担。形象的来说,我们可以把网络想象成高速公路,如果上面全是未经压缩的数据,也就都是大卡车的话,就会十分拥挤。而如果采取位段的方式,对没有必要使用的空间进行压缩,就可以将大卡车变成小轿车,从而缓解交通压力。
三、结语
关于结构体的讲解就已经全部结束了,下期我们会继续分享自定义类型的其他成员!
关注我 _麦麦_分享更多干货:_麦麦_的博客_CSDN博客-领域博主
大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下期见!
相关文章:
C语言进阶——自定义类型:结构体
🌇个人主页:_麦麦_ 📚今日名言:生活不可能像你想象的那么好,也不会像你想象的那么糟。——莫泊桑《羊脂球》 目录 一、前言 二、正文 1结构体 1.1结构体的基础知识 1.2结构的声明 1.3特殊的声明 1.4结构体变量的…...
SpringSecurity学习笔记01
目录 一、课程介绍 二、框架概述 三、入门案例 四、基本原理(过滤器链) 五、基本原理(过滤器加载过程) 六、基本原理(两个重要的接口) 七、web权限方案-用户认证(设置用户名密码上) 八、…...
Python语言零基础入门教程(十一)
Python 列表(List) 序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。 Python有6个序列的内置类型,但最常见的是列表和元组。 序列都可以…...
现货白银基础知识
任何活动,任何项目,任何工作都离不开基础知识,这是肯定的。万丈高楼平地起,要想要简称百层高楼,首先得把低级打好!现货白银投资也是一样的道理,现在我们就来一起聊聊现货白银基础知识的问题&…...
数据库原理及应用基础知识点
数据库原理基础知识点大全数据库原理及应用1、数据库系统概述1.1 基本概念1.2 数据模型1.3 数据库系统的结构2、实体 -- 联系模型2.1 基本概念2.2 实体-联系图2.3 弱实体集3、关系数据模型3.1 关系数据库的结构3.2 从ER模型到关系模型3.3 关系操作、完整性约束、关系代数4、关系…...
【数据结构】栈(stack)
写在前面本篇文章开始讲解栈的有关知识,其实把顺序表和链表学好,那么这一章便不在话下,栈实际上就是顺序表或链表的一些特殊情况。用顺序表实现的栈叫做顺序栈用链表实现的栈叫做链栈文章的内容分为几个部分,希望读者能快速了解文…...
初识shell
文章目录一、shell基本知识1.1为什么学习和使用Shell编程1.2 什么是Shell1.2.1 shell的起源1.2.2 shell的功能1.3 shell的分类1.4 作为程序设计的语言——shell1.5 如何学好shell1.6 shell脚本的基本元素1.7 shell脚本编写规范1.8shell脚本的执行方式1.9 执行脚本的方法1.10 sh…...
程序员如何编写好开发技术文档 如何编写优质的API文档工作
编写技术文档,是令众多开发者望而生畏的任务之一。它本身是一件费时费力才能做好的工作。可是大多数时候,人们却总是想抄抄捷径,这样做的结果往往非常令人遗憾的,因为优质的技术文档是决定你的项目是否引人关注的重要因素。无论开…...
二级C语言操作例题(四十)
一、程序填空题 在此程序中,函数fun的功能是:在形参s所指字符串中寻找与参数c相同的字符,并在其后插入一个与之相同的字符,若找不到相同的字符则不做任何处理。 例如,若s所指字符串”baacda”,中c的字符为…...
vue-router 源码解析(二)-创建路由匹配对象
文章目录基本使用导语createRouterMatcher 创建匹配路由记录addRoute 递归添加matchercreateRouteRecordMatcher 创建matchertokenizePath 解析pathtokensToParser 记录打分insertMatcher 将matcher排序总结基本使用 const routes [{path:"/",component: Demo2,nam…...
分布式新闻项目实战 - 10.Long类型精度丢失问题
怒发冲冠,凭阑处、潇潇雨歇。抬望眼,仰天长啸,壮怀激烈。三十功名尘与土,八千里路云和月。莫等闲、白了少年头,空悲切。 靖康耻,犹未雪。臣子恨,何时灭。驾长车,踏破贺兰山缺。壮志饥…...
如何将本地jar包安装到maven仓库
mvn install:install-file:主要是将本地自定义jar安装到maven仓库,然后在pom中可以直接通过dependency的方式来引用。 此命令有如参数: 命令说明-DgroupId自定义groupId设置groupId 名-DartifactId自定义artifactId设置该包artifactId名-Dversion自定义…...
C++:map和set的认识和简单使用/关联式容器
关联式容器 关联式容器即是用来存储数据的,并且存储的是<Key,Value>结构的键值对,在数据检索时效率比序列式容器高。 序列式容器也就是vector、list、queue等容器,因为其底层为线性序列的数据结构,里面存储的是…...
网络工程师一定要学会的知识点:OSPF,今天给大家详细介绍
1. OSPF 概念OSPF(Open Shortest Path First 开放式最短路径优先)是一种动态路由协议,属于内部网关协议(Interior Gateway Protocol,简称 IGP),是基于链路状态算法的路由协议。2. OSPF 的运行原理(1)OSPF 的…...
企业管理的三大基石及其关系
企业管理的三大基石三大基石是什么三大基石的关系制度:管理:文化:三大基石是什么 一个企业,不管它是属于哪种类型,影响员工行为的都有三种力量——制度、管理和文化,这是管理的三大基石。 三大基石的关系 …...
6个月软件测试培训出来后的感悟 —— 写给正在迷茫是否要转行或去学软件测试的学弟们
本人刚从某培训机构学习结束,现在已经上班一个月了。这篇文章我不会说太多的知识点,或噱人去培训机构学习的话语,仅作为一个普通打工者的身份,来写给那些对于软件测试未来发展、薪资待遇等不清楚的正在为家庭,解决信用…...
IoU Loss综述(IOU,GIOU,CIOU,EIOU,SIOU,WIOU)
边界框回归(BBR)的损失函数对于目标检测至关重要。它的良好定义将为模型带来显著的性能改进。大多数现有的工作假设训练数据中的样本是高质量的,并侧重于增强BBR损失的拟合能力。 一、L2-norm 最初的基于回归的BBR损失定义为L2-norm…...
Node=>Express中间件 学习3
1.概念: 例:在处理污水的时候,一般都要经过三个处理环节,从而保证处理过后的废水,达到排放标准 处理污水的这三个中间处理环节,就可以叫中间件 2.中间件调用流程 当一个请求到达Express的服务器之后&#x…...
【STM32笔记】HAL库UART串口配置及重定向(解决接收中断与scanf不能同时工作的问题)
【STM32笔记】HAL库UART串口配置及重定向(解决接收中断与scanf不能同时工作的问题) 首先 要使用printf和scanf 必不可少的就是 #include <stdio.h>这里需要做的就是配置单片机的UART 并且使其能够被printf和scanf调用 打开异步工作模式 并且选择…...
【前端CSS面试题】2023前端最新版css模块,高频15问
🥳博 主:初映CY的前说(前端领域) 🌞个人信条:想要变成得到,中间还有做到! 🤘本文核心:博主收集的CSS面试题 目录 一、CSS必备面试题 1.CSS3新特性 2.CSS实现元素两个盒子垂…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
uni-app学习笔记三十五--扩展组件的安装和使用
由于内置组件不能满足日常开发需要,uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件,需要安装才能使用。 一、安装扩展插件 安装方法: 1.访问uniapp官方文档组件部分:组件使用的入门教程 | uni-app官网 点击左侧…...
【若依】框架项目部署笔记
参考【SpringBoot】【Vue】项目部署_no main manifest attribute, in springboot-0.0.1-sn-CSDN博客 多一个redis安装 准备工作: 压缩包下载:http://download.redis.io/releases 1. 上传压缩包,并进入压缩包所在目录,解压到目标…...
Qwen系列之Qwen3解读:最强开源模型的细节拆解
文章目录 1.1分钟快览2.模型架构2.1.Dense模型2.2.MoE模型 3.预训练阶段3.1.数据3.2.训练3.3.评估 4.后训练阶段S1: 长链思维冷启动S2: 推理强化学习S3: 思考模式融合S4: 通用强化学习 5.全家桶中的小模型训练评估评估数据集评估细节评估效果弱智评估和民间Arena 分析展望 如果…...
五、jmeter脚本参数化
目录 1、脚本参数化 1.1 用户定义的变量 1.1.1 添加及引用方式 1.1.2 测试得出用户定义变量的特点 1.2 用户参数 1.2.1 概念 1.2.2 位置不同效果不同 1.2.3、用户参数的勾选框 - 每次迭代更新一次 总结用户定义的变量、用户参数 1.3 csv数据文件参数化 1、脚本参数化 …...
python读取SQLite表个并生成pdf文件
代码用于创建含50列的SQLite数据库并插入500行随机浮点数据,随后读取数据,通过ReportLab生成横向PDF表格,包含格式化(两位小数)及表头、网格线等美观样式。 # 导入所需库 import sqlite3 # 用于操作…...
Oracle实用参考(13)——Oracle for Linux物理DG环境搭建(2)
13.2. Oracle for Linux物理DG环境搭建 Oracle 数据库的DataGuard技术方案,业界也称为DG,其在数据库高可用、容灾及负载分离等方面,都有着非常广泛的应用,对此,前面相关章节已做过较为详尽的讲解,此处不再赘述。 需要说明的是, DG方案又分为物理DG和逻辑DG,两者的搭建…...
华为OD机考- 简单的自动曝光/平均像素
import java.util.Arrays; import java.util.Scanner;public class DemoTest4 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint[] arr Array…...

