第20讲:自定义类型:结构体
1.结构体类型的声明1.1 结构体结构是一些值的集合这些值称为成员变量。结构的每个成员可以是不同类型的变量。结构的声明struct tag{member-list;}variable-list;// 结构体类型的声明 -- 假设描述一个学生structStu{charname[20];// 姓名intage;// 年龄charsex[5];// 性别charid[20];// 学号};// 分号不能丢结构体变量的创建和初始化注意结构体的声明之后使用前必须手动赋值否则是随机未知的值。// 结构体变量的创建structStu{charname[20];intage;charsex[5];charid[20];}s3,s4;// 方式三// 方式二全局变量structStus2;// 结构体变量的初始化intmain(){// 方式一局部变量// 初始化方式一按照结构体成员的顺序初始化structStus1{张三,20,男,20230818001};printf(name: %s\n,s1.name);// 张三printf(age: %d\n,s1.age);// 20printf(sex: %s\n,s1.age);// 男printf(id: %s\n,s1.id);// 20230818001// 初始化方式二按照指定的顺序初始化structStus5{.age18,.namelisi,.id20230818002,.sex女};printf(name: %s\n,s1.name);// lisiprintf(age: %d\n,s1.age);// 18printf(sex: %s\n,s1.age);// 女printf(id: %s\n,s1.id);// 20230818002return0;}2.结构的特殊声明在声明结构的时候可以不完全的声明。匿名结构体类型 - 只能创建一次结构体后续不能再创建结构体struct{inta;charb;floatc;}x;struct{inta;charb;floatc;}*px;警告编译器会把上面的两个声明当成完全不同的两个类型所以是非法的。匿名的结构体类型如果没有对结构体重命名的话基本上只能使用一次。可以使用typedef进行类型重定义操作typedefstruct{inta;charb;floatc;}S;intmain(){S s1{4,a,4.0};return0;}3.结构的自引用在结构体中包含一个类型为该结构体本身的成员是否可以比如定义一个链表的节点代码块 struct Node { int data; struct Node next; // 访问下一个节点 };其实这是不行的因为一个结构体中再包含一个同类型的结构体变量这样结构体变量的大小就会无穷的大是不合理的。正确的自引用示范代码块 struct Node { int data; struct Node* next; // 访问下一个节点指针 };结构体自引用使用过程中夹杂了typedef对匿名结构体类型重命名也容易引入问题比如下面代码。typedef struct { int data; Node* next; }Node;因为Node是对前面匿名结构体的重命名产生的但是在匿名结构体内部提前使用Node类型来创建成员变量这是不行的。解决办法定义结构体不要使用匿名结构体了。typedef struct Node { int data; struct Node* next; }Node; int main() { Node n1; printf(%zu\n, sizeof(struct Node)); // 16 return 0; }4.结构体内存对齐Structure Memory Alignment本节深入讨论一个问题计算结构体的大小。这是特别热门的考点结构体内存对齐。补充知识点offset()C语言标准宏 – offset of member 成员偏移量函数原型#define offsetof(type, member)((size_t)((type*)0)-member)参数type结构体类型member结构体里的成员返回值size_t类型该成员相对于结构体首地址的字节偏移量。本质是宏不是函数。包含头文件stddef.h宏和函数的核心区别本质区别宏Macro是预处理阶段的文本替身没有类型检查直接简单替换字符串。函数Function是编译阶段编译指令有函数调用栈、参数压栈、返回值有类型检查。详细对比2.1 执行阶段不同宏预处理阶段编译前直接文本替换函数编译运行阶段运行时调用2.2 参数处理不同宏无类型检查原样替换容易出现运算优先级Bug。如#define ADD(a, b) abADD(2, 3)*4 — 23*4 14函数有类型检查参数先计算值在传入2.3 开销不同宏无调用开销直接展开代码速度快、但代码冗余多处使用会复制多份函数有调用开销压栈、跳转、返回代码复用体积更小。2.4 作用域与安全宏无作用域全局生效不做参数计算会重复执行。函数参数值计算一次安全有作用域。2.5 调试宏不能断点调试展开后才是调试函数可正常断点调试4.1 对齐规则首先得掌握结构体的对齐规则结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。从第2个成员变量开始都要对齐到某个对齐数的整数倍的地址处。对齐数 编译器默认的一个对齐数 与 该成员变量大小 的较小值VS中默认的值为8Linux中gcc没有默认对齐数对齐数就是成员自身的大小结构体总大小为最大对齐数结构体中每个成员变量都有一个对齐数所有对齐数中最大的的整数倍。如果嵌套了结构体的情况嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体中成员的对齐数的整数倍。#includestddef.h#includestdio.hstructS1{// 成员变量大小 VS默认对齐数 对齐数charc1;// 1 8 1inti;// 4 8 4charc2;// 1 8 1}intmain(){// 查看结构体成员变量对齐的起始偏移量printf(%zu\n,offset(structS1,c1))// 0printf(%zu\n,offset(structS1,i))// 4printf(%zu\n,offset(structS1,c2))// 8printf(%zu\n,sizeof(structS1));// 12}分析该结构体变量的对齐数为4所以第二个变量在偏移量为4的地址处所以该变量的大小为 13(浪费)419 - 最大对齐数为4结构体总大小为4*312#includestddef.h#includestdio.hstructS2{// 成员变量大小 VS默认对齐数 对齐数charc1;// 1 8 1charc2;// 1 8 1inti;// 4 8 4};intmain(){printf(%zu\n,offsetof(structS2,c1));// 0printf(%zu\n,offsetof(structS2,c2));// 1printf(%zu\n,offsetof(structS2,i));// 4printf(%zu\n,sizeof(structS2));// 8return0;}分析c1的偏移量是0c2的偏移量是1i的对齐数是4所以偏移量是4结构体变量大小是112(浪费)48 - 最大对齐数是4结构体总大小为4*28#includestddef.h#includestdio.hstructS3{// 成员变量大小 VS默认对齐数 对齐数doubled;// 8 8 8charc;// 1 8 1inti;// 4 8 4};intmain(){printf(%zu\n,offsetof(structS3,d));// 0printf(%zu\n,offsetof(structS3,c));// 8printf(%zu\n,offsetof(structS3,i));// 12printf(%zu\n,sizeof(structS3));// 16return0;}分析第一个成员变量d的偏移量是0c的偏移量是8i的偏移量是12结构体变量大小是817(浪费)412最大对齐数是8所以结构体变量总大小是8*216#includestddef.h#includestdio.hstructS3{// 成员变量大小 VS默认对齐数 对齐数doubled;// 8 8 8charc;// 1 8 1inti;// 4 8 4};structS4{// 成员变量大小 VS默认对齐数 对齐数charc1;// 1 8 1structS3s3;// 16 8 8doubled;// 4 8 4};intmain(){printf(%zu\n,sizeof(structS3,c1));// 0printf(%zu\n,sizeof(structS3,s3));// 8printf(%zu\n,sizeof(structS3,d));// 24printf(%zu\n,sizeof(structS3));// 32return0;}分析d的偏移量是0c的偏移量是17i的偏移量是816243*8结构体变量大小是17(浪费)16832最大对齐数是8结构体总大小是324.2 为什么存在内存对齐大部分参考资料都是这样说的平台原因移植原因不是所有的硬件平台都能访问任意地址上的任意数据的某些硬件平台只能在某些地址处取某些特定类型的数据否则会抛出硬件异常。性能原因数据结构尤其是栈应该尽可能地在自然边界上对齐。原因在于为了访问未对齐的内存处理器需要作两次内存访问而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节则地址必须是8的整数倍。如果能保证将所有的double类型的数据的地址都对齐成8的整数倍那么就可以用一个内存操作来读或者写值了。否则可能需要执行两次内存访问。因为对象可能被分放在两个8字节内存块中。总体来说结构体的内存对齐是拿空间来换取时间的做法。在设计结构体的时候既要满足对齐又要节省空间如何做到让占用空间小的成员尽量集中在一起。(成员顺序可以调整的情况)4.3 修改默认对齐数#pragma 这个预处理指令可以改变编译器的默认对齐数。一般修改的对齐数都是2的次方数1、2、4、8、。。。// 设置默认对齐数为1#pragmapack(1)structS{charc1;// 1 1 1inti;// 4 1 1charc2;// 1 1 1};// 取消设置的对齐数还原为默认。#pragmapack()intmain(){printf(%zu\n,sizeof(structS));// 6return0;}5.结构体传参函数传参的时候参数是需要压栈会有时间和空间上的系统开销。如果传递一个结构体对象的时候结构体过大参数压栈的系统开销比较大所以会导致性能的下降。结论结构体传参的时候要传结构体的地址。推荐print2()structS{intdata[1000];intnum;};// 值传递 - 不推荐还要创建一次变量可能结构体过大voidprint1(structSt){for(inti0;i5;i){printf(%d ,t.data[i]);}printf(\n);printf(%d\n,t.num);}// 址传递 - 推荐不会额外开辟空间voidprint2(conststructS*ps){for(inti0;i5;i){printf(%d ,ps-data[i]);}printf(\n);printf(%d\n,ps-num);}intmain(){structSs{{1,2,3,4,5},100};print1(s);print2(s);return0;}6.结构体实现位段6.1 什么是位段位段的声明和结构体是类似的有两个不同位段的成员必须是int、unsigned int或signed int在C99中位段成员的类型也可以选择其他整形家族类型比如char。位段的成员名后边有一个冒号和数字。语法strcut 位段名 { 类型 成员名 : 占用位数; // 核心冒号数字指定位数 };注意占用位数不能超过类型本身的位数如int最多32位structB{int_a;// 32bitint_b;// 32bitint_c;// 32bitint_d;// 32bit};structA{int_a:2;// 1字节int_b:5;// 1字节int_c:10;// 2字节int_d:30;// 4字节};// 25103047bitintmain(){printf(%zu\n,sizeof(structB));// 16printf(%zu\n,sizeof(structA));// 8return0;}6.2 位段的内存分配位段的成员可以是int、unsigned int、signed int或者char等所有整型类型。位段的空间上是按照需要以4字节int或者1个字节char的方式来开辟。位段涉及很多不稳定因素位段是不跨平台的注意可移植的程序应该避免使用位段。vs环境下每个字节分配的内存空间是从右向左使用的低位向高位剩余的空间不够下一个成员使用的时候直接浪费。6.3 位段的跨平台问题int位段被当成有符号数还是无符号数是不确定的。位段中最大的数目不能确定早期16位机器int最大16,32位机器最大32写成27在16为机器会出问题。位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义。当一个结构包含两个位段第二个位段成员比较大无法容纳于第一个位段剩余的位时是舍弃剩余的位还是利用这是不确定的。总结跟结构相比位段可以达到同样的效果并且可以很好的节省空间。但是有跨平台的问题存在。structS{chara:3;charb:4;charc:5;chard:4;};/* 解析内存中每个字节中假设从右向左使用 b a c d 0 1100 010 |000 0 0011 |0000 0100 内存中是 6 2 0 3 0 4 小端存储为62 03 04占用3个字节 */intmain(){structSs{0};s.a10;// 0000 1010s.b12;// 0000 1100s.c3;// 0000 0011s.d4;// 0000 0100return0;}6.4 位段的应用网络协议IP数据报的格式很多的属性只需要几个bit位就能描述这里使用位段能够实现想要的效果也节省了空间这样网络传输的数据报大小也会较小一些对网络的畅通是有帮助的。6.5 位段使用的注意事项位段的几个成员共用同一个字节这样有些成员的起始位置并不是某个字节的起始位置那么这些位置处是没有地址的。内存中每个字节分配一个地址一个字节内部的bit位是没有地址的。所以不能对位段的成员使用操作符这样就不能scanf直接给位段的成员输入值只能是先输入放在一个变量中然后赋值给位段的成员。structC{int_a:2;int_b:5;int_c:10;int_d:30;};intmain(){structCsa{0};//scanf(%d, sa._a); // err// 正确的示范intb0;scanf(%d,b);sa._bb;return0;}7.练习练习1变种水仙花数变种水仙花数 - Lily Number把任意的数字从中间拆分成两个数字比如1461可以拆分成1和461,14和61,146和1),如果所有拆分后的乘积之和等于自身则是一个Lily Number。例如655 6 * 55 65 * 51461 1461 1461 146*1求出5位数中的所有 Lily Number。输入描述无输出描述一行5位数中的所有 Lily Number每两个数之间间隔一个空格。#includemath.h#includestdio.hintmain(){// 1. 遍历五位数的整数for(inti10000;i99999;i){intret0;// 保存加起来的和intexp1;// 10的指数inta0// 拆分五位数的前半部分intb0;// 拆分五位数的后半部分while(exp5){ai/pow(10,exp);bi%pow(10.exp);reta*b;exp;}// 判断是否是变种水仙花数将其打印出来if(reti){printf(%d ,i);}}return0;}// 方式二intmain(){for(inti10000;i99999;i){intsum0;for(intj10;j10000;j*10){sumsum(i/j)*(i%j);}if(sumi){printf(%d ,i);}}}2.练习2序列中去除指定数字描述有一个整数序列可能有重复的整数现删除指定的某一个整数输出删除指定数字之后的序列序列中未被删除数字的前后位置没有发生改变。若序列中有多个指定的数需要一起删除。数据范围序列长度和序列中的值都满足1≤n≤50输入描述第一行输入一个整数(0≤N≤50)。第二行输入N个整数输入用空格分隔的N个整数。第三行输入想要进行删除的一个整数。输出描述输出为一行删除指定数字之后的序列。intmain(){// 1.输入整数intN;scanf(%d,N);// 2.输入N个整数intarr[N];// 可变数组VS不支持可以改成int arr[50] { 0 };for(inti0;iN;i){scanf(%d,arr[i]);}// 3.输入需要删除的数字intdel;scanf(%d,del);intj0;for(inti0;iN;i){if(arr[i]!del){arr[j]arr[i];j;}}return0;}intmain(){// 1.输入整数intN;scanf(%d,N);// 2.输入N个整数intarr[N];// 可变数组VS不支持可以改成int arr[50] { 0 };for(inti0;iN;i){scanf(%d,arr[i]);}// 3.输入需要删除的数字intdel;scanf(%d,del);for(inti0;iN;i){if(arr[i]del){// 将后面的序列全部向前移一位for(intki;kN-1;k){arr[k]arr[k1];}N--;i--;}}return0;}莫因表格遮前路代码长明赴山海

相关新闻

Linux栈溢出漏洞原理与实践:从内存布局到控制流劫持

Linux栈溢出漏洞原理与实践:从内存布局到控制流劫持

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 这次我们来看一个Linux二进制安全领域的核心基础:栈溢出漏洞。对于想入门二进制漏洞挖掘与利用(Pwn&#xff0…

2026/7/4 20:00:27阅读更多 →
深度解析:如何高效提取Wallpaper Engine资源文件与TEX纹理转换

深度解析:如何高效提取Wallpaper Engine资源文件与TEX纹理转换

深度解析:如何高效提取Wallpaper Engine资源文件与TEX纹理转换 【免费下载链接】repkg Wallpaper engine PKG extractor/TEX to image converter 项目地址: https://gitcode.com/gh_mirrors/re/repkg RePKG是一款专业的Wallpaper Engine资源提取与TEX纹理格式…

2026/7/4 20:00:27阅读更多 →
解密Lua字节码:luadec51深度解析与实战应用

解密Lua字节码:luadec51深度解析与实战应用

解密Lua字节码:luadec51深度解析与实战应用 【免费下载链接】luadec51 Lua Decompiler for Lua version 5.1 项目地址: https://gitcode.com/gh_mirrors/lu/luadec51 你是否曾面对编译后的Lua字节码文件感到束手无策?luadec51作为Lua 5.1版本的专…

2026/7/4 20:00:27阅读更多 →
LiveViewJS vs 传统SPA:为什么服务器端渲染实时应用是未来趋势

LiveViewJS vs 传统SPA:为什么服务器端渲染实时应用是未来趋势

LiveViewJS vs 传统SPA:为什么服务器端渲染实时应用是未来趋势 【免费下载链接】liveviewjs LiveView-based library for reactive app development in NodeJS and Deno 项目地址: https://gitcode.com/gh_mirrors/li/liveviewjs 在现代Web开发中&#xff0c…

2026/7/4 21:20:47阅读更多 →
OpenCPU核心功能解析:HTTP API如何赋能R语言远程计算

OpenCPU核心功能解析:HTTP API如何赋能R语言远程计算

OpenCPU核心功能解析:HTTP API如何赋能R语言远程计算 【免费下载链接】opencpu OpenCPU system for embedded scientific computation and reproducible research 项目地址: https://gitcode.com/gh_mirrors/op/opencpu OpenCPU是一个专为嵌入式科学计算和可…

2026/7/4 21:20:47阅读更多 →
从0到1开发OpenCPU Web应用:基于R语言的交互式科研工具

从0到1开发OpenCPU Web应用:基于R语言的交互式科研工具

从0到1开发OpenCPU Web应用:基于R语言的交互式科研工具 【免费下载链接】opencpu OpenCPU system for embedded scientific computation and reproducible research 项目地址: https://gitcode.com/gh_mirrors/op/opencpu OpenCPU是一个基于R语言的嵌入式科学…

2026/7/4 21:20:47阅读更多 →
【信息科学与工程学】【制造工程】第三十八篇 制造工艺中的制造数学01

【信息科学与工程学】【制造工程】第三十八篇 制造工艺中的制造数学01

📊 表一:TSMC 逻辑工艺主线(从成熟到先进) 数据源自 TSMC 官网 Logic Technology 页,配量产年与架构。 节点 官方代号 架构 风险/量产年 关键说明 3 m — 平面 CMOS 1987(创始节点) TSMC 起家节点 0.18 m 0.18m 平面 1999 前后 成熟长青节点 0.13 m 0.1…

2026/7/4 21:20:47阅读更多 →
Attributed框架社区贡献指南:如何参与开源开发

Attributed框架社区贡献指南:如何参与开源开发

Attributed框架社区贡献指南:如何参与开源开发 【免费下载链接】Attributed framework for Attributed strings. 项目地址: https://gitcode.com/gh_mirrors/at/Attributed 欢迎来到Attributed框架的社区贡献指南!🎉 如果你是Swift开发…

2026/7/4 21:20:47阅读更多 →
FluidNet:革命性AI流体模拟加速器 - 用卷积神经网络加速欧拉流体计算

FluidNet:革命性AI流体模拟加速器 - 用卷积神经网络加速欧拉流体计算

FluidNet:革命性AI流体模拟加速器 - 用卷积神经网络加速欧拉流体计算 【免费下载链接】FluidNet Accelerating Eulerian Fluid Simulation With Convolutional Networks 项目地址: https://gitcode.com/gh_mirrors/fl/FluidNet FluidNet是一款基于卷积神经网…

2026/7/4 21:15:47阅读更多 →
AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

6个月前的2025年12月,Boris Cherny 公开宣布自己卸载了 IDE。一时间,Vibe Coding 成了全行业最热的话题。6个月后,当我们回过头来拉一份真实账本,发现事情远没有"一句话生成一个App"那么浪漫。本文从产品经理和研发两个…

2026/7/4 14:25:39阅读更多 →
审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

引言:审计结束三个月了,审计员的权限还没关某城商行每年按照监管要求开展至少一次数据安全审计。审计期间,内审部门需要抽样检查各类业务数据——交易流水、客户信息、员工操作日志、权限配置记录。这些数据分布在不同系统中,审计…

2026/7/4 14:57:00阅读更多 →
端到端自动驾驶:从GTC‘26看工程可信落地的核心逻辑

端到端自动驾驶:从GTC‘26看工程可信落地的核心逻辑

1. 项目概述:当算法工程师走进GTC26展厅,看到的不是芯片,而是“端到端”的呼吸节奏“端到端”这三个字,在GTC’26现场出现的频率,高得像NVLink带宽测试时的峰值曲线——它不再是一个论文里的技术路径选项,而…

2026/7/4 0:02:48阅读更多 →
缺牙修复科普:常见义齿类型与选择参考

缺牙修复科普:常见义齿类型与选择参考

缺牙修复科普:常见义齿类型与选择参考牙齿缺失是中老年人群中较为常见的口腔问题,不仅会造成咀嚼不便、进食受影响,长期还可能对营养摄入与日常社交带来困扰。义齿是改善缺牙问题的常用方式,目前市面上的义齿种类较多,…

2026/7/4 0:02:48阅读更多 →
STM32F091RC与LTC6904实现高精度方波信号生成

STM32F091RC与LTC6904实现高精度方波信号生成

1. 项目概述:LTC6904与STM32F091RC的精准方波生成方案在嵌入式系统开发中,精确的时钟信号和定时控制往往是项目成败的关键。LTC6904作为一款低功耗、高精度的可编程振荡器芯片,与STM32F091RC这款ARM Cortex-M0内核微控制器的组合,…

2026/7/4 0:02:48阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/4 1:16:56阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/4 2:33:55阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/4 2:33:55阅读更多 →