自定义类型: 结构体 (详解)
本文索引
- 一. 结构体类型的声明
- 1. 结构体的声明和初始化
- 2. 结构体的特殊声明
- 3. 结构体的自引用
- 二. 结构体内存对齐
- 1. 对齐规则
- 2. 为啥存在对齐?
- 3. 修改默认对齐值
- 三. 结构体传参
- 四. 结构体实现位段
- 1. 什么是位段?
- 2. 位段的内存分配
- 3. 位段的应用
- 4. 位段的注意事项
前言:
这篇博客将对结构体类型进行详解, 后续我还会对枚举与联合体进行详解
个人主页: 酷酷学!!!
正文开始
一. 结构体类型的声明
1. 结构体的声明和初始化
结构体是⼀些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。
struct tag
{member-list;
}variable-list;

描述一个学生:
struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
};//分号不能丢
初始化:
#include<stdio.h>
struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
};//分号不能丢int main()
{//按照结构成员的顺序初始化struct Stu s = { "zhangsan", 20 , "nan","20230818001" };printf("%s\n", s.name);printf("%d\n", s.age);printf("%s\n", s.sex);printf("%s\n", s.id);//按照指定的顺序初始化struct Stu s2 = { .age = 20,.name = "lisi",.id = "12345678",.sex = "nv" };printf("%s\n", s2.name);printf("%d\n", s2.age);printf("%s\n", s2.sex);printf("%s\n", s2.id);return 0;
}
2. 结构体的特殊声明
在声明结构体的时候,可以不完全的声明。

//匿名结构体
struct
{int a;char b;float c;
}x;struct
{int a;char b;float c;
}a[20] , *p;
上面的两个结构体在声明的时候省略掉了结构体标签(tag)
那么问题来了?
//在上⾯代码的基础上,下⾯的代码合法吗?
p = &x;
警告:
编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用⼀次。
3. 结构体的自引用
在结构体中包含⼀个类型为该结构本⾝的成员是否可以呢?
比如,定义⼀个链表的节点:
struct Node
{int data;struct Node next;
};
上述代码正确吗?如果正确,那 sizeof(struct Node) 是多少?
仔细分析,其实是不⾏的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的⼤
⼩就会⽆穷的⼤,是不合理的。
正确的⾃引⽤⽅式:
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;
二. 结构体内存对齐
我们已经掌握了结构体的基本使⽤了。
现在我们深⼊讨论⼀个问题:计算结构体的⼤⼩。
这也是⼀个特别热⻔的考点: 结构体内存对⻬
1. 对齐规则
1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。-VS 中默认的值为 8 -Linux中gcc没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤
的)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,
结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
//练习1
struct S1
{char c1;int i;char c2;
};int main()
{printf("%zd\n", sizeof(struct S1));return 0;
}
解析: 首先c1存放在偏移量为0的位置处, i的对其数为4, 需要对其到起始位置的4倍,如下图, c2对其数为1, 如下图, 然后结构体的总大小为最大对其数的整数倍,即为12个字节
画图:
//练习2
struct S2
{char c1;char c2;int i;
};int main()
{printf("%zd\n", sizeof(struct S2));return 0;
}
解析: c1对齐数1, 对齐到起始位置1倍, c2对齐数1, 对齐到起始位置1倍, i对齐数4,对齐到起始位置4倍, 整体为4的整数倍.
画图:
//练习3
struct S3
{double d;char c;int i;
};
int main()
{printf("%zd\n", sizeof(struct S3));return 0;
}
解析: d的对齐数为8, c的对齐数为1,对齐到起始位置的1倍, i 的对齐数为4, 对齐到起始位置的4倍
画图:
//练习4-结构体嵌套问题 struct S3
{double d;char c;int i;
};struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf("%zd\n", sizeof(struct S4));return 0;
}
解析: 首先, c1的对齐数为1, s3的对齐数取其成员最大对齐数为8, d对齐数为8,总大小为8的倍数
画图:
2. 为啥存在对齐?
⼤部分的参考资料都是这样说的:
-
平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。 -
性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间,如何做到:
那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间,如何做到:
//例如:
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};
S1 和 S2 类型的成员⼀模⼀样,但是 S1 和 S2 所占空间的⼤⼩有了⼀些区别。
3. 修改默认对齐值
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{//输出的结果是什么? printf("%d\n", sizeof(struct S));return 0;
}
结构体在对⻬⽅式不合适的时候,我们可以⾃⼰更改默认对⻬数。
三. 结构体传参
struct S
{int data[1000];int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{printf("%d\n", ps->num);
}
int main()
{print1(s); //传结构体 print2(&s); //传地址 return 0;
}
上⾯的 print1 和 print2 函数哪个好些?
答案是:⾸选print2函数。
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,
所以会导致性性能的下降。
结论:
结构体传参的时候,要传结构体的地址。
四. 结构体实现位段
1. 什么是位段?
位段的声明和结构体是类似的,有两个不同:
- 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以选择其他类型。
- 位段的成员名后边有⼀个冒号和⼀个数字。
⽐如:
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
A就是⼀个位段类型。
那位段A所占内存的⼤⼩是多少?
接下来我们就来了解位段的内存分配
2. 位段的内存分配
- 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
//⼀个例⼦
struct S
{char a:3;char b:4;char c:5;char d:4;
};
struct S s = {0};//如果让
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空间是如何开辟的?
但是位段存在几个跨平台的问题:
1. int位段被当成有符号数还是⽆符号数是不确定的。
2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会出问题。
(比如int位数写成int _e : 27在16位机器上是错误的,因为16位机器int占2个字节)
4. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
5. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃
剩余的位还是利⽤,这是不确定的。
跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
3. 位段的应用
下图是⽹络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要⼏个bit位就能描述,这⾥使⽤位段,能够实现想要的效果,也节省了空间,这样⽹络传输的数据报⼤⼩也会较⼩⼀些,对⽹络的畅通是有帮助的。
4. 位段的注意事项
位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。
所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员。
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{struct A sa = { 0 };scanf("%d", &sa._b);//这是错误的 //正确的⽰范 int b = 0;scanf("%d", &b);sa._b = b;return 0;
}
完
本次分享就到这里, 您的点赞收藏就是我的最大动力!
相关文章:

自定义类型: 结构体 (详解)
本文索引 一. 结构体类型的声明1. 结构体的声明和初始化2. 结构体的特殊声明3. 结构体的自引用 二. 结构体内存对齐1. 对齐规则2. 为啥存在对齐?3. 修改默认对齐值 三. 结构体传参四. 结构体实现位段1. 什么是位段?2. 位段的内存分配3. 位段的应用4. 位段的注意事项 前言:…...
设计模式(23):访问者模式
定义 表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变元素的类的前提下定义作用与这些元素的新操作。 模式动机 对于存储在一个集合中的对象,他们可能具有不同的类型(即使有一个公共的接口),对于该集合中的对象࿰…...

【C++】类和对象③(类的默认成员函数:拷贝构造函数 | 赋值运算符重载)
🔥个人主页:Forcible Bug Maker 🔥专栏:C 目录 前言 拷贝构造函数 概念 拷贝构造函数的特性及用法 赋值运算符重载 运算符重载 赋值运算符重载 结语 前言 本篇主要内容:类的6个默认成员函数中的拷贝构造函数…...

掀起区块链开发狂潮!Scaffold-eth带你一键打造震撼DApp
文章目录 前言一、Scaffold-eth是什么?二、安装和配置1.准备工作2.安装3.配置开发环境 三、进阶使用1.放入自己的合约2.部署运行 总结 前言 前面的文章传送🚪:hardhat入门 与 hardhat进阶 在之前的文章中,我们已经探讨了使用Har…...

【Qt 学习笔记】Qt常用控件 | 按钮类控件Check Box的使用及说明
博客主页:Duck Bro 博客主页系列专栏:Qt 专栏关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞👍收藏⭐评论✍ Qt常用控件 | 按钮类控件Check Box的使用及说明 文章编号:…...
android gradle 配置远程仓库
build.gradle buildscript { ext.kotlin_version "1.6.0" // 使用适合你项目的Kotlin版本 repositories { maven { url http://maven.aliyun.com/nexus/content/groups/public/ } maven { url http://maven.aliyun.com/nexus/content/repos…...
第十二章 OpenGL ES 基础-色温、色调、亮度、对比度、饱和度、高光
第十二章 OpenGL ES 基础-色温、色调、亮度、对比度、饱和度、高光 第一章 OpenGL ES 基础-屏幕、纹理、顶点坐标 第二章 OpenGL ES 基础-GLSL语法简单总结 第三章 OpenGL ES 基础-GLSL渲染纹理 第四章 OpenGL ES 基础-位移、缩放、旋转原理 第五章 OpenGL ES 基础-透视投影…...
力扣经典150题解析之二十八:盛最多水的容器
目录 力扣经典150题解析之二十八:盛最多水的容器1. 介绍2. 问题描述3. 示例4. 解题思路5. 算法实现6. 复杂度分析7. 测试与验证测试用例设计测试结果分析 8. 总结9. 参考文献感谢阅读 力扣经典150题解析之二十八:盛最多水的容器 1. 介绍 在这篇文章中&…...
Rockchip Android13 Vold(二):Framework层
目录 前言 1、接收VolumeInfo状态 2、通知VolumeInfo状态变化 3、创建StorageVolume...
Oracle数据库故障类别及日常运维规划策略
一、故障类别 1、语句故障 单个数据库操作失败(select、insert、update或delete),如: 在表中输入无效的数据,解决方法:可与用户合作来验证并更正数据;执行操作,但权限不足&#x…...

电商技术揭秘九:搜索引擎中的SEO数据分析与效果评估
相关系列文章 电商技术揭秘一:电商架构设计与核心技术 电商技术揭秘二:电商平台推荐系统的实现与优化 电商技术揭秘三:电商平台的支付与结算系统 电商技术揭秘四:电商平台的物流管理系统 电商技术揭秘五:电商平台的个性…...

多线程传参以及线程的优缺点
进程是资源分配的基本单位 线程是调度的基本单位 笼统来说,线程有以下优点: 创建一个新线程的代价要比创建一个新进程小得多 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多 线程占用的资源要比进程少很多 能充分利用多…...

keil创建单片机工程
一、创建工程 打开Keil uVision4,依次选择 Project—>New uVision4 Project,选择工程保存路径及填写工程名称,如下图 然后点“保存”。在Select a CPU Data Base File中选择"STC MCU Database",点 "OK"&am…...

QT 串口助手 学习制作记录
QT 串口助手qt 学习制作记录 参考教程:QT初体验:手把手带你写一个自己的串口助手_qt设计串口助手的流程图-CSDN博客 Qt之串口编程(添加QSerialPort模块)_如何安装 qt串口模块教程-CSDN博客 串口调试助手࿱…...
Github 2024-04-13 Rust开源项目日报Top10
根据Github Trendings的统计,今日(2024-04-13统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10CUE项目1Go项目1Tauri: 构建小型、快速和安全的桌面应用程序 创建周期:1673 天开发语言:Rust协议类型:Apache License 2.0Star数量…...

大模型日报|今日必读的10篇大模型论文
大家好,今日必读的大模型论文来啦! 1.谷歌推出新型 Transformer 架构:反馈注意力就是工作记忆 虽然 Transformer 给深度学习带来了革命性的变化,但二次注意复杂性阻碍了其处理无限长输入的能力。 谷歌研究团队提出了一种新型 T…...

深度学习 Lecture 8 决策树
一、决策树模型(Decision Tree Model) 椭圆形代表决策节点(decison nodes),矩形节点代表叶节点(leaf nodes),方向上的值代表属性的值, 构建决策树的学习过程: 第一步:决定在根节点…...
打包 docker 容器镜像到另一台电脑
# 提交容器为镜像 <container_id> 容器id my_migration_image 镜像名称 docker commit <container_id> my_migration_image # 保存镜像为tar文件 docker save my_migration_image > my_migration_image.tar 在另一台电脑上导入上面的镜像,请…...
贪心算法--购买股票
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。 在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。 返回 你能获得的 最大 利润 。 示例 1&a…...

在Mac主机上连接Linux虚拟机
前言 最近醉心于研究Linux,于是在PD上安装了一个Debian Linux虚拟机,用来练练手。但是每次在mac和Linux之间切换很是麻烦,有没有一种方法,可以在mac终端直接连接我的虚拟机,这样在mac终端上就可以直接操控我的Linux虚…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...