当前位置: 首页 > news >正文

【C语言】-自定义类型:结构体


🦆 个人主页:深邃-

❄️专栏传送门:《C语言》《数据结构》
🌟Gitee仓库:《C语言》《数据结构》


目录

  • 结构体类型的声明
    • 结构体回顾​
    • 结构的声明​
    • 结构体变量的创建和初始化
  • 结构的特殊声明
    • 匿名结构体类型
    • 结构的自引用
  • 结构体内存对齐​
    • 对齐规则
    • 为什么存在内存对齐?​
    • 修改默认对齐数
  • 结构体传参​
  • 结构体实现位段
    • 什么是位段​
    • 位段的内存分配​
    • 位段的跨平台问题​
    • 位段的应用
    • 位段使用的注意事项​
    • 位段内存计算练习

结构体类型的声明

前面我们在学习操作符的时候,已经学习了结构体的知识,这里稍微复习一下

结构体回顾​

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量

结构的声明​

structStu{charname[20];//名字intage;//年龄charsex[5];//性别charid[20];//学号};//分号不能丢

结构体变量的创建和初始化

#include<stdio.h>structStu{charname[20];//名字intage;//年龄charsex[5];//性别charid[20];//学号};intmain(){//按照结构体成员的顺序初始化structStus={"张三",20,"男","20230818001"};printf("name: %s\n",s.name);printf("age : %d\n",s.age);printf("sex : %s\n",s.sex);printf("id : %s\n",s.id);//按照指定的顺序初始化structStus2={.age=18,.name="lisi",.id="20230818002",.sex="⼥"};printf("name: %s\n",s2.name);printf("age : %d\n",s2.age);printf("sex : %s\n",s2.sex);printf("id : %s\n",s2.id);return0;}

结构的特殊声明

匿名结构体类型

在声明结构的时候,可以不完全的声明。 在声明结构的时候,可以不完全的声明。在声明结构的时候,可以不完全的声明

  • 匿名结构体类型 - 只能使用一次,后期不能使用这个类型再创建变量
  • 编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
    匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次
//匿名结构体类型 - 只能使用一次,后期不能使用这个类型再创建变量struct{inta;charb;floatc;}x,y,z;//struct{inta;charb;floatc;}*ps;intmain(){ps=&x;//编译器报错return0;}


对结构体 t y p e d e f 对结构体typedef对结构体typedef

typedefstruct{inta;charb;floatc;}S;//struct S //其实极为类似//{// int a;// char b;// float c;//};structStus5,s6;//全局变量intmain(){S s1;S s2;return0;}

结构的自引用

在结构中包含一个类型为该结构本身的成员是否可以呢? 在结构中包含一个类型为该结构本身的成员是否可以呢?在结构中包含一个类型为该结构本身的成员是否可以呢?

structNode{intdata;structNodenext;};

上述代码正确吗?如果正确,那 sizeof (struct Node) 是多少?
仔细分析,其实是不行的,因为一个结构体中再包含一个同类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的。

正确的自引用方式 : 正确的自引用方式:正确的自引用方式:

//正确的写法typedefstructNode{intdata;structNode*next;}Node;

在结构体自引用使用的过程中,夹杂了 t y p e d e f 对匿名结构体类型重命名,也容易引入问题,看看下面的代码,可行吗? 在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引入问题,看看下面的代码,可行吗?在结构体自引用使用的过程中,夹杂了typedef对匿名结构体类型重命名,也容易引入问题,看看下面的代码,可行吗?

typedefstruct{intdata;Node*next;}Node;
  • 答案是不行的,因为 Node 是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用 Node 类型来创建成员变量,这是不行的。
  • 解决方案如下:定义结构体不要使用匿名结构体了
typedefstructNode{intdata;structNode*next;}Node;

结构体内存对齐​

我们已经掌握了结构体的基本使用了

  • 现在我们深入讨论一个问题:计算结构体的大小。
  • 这也是一个特别热门的考点:结构体内存对齐

对齐规则

  1. 结构体的第一个成员对齐到和结构体变量起始位置偏移量为 0 的地址处​
  2. 其他成员变量要对齐到某个数字 (对齐数) 的整数倍的地址处。
  • 对齐数= 编译器默认的一个对齐数与该成员变量大小的较小值
  • VS 中默认的值为 8
  • Linux 中 gcc 没有默认对齐数,对齐数就是成员自身的大小​
  1. 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的) 的整数倍。
  2. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数 (含嵌套结构体中成员的对齐数—嵌套结构体的对齐数即为成员最大对其数) 的整数倍。
  3. 如果成员是数组,那么是按照数组元素类型的对齐数与编译器默认的对齐数的最小值,注意也不是整个数组,与上一个类似

为什么存在内存对齐?​

  • 平台原因 (移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  • 性能原因: 数据结构(尤其是栈)
    应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8 个字节,则地址必须是 8 的倍数。如果我们能保证将所有的 double 类型的数据的地址都对齐成 8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个 8 字节内存块中。

总体来说: 总体来说:总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
CPU 读取内存时,喜欢 “按自己的字长整块读”,不喜欢跨块读。
举个最通俗的例子:

  • 32 位 CPU:一次读 4 字节
  • 64 位 CPU:一次读 8 字节

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起:比如先小后大的写结构体成员

//例如:structS1{charc1;//1inti;//4charc2;//1};//1+3+4+1=9 -> 12structS2{charc1;charc2;inti;};//1+1+2+4=8


修改默认对齐数

#pragma 这个预处理指令,可以改变编译器的默认对齐数

//设置的一般都是2的次方数#include<stdio.h>#pragmapack(1)//设置默认对⻬数为1structS{charc1;inti;charc2;};#pragmapack()//取消设置的对⻬数,还原为默认intmain(){//输出的结果是什么?printf("%d\n",sizeof(structS));return0;}

设置的一般都是2的次方数
那我为啥不直接把对齐值全改成 1,这样最省空间?

  • 偏移对齐 = 为了 CPU 读取快、不崩溃
  • 强行改成 1 字节对齐 = 能省空间,但可能崩、变慢、不可移植

结构体传参​

structS{intdata[1000];intnum;};structSs={{1,2,3,4},1000};//结构体传参voidprint1(structSs){printf("%d\n",s.num);}//结构体地址传参voidprint2(structS*ps){printf("%d\n",ps->num);}intmain(){print1(s);//传结构体print2(&s);//传地址return0;}

上面的 print1 和 print2 函数哪个好些?
答案是:首选 print2 函数。
原因:

  • 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
  • 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

结论

  • 结构体传参的时候,要传结构体的地址。

结构体实现位段

结构体讲完就得讲讲结构体实现位段的能力

什么是位段​

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

  • 位段的成员必须是 int、unsigned int 或 signed int , 在 C99 中位段成员的类型也可以选择其他类型。
  • 位段的成员名后边有一个冒号和一个数字。
structA{int_a:2;int_b:5;int_c:10;int_d:30;};

位段的内存分配​

  • 位段的成员可以是 int unsigned int signed int 或者是 char 等类型​
  • 位段的空间上是按照需要以 4 个字节 (int) 或者 1 个字节 (char ) 的方式来开辟的。
  • 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
//⼀个例⼦structS{chara:3;charb:4;charc:5;chard:4;};structSs={0};s.a=10;s.b=12;s.c=3;s.d=4;//空间是如何开辟的?

注意:虽然结构体压栈是从高地址向低地址压栈,但是压栈后的空间,是按照先定义的成员为压栈空间中的低地址,后定义成员为高地址

  1. 一个字节(整型)的内存中,到底是从左向右使用,还是从右向左使用不确定
    假设从右向左使用,vs上正确

  2. 剩余的空间不能满足下一个成员的时候,是否浪费,不确定
    假设浪费,vs上正确

位段的跨平台问题​

  • int 位段被当成有符号数还是无符号数是不确定的。
  • 位段中最大位的数目不能确定。(16 位机器最大 16,32 位机器最大 32, 写成 27, 在 16 位机器会出问题。
  • 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
  • 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
    总结:
    跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

位段的应用

下图是网络协议中,IP 数据报的格式,我们可以看到其中很多的属性只需要几个 bit 位就能描述,这里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小一些,对网络的畅通是有帮助的。

位段使用的注意事项​

  • 位段的几个成员共有同一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的 bit 位是没有地址的。
  • 所以不能对位段的成员使用 & 操作符,这样就不能使用 scanf 直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员。
structA{int_a:2;int_b:5;int_c:10;int_d:30;};intmain(){structAsa={0};scanf("%d",&sa._b);//这是错误的//正确的⽰范intb=0;scanf("%d",&b);sa._b=b;return0;}

位段内存计算练习

structTest4{chara:3;//3bit 对齐数1intb:20;//20bit,以int为单元(4字节) 对齐数4shortc;//普通short,2字节 对齐数2chard:5;//5bit 对齐数1doublee;//普通double,8字节 对齐数8};

1.排a:3:char单元1字节,剩5bit。
2.排b:20:int单元要求4字节对齐,当前地址0x01不是4的倍数,补3字节,新开1个int单元(4字节)存b:20,剩12bit。累计:1+3+4=8字节。
3.排shortc:short要求2字节对齐,当前地址0x08是2的倍数,直接存放,占2字节->累计10字节。
4.排d:5:char单元1字节,直接存放,占1字节->累计11字节。
5.排double e:double要求8字节对齐,当前地址0x0B不是8的倍数,补5字节,存放e占8字节->累计11+5+8=24字节。
6.整体对齐:最大成员是double(8字节),24是8的倍数,无需补位。
最终大小:24字节

http://www.jsqmd.com/news/645583/

相关文章:

  • RKNN模型部署实战:对比RKNN Toolkit2与Lite2,在RK3588上如何选择与切换?
  • 多模态模型灰度发布必须绕开的7个反模式,92%团队已在第4步 silently rollback
  • 多模态健身指导不是“加摄像头+加麦克风”,而是重构感知-决策-反馈闭环:奇点大会披露的12层异构融合推理引擎架构
  • Python字体处理终极指南:fontTools库的完整实践手册
  • 2026年纸箱包装全行业深度横评:从普箱到精品礼盒,如何选择梓童包装等优质供应商 - 精选优质企业推荐榜
  • Java 的金额计算用 long 还是 BigDecimal?资深程序员这样选
  • 别再手动画了!用Python脚本5分钟搞定AutoCAD Plant 3D水平四通管件
  • 广东开窗器控制箱生产厂家哪家靠谱 - GrowthUME
  • 彩信接口文档怎么写?彩信开发教程
  • 3分钟搞定iPhone USB网络共享:Windows驱动终极解决方案 [特殊字符]
  • 【奇点大会独家剧透】:2026最硬核AI图像生成技术TOP3——仅限前200名开发者获取的SDK调用密钥已生成
  • 免费游戏光标增强工具:三步让你的鼠标在游戏中永不消失
  • 雀魂Mod Plus终极指南:免费解锁全角色皮肤的完整教程
  • 微电网(两台)主从控制孤岛-并网平滑切换的分析。 分析了: 1.孤岛下VF控制 2.并网下PQ...
  • 如何用罗技鼠标宏实现绝地求生自动压枪:3分钟快速上手终极指南
  • 基于人工势场算法实现单长机+多僚机的编队运动与避障Matlab仿真
  • 保姆级教程:用VMware和CentOS 7为你的SystemVerilog项目搭建VCS2018与Verdi调试环境
  • 2026年大连高端海鲜消费再升级:这家海景海鲜餐厅凭综合实力登上口碑榜 - GrowthUME
  • NVIDIA GB200 SuperPOD实战指南:如何快速部署你的首个AI智算中心(附避坑清单)
  • PKHeX自动合法性插件:宝可梦数据管理的终极解决方案
  • 竞赛规则已定,就不要放水了
  • 梳理头皮养护加盟推荐公司,哪个口碑好一目了然 - 工业推荐榜
  • 2026年超全整理:十大矢量图素材网站推荐与样机素材网站推荐 - 品牌2026
  • 英国金融监管机构紧急评估Anthropic AI模型安全风险
  • Linux系统Photoshop安装终极指南:如何在Linux上免费运行Photoshop CC 2022
  • 【架构实战】系统容量评估与压测工具对比
  • 搞定安卓7.0+抓包难题:雷电模拟器9.0搭配Charles证书安装到系统凭据的保姆级教程
  • 2026年韩国美容展 InterCharm Beauty Expo Korea - 中国组团单位- 新天国际会展 - 新天国际会展
  • 从自然奇观到优化利器:RIME(雾凇优化算法)核心原理与实现解析
  • JPL 公式由来