【C语言】自定义类型之---结构体超详解(结构体的定义使用、指针结构体,内存对齐,......代码详解)
目录
前言:
一:结构体
1.1:什么是结构体?
1.2:结构体类型的声明
1.3:结构体变量的定义
1.4:结构体的内存对齐
1.5:结构体传参
二:位段
2.1:位段是什么?
2.2:位段的内存分配
2.3:位段在vs编译器上内存的分配和使用
前言:
今天分享的内容是C语言中自定义类型之一的结构体。在C语言中我们知道有很多种数据类型,如 int ,char,float 等,但是我们处于社会中,那么社会中的东西能用数据来表示吗?比如,一本书能用 int 或者 char 类型所表示吗?答案是不能的。因为一本书既包含有书名,还包含有作者名,单价和出版社等信息,那么此时单纯的 int ,char 等数据类型就行不通了,这时就得根据自己的需要来自定义一种结构体来描述这本书,其中以书名,单价,出版社等表示结构体的成员列表。可见自定义结构体给我们很大的遐想空间,表述万事万物。接下来我们一起看看结构体有哪些独特的魅力。
一:结构体
1.1:什么是结构体?
结构就是一些值的集合,这些被称为成员变量。并且结构的每个成员可以是不同类型的变量。
1.2:结构体类型的声明
声明:
struct tag // struct(关键字),tag是自定义事物的名称
{mem_list; // 成员列表,可以一个/多个
}variable_list; // 变量列表
1.2.1:普通声明
例如描述一个学生:
//结构体定义一个学生
struct Student
{char name[20]; // 学生姓名char sid[20]; // 学生学号char sex[5]; // 学生性别int age; // 学生年龄
}stu1,stu2; //注意有分号,此时创建了两个struct Student类型的变量stu1和stu2.
// stu1 和 stu2 是全局变量int main()
{struct Student stu3,stu4; //创建了两个struct Student类型的全局变量。reutrn 0;
}
此处的 stu1和stu2是全局变量,是在声明结构体的时候顺带创建的,当然也可以不顺带创建。
//结构体定义一个学生
struct Student
{char name[20]; // 学生姓名char sid[20]; // 学生学号char sex[5]; // 学生性别int age; // 学生年龄
};
1.2.2:特殊声明
在声明结构体的时候,可以不完全的声明,被称为匿名结构体类型。
例如:
// 匿名结构体
struct
{char name[20];int age;
}x; //注意:在创建匿名结构体变量的时候,只能在这里创建(x)int main()
{return 0;
}
但是这种结构在声明的时候已经省略了结构体标签(tag),这种结构体变量只能在定义的时候创建,并且这种结构体类型只能在声明的时候用一次(只能声明一次)。
为什么只能用一次?
// 结构体1
struct
{int a;char b;double c;
}x;// 结构体2
struct
{int a;char b;double c;
}*p;int main()
{p = &x;return 0;
}
发现运行报错:
发现 结构体指针变量p与&结构体x的类型不匹配。
我们知道:
int a = 10;
int* p = &a;
所以 结构体1和结构体2的类型是不同的(即类型不匹配),故此这种匿名结构体在程序中只能使用一次。
1.2.3:结构体的自应用
在结构中包含一个类型为该结构本身的成员是否可以呢?就如同数据结构之中的链表一样:
struct Node
{int data;struct Node next;
};
不过这样的结构体形式是否正确呢?答案是错误,这是因为在这一结构体中包含有一个结构体,这样的结构体在实现的时候会一直进行下去,永远没有尽头,也就是说它只有进没有出结构体的条件。那该怎样实现结构体的自引用呢?
正确形式:
struct Node
{int data; // 数据域struct Node* next; // 指针域,声明一个同类型的指针
};
就是说:将一个结构体通过其内的结构体指针与另一个同类型的结构体相连接起来,之后继续连,连,连。即将多个同类型的结构体通过内部的结构体指针(next)相连接起来就是结构体的自引用。
1.2.4:结构体的重命名
重命名的关键字:typedef
我们发现结构体的类型写起来实在是太长了:
struct Student;
struct Node;
...
将它们重新命一下名,方便写些。
typedef struct Student
{char name[20];int age;
}Stu; //此处的 Stu 不是全局变量了,而是此结构体被重命名之后的新名字int main()
{struct Student stu1;Stu stu2;return 0;
}
其中,变量 stu1 和变量 stu2 的类型是相同的。
1.3:结构体变量的定义与初始化
1.3.1:声明类型的同时定义变量
struct point
{int x;int y;int z;
}p1; // 在声明类型的同时定义结构体变量
1.3.2:先声明类型,再定义变量
// 先定义一个结构体
struct point
{int x;int y;int z;
};// 再定义一个结构体变量(全局变量)
struct point p2; int main()
{struct point p3; // 局部变量return 0;
}
1.3.3:结构体变量的初始化
struct point
{int x;int y;int z;
}p1 = {1,2,3}; //对p1初始化struct point p2 = {2,3,4}; //对p2初始化 int main()
{struct point p3 = {3,4,5}; //对p3初始化return 0;
}
1.3.4:结构体嵌套结构体的定义与初始化
struct point
{int x;int y;int z;
};struct stu
{char name[20];int age;struct point a; //结构体嵌套一个struct point类型的结构体
};int main()
{struct stu s1 = { "张三",13,{1,2,3} }; // 赋值初始化struct stu s2 = { "李四",20,{2,3,4} };printf("%-20s\t%d\t%d\t%d\t%d\n", s1.name, s1.age, s1.a.x, s1.a.y, s1.a.z);printf("%-20s\t%d\t%d\t%d\t%d\n", s2.name, s2.age, s2.a.x, s2.a.y, s2.a.z);return 0;
}
代码运行结果:
1.4:(*)结构体的内存对齐(*)
当我们掌握了关于结构体的使用情况后,接下来我们来深思考虑一下结构体的大小有多大呢?
1.4.1:分析结构体内存大小(内存对齐)
先看一下下面两种结构体代码:
struct Stu1
{int a;char b;char c;
};struct Stu2
{char b;int a;char c;
};int main()
{printf("sizeof(struct Stu1) = %d\n", sizeof(struct Stu1));printf("sizeof(struct Stu2) = %d\n", sizeof(struct Stu2));return 0;
}//运行结果:
sizeof(struct Stu1) = 8
sizeof(struct Stu2) = 12
结果显示 struct Stu1类型的结构体大小是8个字节,而显示 struct Stu2类型的结构体大小是8个字节。为什么它们结构体内部的类型变量都相同,只是排序不同它们的大小就不同呢?这就涉及到结构体内部的内存对齐规则了。
结构体的对齐规则:
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
- 对齐数=编译器默认的一个对齐数与该成员大小的较小值。
- VS编译器上的对齐数是8,gcc编译器和Linux编译器上没有默认对齐数,这两种编译器上对齐数就是其成员自身的大小。
- 结构体大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 若有嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
该如何计算结构体的大小呢?
结构体struct Stu1 内的成员变量一共占用了6个字节,因为结构体的内存对齐规则,需要2个最大对齐数才能将其存下。所以, 结构体struct Stu1的总大小为8个字节。
同理:结构体 struct Stu2 需要3个最大对齐数才能将其存下。所以, 结构体 struct Stu2 的总大小为12个字节。
1.4.2:为什么存在内存对齐
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
结构体的内存对齐是拿空间来换取时间的做法。
设计一个优秀的结构体
让占用空间小的成员尽量集中在一起 。
1.4.3:修改默认对齐数
#pragma // 预处理指令--用来改变默认对齐数
#include<stdio.h>#pragma pack(8) //设置默认对齐数为8
struct s1
{char c1;int i;char c2;
};#pragma pack() //取消设置的默认对齐数,还原为默认#pragma pack(1) //设置默认对齐数为1
struct s2
{char c1;int i;char c2;
};#pragma pack() //取消设置的默认对齐数,还原为默认int main()
{printf("%d\n", sizeof(struct s1));printf("%d\n", sizeof(struct s2));return 0;
}// 运行结果:
12
6
1.5:结构体传参
#include<stdio.h>
struct Stu
{char name[20];int age;
};void Print1(struct Stu s) // 传整个结构体
{printf("%s\n", s.name);
}void Print2(struct Stu* ps) // 传结构体的指针
{printf("%s\n", ps->name);
}void test2()
{struct Stu s = { "小明",12 };Print1(s);Print2(&s);
}
共有两种结构体传参的方法。
但是哪种方法更好一些呢?
首选Print2函数。因为:
1. 函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。
2. 如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,故会导致性能的下降。
结论:结构体传参的时候,要传结构体的地址。
二:位段
2.1:位段是什么?
首先:位段的声明和结构体是类似的,但有两个不同:
1. 位段的成员类型必须是 int,unsigned int,signed int。
2. 位段的成员名后面有一个冒号和一个数字。
例如:
#include<stdio.h>
struct A
{int a : 2;int b : 5;int c : 10;int d : 30;
};//其中 A 就是一个位段类型。int main()
{printf("%d\n", sizeof(struct A));return 0;
}//运行结果:8
A 就是一个位段类型,但是为什么运行结果是8呢?位段A内部有4个int类型的数据,不应该是4*4=16个字节吗,这就需要了解了解位段的内存分配问题来。
2.2:位段的内存分配
首先我们应知道 ”位段“ 中的 ”位“ 是二进制位,所以,int a:2 表示给 a 分配 2个二进制位(bit位),同理:int b:5 表示给 b 分配 5个二进制位(bit位),int c:10 表示给 c 分配 10个二进制位(bit位),int d:30 表示给 d 分配 30个二进制位(bit位)。
位段的内存分配规则:
1. 位段的成员可以是 int,unsigned int,signed int,或者是 char(属于整形家族)类型
2. 位段的空间是按照需要以 4个字节( int )或者 1个字节(char)的方式来开辟的。
3. 位段涉及很多不确定性因素,位段是不跨平台的,注重可移值的程序应该避免使用位段。
以 struct A 位段分析:
因为位段的空间是按照以4个字节( int )或者 1个字节(char)的方式来开辟的。此时当4个字节不够的时候,需要再申请4个字节来存储,直到存储完毕。
看下列代码:
#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;return 0;
}
内存分析:
我们可以知道此位段得到大小是3个字节(一次增加一个字节)。
vs编译器中一个字节内部是按照从右往左的顺序,即从二进制低位向高位使用的。
位段的跨平台问题:
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
结论:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
总结:
今天,我们从结构体的声明与定义开始,学习了另外一种特殊声明(匿名结构体),又了解了结构体变量的定义与初始化是怎样描述的,接着学习了本章最最重要的结构体的内存分配对其问题,最后介绍了一种特殊结构体(位段),优点是比结构体更节省空间,但是也不要忘了它的局限性。
以上的内容若是对大家起到帮助的话,大家可以动动小手点赞,评论+收藏。大家的支持就是我进步路上的最大动力!
相关文章:

【C语言】自定义类型之---结构体超详解(结构体的定义使用、指针结构体,内存对齐,......代码详解)
目录 前言: 一:结构体 1.1:什么是结构体? 1.2:结构体类型的声明 1.3:结构体变量的定义 1.4:结构体的内存对齐 1.5:结构体传参 二:位段 2.1:位段是什…...

【完美恢复】修复计算机中丢失emp.dll的多个详细方法
最近,在尝试运行某款游戏时,我遭遇了一个令人头痛的问题——“emp.dll文件丢失”。这个错误通常意味着游戏的某个关键文件没有被正确加载或已损坏。以下是我解决问题的步骤和一些心得体会,希望对遇到类似问题的玩家们有所帮助。 emp.dll是一…...

暗黑4可以搬砖吗?暗黑4怎么搬砖 搬砖攻略
暗黑4可以搬砖吗?暗黑4怎么搬砖 搬砖攻略 暗黑破坏神4属于是暴雪旗下一款经典游戏IP,在全世界有着广泛的玩家群体,更是在今年暴雪国服宣布回归之后,吸引了一大批新玩家加入。今天小编就为大家带来暗黑4的详细搬砖教程。 现在我们…...

WLAN技术
冲突域:连接在同一传输线缆上的所有工作站的集合,或者说是同一物理网段上所有节点的集合共同竞争网络资源形成的域叫冲突域。 在OSI模型中,冲突域被看作是第一层的概念,连接同一冲突域的设备有中继器、集线器(hub&…...

维修AB罗克韦尔工控机 PanelView 900 2711-T9C8 SER C 触摸屏人机界面
可视化和 HMI 解决方案可帮助您满足生产力、创新和全球化需求。为电子操作员界面终端、分布式客户端/服务器 HMI 和信息软件提供了一致的外观和感觉。编程工具和高级软件应用程序包括远程访问和数据分析,可加速开发并提高效率。 图形终端 图形终端提供各种尺寸、操…...

334_C++_std::bind中使用shared_from_this()
std::bind(&HttpClient::getPwd, shared_from_this(), std::placeholders::_1, std::placeholders::_2);[ HttpClient继承自NetObj,NetObj是父类,NetObj受到std::shared_pt...

【Python】防御性编程入门
1. 前言 防御性编程指的是为了防止代码泄露后被竞品公司窃取技术,使用一种较高级的明文加密编程方式。也可以当做一种带解密性质的时间胶囊,锻炼程序员自己的记忆能力、读代码能力等。 2. 案例分析 2.1 import Import里面可以多取一些喜欢的名字&#…...

无线麦克风哪个品牌音质最好?热门无线麦克风品牌推荐
这段时间短视频行业兴起,很多人都开始尝试步入自媒体,不过想要自己的视频内容更出色、更吸引人,好的音频设备肯定是必不可少的,而麦克风就是其中的一种。麦克风的好坏也将决定了一个视频的质量与完整性等等,如果我们作…...

粒子奇观:用Processing创造宇宙级的动态效果
前言: 👋 今天,我们将一起探索宇宙的奥秘,不是在星空下,而是在Processing的代码世界中。这是我们的第八篇文章,我们将深入粒子系统的神奇领域,学习如何创造出令人惊叹的动态效果。 粒子系统:构建动态世界的基石 🔨 粒子系统是计算机图形学中用于模拟复杂自然现象…...

Filesystem Fragmentation on Modern Storage Systems——论文泛读
TOCS 2023 Paper 论文阅读笔记整理 问题 文件系统碎片是计算机系统随着时间的推移而变慢的主要原因之一。以前认为,碎片化对硬盘驱动器(HDD)等旋转存储设备有害,但不影响固态驱动器(SSD),因为…...

如何同步管理1000个设备的VLAN数据?
什么是VLAN? VLAN,也就是虚拟局域网,是通过为子网提供数据链路连接来抽象出局域网的概念。在企业网中,一个企业级交换机一般是24口或者是48口,连接这些接口的终端在物理上形成一个广播域。广播域过大,就会导…...

【谷粒商城】01-环境准备
1.下载和安装VirtualBox 地址:https://www.virtualbox.org/wiki/Downloads 傻瓜式安装VirtualBox 2.下载和安装Vagrant官方镜像 地址:https://app.vagrantup.com/boxes/search 傻瓜式安装 验证是否安装成功 打开CMD,输入vagrant命令,是否…...

2024深圳杯数学建模C题参考论文24页+完整代码数据解题
一、问题研究 24页参考论文: 【编译器识别】2024深圳杯C题24页参考论文1-3小问完整解题代码https://www.jdmm.cc/file/2710545/ 为了回答这些问题,我们需要进行一系列的编译实验、分析编译结果,并构建判别函数。以下是对这些问题的初步分析…...

用go语言写一个代码,加班就自动给老婆发信息,下班自动提醒的代码
文章推荐 1 作为程序员,开发用过最好用的AI工具有哪些? 2 Github Copilot正版的激活成功,终于可以chat了 3 idea,pycharm等的ai assistant已成功激活 4 新手如何拿捏 Github Copilot AI助手,帮助你提高写代码效率 5 Jetbrains的a…...

Spring-Cloud 微服务
1. 微服务架构 1.1 单体应用架构---内部项目【OA WMS等】 将项目所有模块(功能)打成jar或者war,然后部署一个进程 优点: 1:部署简单:由于是完整的结构体,可以直接部署在一个服务器上即可。 2:技术单一:项目不需要复杂的技术栈,往往一套熟悉的…...

python数据分析——数据可视化(图形绘制基础)
数据可视化(图形绘制基础) 前言一、图形绘制基础Matplotlib简介使用过程sin函数示例 二、常用图形绘制折线图的绘制plot示例 散点图的绘制plot示例 柱状图的绘制bar示例 箱型图绘制plot.box示例 饼状图的绘制pie示例 三、图形绘制的组合情况多个折线图的…...

必背!!2024年软考中级——网络工程师考前冲刺几页纸
距离软考考试的时间越来越近了,趁着这两周赶紧准备起来 今天给大家整理了——软考网络工程师考前冲刺几页纸,都是核心重点,有PDF版,可打印下来,每天背一点。 计算机总线分类 ①总线的分类:数据总线、地址总…...

html+js光标操作
光标设置id为username的字段 window.addEventListener("load", function() {document.getElementById("username").focus(); }); 光标在username的时候点击enter回车键的时候光标移动到id为password的input里面 document.getElementById("username…...

Cannot read properties of undefined (reading ‘init‘)报错
出现这个报错是印象项目没有引echarts包 npm i echarts 下包 然后在main.js中引入 import echarts from echarts Vue.prototype.$echarts echarts 如果还不行 import * as echarts from echarts; 更改一下引入方式 ok了...

golang html/template模板中使用自定义函数/方法的2种方法总结
在golang的html/template模板库中我们如果希望在视图文件中调用自定义的函数 或者方法可以通过以下2种方法实现: 1. 调用自定义函数 可通过将自定义的函数加入到 template.FuncMap中,然后再使用 template.New("xxx.html").Funcs(funcMap)来在…...

浅析vue3自定义指令
vue3中可以像下面这样使用自定义指令。 这里我们只是定义了一个vFoucs变量,vue怎么知道这是一个指令呢? 这是因为约定大于配置,vue3中有这样一个约定(截图来自官方文档): 注意这里说的是驼峰命令&#x…...

后仿真中的关于延时问题(延迟类型选择和脉冲控制)
目录 通过前面的文章提到,从物理特性角度出发,仿真中存在两种延时:惯性延时和传输延时。那么,实际仿真电路过程中,我们究竟选择的哪种模式呢? 一 指定传输延迟类型 传输延迟类型不是默认的延迟类型。我们需要显示指定它。 1.1 module-path delay VCS 仿真中添加如下三…...

欧拉公式e^(ix)=(cos x+isin x)
啊,哈喽,小伙伴们大家好。我是#张亿,今天呐,学的是欧拉公式 在不同的学科中有着不同的含义和应用。在复变函数中,欧拉公式表述为e^(ix)(cos xisin x),其中e是自然对数的底,i是虚数单位&#x…...

Android 获取已安装应用、包名、应用名、版本号、版本名
1、相关代码 List<ApplicationInfo> installedApps getPackageManager().getInstalledApplications(0);for (ApplicationInfo appInfo : installedApps) {CharSequence getAppName getPackageManager().getApplicationLabel(appInfo);String appNamegetAppName.toStrin…...

2024数学建模深圳杯B题成品论文43页word+完整可视化结果图+可执行代码
【无水印word】2024深圳杯B题成品论文43页(附带1-4小问完整py解题代码思路)https://www.jdmm.cc/file/2710664 批量工件并行切割下料优化研究 摘 要 本研究针对批量工件并行切割下料问题展开了深入的探讨与分析。通过建立数学模型和运用优化算法&…...

达梦(DM) SQL查询及联合查询
达梦DM SQL查询及联合查询 查询结果排序多表联合查询 这里继续讲解DM数据库的Sql查询操作 查询结果排序 为提高查询结果可读性,我们可以对查询结果按照一定顺序排列,或者也可以将列名替换成数字,例如 ORDER BY 1 DESC,意思是按第…...

【重生之我在学Android】WorkManager (章一)
相关文章 【重生之我在学Android原生】ContentProvider(Java) 【重生之我在学Android原生】Media3 【重生之我在学Android】WorkManager (章一) 前言 官方文档 官方推荐 - 前台服务、后台服务都可以使用WorkManger来实现 案例 语言:JA…...

【强训笔记】day23
NO.1 思路:直接计算结果,先计算怪物可以抗几次攻击,再计算勇士受到的伤害,如果勇士的攻击力大于等于怪物的血量,那么就可以击杀无数只,如果勇士的血量正好是受到攻击的整数倍,那么击杀的怪物数…...

C语言-STM32:介绍PWM,并使用PWM实现呼吸灯
1、什么是PWM PWM,全称为Pulse Width Modulation,中文名为脉冲宽度调制。这是一种模拟控制技术,通过改变脉冲信号的宽度来表征一个连续变量的平均值,通常用于对模拟信号的数字化控制,特别是在功率转换和信号处理中非常…...

SpringBean详解
文章目录 概述Spring获取Bean的流程依赖注入bean的作用域Spring 中的 Bean 是线程安全的吗Spring如何处理线程并发问题bean 的自动装配和方式Resource和Autowired的区别bean的自动装配bean的生命周期BeanFactoryBeanFactory 常用的实现类有哪些BeanFactory与FactoryBean的不同A…...