
目录
💕1.什么是结构体类型
💕2.结构体变量的创建
💕3.结构体变量的初始化
💕4.结构体的特殊声明
💕5.typedef重定义结构体变量(两种方式)
💕6.结构体自引用
💕 7.创建结构体指针变量
💕8.结构体内容的访问
8.1直接访问:
8.2 结构体指针访问
💕9.结构体内存存储
9.1 结构体内存存储4条规则
例题1&&讲解:
结构体内容的储存方式:
结构体字节的大小:
例题2&&讲解:
结构体内容的储存方式:
结构体字节的大小:
例题3&&讲解:
结构体内容的储存方式:
结构体字节的大小:
例题4&&讲解(结构体嵌套问题 )
结构体内容的储存方式:
结构体字节的大小:
✨ 10.为什么存在内存对齐?
✨ 11.如何节省结构体内存大小
✨12.修改默认对齐数
✨ 13.scand初始化结构体变量
错误写法如下编辑:
✨14.结构体传参
编辑
小心!VS2022不可直接接触,否则!没这个必要,方源面色淡然一把抓住!顷刻炼化!
什么是结构体类型,这里拿一个常见的格式举例:
这是一个很常见的结构体格式
struct stu 为结构体类型
s1 为结构体的变量
int a 为结构体变量的内容
结构体变量的创建有两种方法,一种是随着结构体的创建而创建
另一种则是单独创建,示例如下:
方法一:
方法二(含注意事项):
这里采用的是 用结构体类型创建结构体变量
不理解的可以类比int a,类型加变量名
结构体变量的初始化可以分为三种情况:
情况1&&情况3:
变量随着结构体而定义
定义结构体变量需要采用,变量名.结构体内容 进行初始化结构体内容
情况2:
变量不随着结构体而定义
定义结构体变量采用, 结构体类型+变量名 的形式进行初始化结构体内容
如果结构体声明不完全,创建的结构体只能使用一次,示例如下:
第一种方式:
第二种方式:
结构体自引用是一件很可怕的事,结构体最怕的就是重命名,一旦重命名就会出现错误
比如说结构体的变量与结构体的内容重名就会出现错误
那么什么是结构体的自引用呢?结构体的自引用就是一个结构体嵌套自己的结构体
示例如下:
使用自己的结构体嵌套自己,就会出现无数多个自己的结构体,结构体的内存就会无限大,是不合理的
对于结构体来说,结构体在未创建变量时不占用内存
所以结构体的变量名会表示整个结构体,因此&结构体变量名,就相当于取出了整个结构体变量的内存
那么如何创建结构体指针变量呢?示例如下:
创建结构体指针变量第一种写法:
相信有的同志会写出这样的代码:
其实这样写也是对的,因为在结构体大括号外的 ; 写完之后,再写句子就是单独的表达式了,所以其实和第一种写法是完全相同的,只是代码放的位置不一样
创建结构体指针变量第二种写法:
结构体内容的访问可分为两种,一种为直接访问,另一种为通过结构体指针访问
结构体内存储存方式有以下4条规则,也叫做结构体内存对齐原则
1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认的第一个对齐数与该成员变量大小的较小值
vs中默认的值为8
Linux中gcc没有默认对齐数,对齐数就是成员自身的大小
3.结构体的总大小为最大对齐数(结构体中每个成员都有一个对齐数,所有对齐数中最大的)的整数倍
4.如果嵌套了结构体,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍,结构体的整数大小就是所有最大对齐数(含嵌套结构体中成员的对齐数的整数倍)
多说无益,我们拿 代码例题+配图 来讲解
首先结构体的内存存储默认从0开始存储,我们以例题讲解
结构体内容的储存方式:
第一个结构体内容为char c1,结构体成员 c1 占用1个字节大小,结构体默认从0位置开始存储,那就从0开始存储1个字节大小的内存
第二个结构体内容为int i,结构体内容 i 占用4个字节,但因为结构体内存对齐原则,占用4个字节的int i,只能从 4 的整数倍数处(也叫结构体成员 i 的对齐数的整数倍)开始存储内存,所以内存将拓展到 4 的位置,从4的位置开始向下存储4个字节大小,4 5 6 7都是 i 的内存存储处
第三个结构体内容为char c2,结构体成员 c2 占用一个字节,因为结构体内存对齐原则,占用1个字节大小的char c2只能从1 的整数倍数处(对齐数的整数倍)开始存储内存,所以接着从 7 的位置向下存储1个字节大小的内存,即:将8的位置设为char c2 的内存存储处
结构体字节的大小:
在我们清楚完毕结构体内存的存储位置后,因为结构体的总大小为最大对齐数(结构体中每个成员都有一个对齐数,所有对齐数中最大的)的整数倍,在此例题中结构体成员的最大对齐数为4,也就是int i 中的 i 的对齐数为4,
因为结构体内存存储占用了9个字节的大小,所以我们取4的整数倍数(最大对齐数)12,最终大小被填充到12个字节
结构体内容的储存方式:
第一个结构体内容为char c1,结构体成员c1占用1个字节大小,结构体默认从0位置开始存储,那就从0开始存储1个字节大小的内存
第二个结构体的内容为char c2,结构体成员c2占用1个字节,但因为结构体内存对齐原则,占用1个字节的char c2,只能从 1 的整数倍数处(也叫c2 的对齐数的整数倍)开始存储内存,所以向下1个字节的位置为c2的内存存储地址
第三个结构体内容为int i,结构体成员 i 占用4个字节,因为结构体内存对齐原则,占用4个字节大小的int i 只能从4 的整数倍数处(对齐数的整数倍)开始存储内存,所以我们从 4 的整数倍处开始存储4个字节,4 5 6 7都为结构体变量内容 i 的存储地址
结构体字节的大小:
在我们清楚完毕结构体内存的存储位置后,因为结构体的总大小为最大对齐数(结构体中每个成员都有一个对齐数,所有对齐数中最大的)的整数倍,在此例题中结构体成员的最大对齐数为4,也就是int i 中的 i 的对齐数为4,
因为结构体内存存储占用了8个字节的大小,所以我们取4的整数倍数(最大对齐数)8,最终结构体字节大小为8个字节
结构体内容的储存方式:
第一个结构体内容为double d,结构体成员 d 占用8个字节大小,结构体默认从0位置开始存储,那就从0开始存储8个字节大小的内存
第二个结构体的内容为char c,结构体成员 c 占用1个字节,但因为结构体内存对齐原则,占用1个字节的char c,只能从 1 的整数倍数处(也叫c 的对齐数的整数倍)开始存储内存,所以向下1个字节的位置为 c 的内存存储地址
第三个结构体内容为int i,结构体成员 i 占用4个字节,因为结构体内存对齐原则,占用4个字节大小的int i 只能从4 的整数倍数处(对齐数的整数倍)开始存储内存,所以我们从 4 的整数倍处开始存储4个字节,因为8的位置已经被存储了,所以我们从下一个整数倍数--也就是12的位置开始存储,12 13 14 15都为结构体变量内容 i 的存储地址
结构体字节的大小:
在我们清楚完毕结构体内存的存储位置后,因为结构体的总大小为最大对齐数(结构体中每个成员都有一个对齐数,所有对齐数中最大的)的整数倍,在此例题中结构体成员的最大对齐数为8,也就是double d 中的 d 的对齐数为4,
因为结构体内存存储占用了16个字节的大小,所以我们取最大对齐数 8 的整数倍数(最大对齐数)16,最终结构体大小为16个字节
结构体内容的储存方式:
第一个结构体内容为char c1,结构体成员 c1 占用1个字节大小,结构体默认从0位置开始存储,那就从0开始存储1个字节大小的内存
第二个结构体的内容为struct S3 s3,结构体成员 s3 占用16个字节,但因为是结构体嵌套的内存存储,所以我们要从s3中成员最大对齐数的整数倍处开始存储,s3中最大对齐数的整数倍为8,所以我们从8的位置开始往下存储16个字节,直到23
第三个结构体内容为double d,结构体成员 d 占用8个字节,因为结构体内存对齐原则,占用8个字节大小的double d 只能从8 的整数倍数处(也叫对齐数)开始存储内存,所以我们从 8 的整数倍处开始存储8个字节,也就是从24的位置开始,向下存放8个字节大小的内存
结构体字节的大小:
在我们清楚完毕结构体内存的存储位置后,因为结构体的总大小为最大对齐数(结构体中每个成员都有一个对齐数,所有对齐数中最大的)的整数倍,在此例题中结构体成员的最大对齐数为8,也就是double d 或者 s3中double d 的对齐数 8,
因为结构体内存存储占用了32个字节的大小,所以我们取最大对齐数 8 的整数倍数32,最终大小为32个字节
大部分的参考资料都是这样说的:
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的:某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
想要节省结构体内存的大小,我们需要同时满足对齐,又要节省空间
我们可以通过将占用空间小的结构体成员尽量集中在一起
例如:
S1和S2的结构体成员一模一样,但是S2占用的字节比S1小
在结构体中,我们可以使用一个预处理指令来修改结构体的默认对齐数
#pragma这个预处理指令可以改变编译器的默认对齐数,这对于节省结构体内存非常有用
#pragma改变默认对齐数后,所有结构体内容的对齐数都会被#pragma修改
使用示例如下:
修改结构体默认对齐数后,所有结构体成员都会受影响,因此结构体成员的最大对齐数也会随之改变
分析如图:
因为结构体成员的最大对齐数为1,所以结构体内存就以最大对齐数的整数倍(也就是 1 的整数倍)为结构体字节大小
使用scanf初始化结构体变量,就需要对变量内的内容直接访问,以初始化结构体变量内的内容
使用&s1是不可以完成赋值结构体变量的内容的,因为结构体变量内存的存储方式多种多样,结构体变量的地址与结构体变量第一个内容的地址相同,但之后的变量内容怎么办?无法通过scanf一次性输入
所以不能使用&s1来实现结构体的输入
结构体传参传的是什么?
结构体传参有两种传法,一种是将整个结构体内容全部传过去,另一种则是将结构体变量的地址传过去
示例如下:
注意:结构体传参时,结构体必须为全局变量,如果为局部变量,结构体传参就会报错,如下图:
问:print1 与 print2 哪个更好些?
答案是首选print2函数,传递结构体指针的地址更好
原因:函数传参时,参数是需要压栈的,会有时间和空间上的开销
如果我们将一整个结构体传过去,结构体的内存越大,参数压栈的系统开销就越大,会导致性能的下降
结论:结构体传参时,首选传递结构体的地址
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/11524.html







