提示:建议先有结构体一定基础再阅读本篇文章🥦当然你也可以先阅读下我的上一篇【结构体(入门)】连接如下:
目录
前言🛫
之前单纯的文本我自己也感觉十分的枯燥,通过借鉴别人的博客,我进行了一些优化,希望读者在观感上不会感觉那么枯燥
希望各位大佬提出建议🥰
🛰️感谢支持:👍点赞🙌收藏✍️留言
各位家人更新不易,欢迎大家提出问题建议,你们的👍点赞👉评论👏支持对我来说很重要,那是我坚持下去的动力❤️🔥需要源代码的可以去Git仓库下载,里面记录了我学习成长的代码。
😸本文重点😸:
🚅结构体声明🚃结构体自引用🚃结构体定义🚏🚏
🚅结构体初始化🚃结构体内存对齐🚃结构体传参🚏🚏
😻正文😻:
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。因此一个结构体中可以包含很多类型的变量,例如:整形,浮点型,指针等
💘1.结构的声明💘:
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
特别的:结构体再声明时,可以不完全声明 即匿名结构体。
#include <stdio.h>
struct
//没有声明结构体标签,即为匿名结构体类型
{
int age;
char name[20];
float score;
}student = {21,"Tong",91.7};
//匿名结构体类型必须在生声明的同时进行定义
int main()
{
printf("%d %s %f", student.age, student.name, student.score);
}
之所以在这里强调这个知识点是因为,在进行完全声明时,例如上面我们的 “ 学生 ”的示例中的s1~s6这六个结构体变量因为声明时声明了结构体标签,所以会被视为同一种类型进行处理和调用。
看下面的例子:
//结构体类型1:
struct
{
char name[20];
int age;
char sex[5];
float score;
}x;
//结构体类型2:
struct
{
char name[20];
int age;
char sex[5];
float score;
}*p;
上面的两个结构在声明的时候省略掉了结构体标签(tag)。
那么问题来了?
p=&x; 这条语句正确么?
警告:
编译器会把上面的两个声明当成完全不同的两个类型。
所以是非法的。
2.💞结构体的自引用💞
在结构中包含一个类型为该结构本身的成员是否可以呢?
答案是肯定的!
但是要注意书写:
代码如下(示例):
//代码1(错误的自引用)
struct Node
{
int data;
struct Node next;
};
//代码2(正确的自引用)
struct Node
{
int data;
struct Node* next;
};
那么有读者又会有这样的问题?
//代码3
typedef struct
{
int data;
Node* next;
}Node;
//这样写代码,可行否?
答案是:不可取的! 那么如何解决呢?
代码如下:
//解决方案:
typedef struct Node
{
int data;
struct Node* next;
}Node;
3.💪结构体变量的定义和初始化💪
这部分内容已经在结构体入门这篇博客中详细讲过,有感兴趣的老铁,可以阅读复习下。💯
4.结构体的内存对齐(计算结构体大小)重要指数⭐⭐⭐⭐⭐
🪐首先我们应该了解结构体的内存对齐规则🪐
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
了解了上述规则我们来看下面几个结构体,并求出他们的内存对齐数
//练习1
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
//练习2
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
//练习3
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
//练习4-结构体嵌套问题
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4))
不知道各位铁汁算对了么?
接卸来我详细讲解计算过程(vs默认对齐数为8)
练习一:
第一个成员变量c1的数据类型为char占1字节内存,将其放在0偏移量处,
第二个成员变量i的数据类型为int占4字节内存,按照规则我们首先要计算它的对齐数,我们将变量 i 的大小4与对齐数默认值8进行比较,得出较小值为4,即对齐数为4,于是我们将它放在对齐数4的整数倍处,即最近位置第四字节处:同理,第三个成员变量c2的数据类型为char占1字节内存,对齐数为1,将它放在1对齐数整数倍处,即第八字节处。我们计算出的偏移量为0-8字节,即9字节,最后,根据规则,结构体的总大小为最大对齐数的整数倍,而这三个变量中,对齐数最大的是 int 类型变量的对齐数4,则总大小应当为4的倍数。而既为4的倍数,又要能够容纳所有的结构体成员,最小的结构体大小应当为12个字节。
接下来的s2,s3大家可以自行验证一下。接下来我们只讲S4的嵌套结构体,解析如下:
练习四:
第一个成员变量c1的数据类型为char占1字节内存,将其放在0偏移量处
第二个成员变量为结构体S3,该结构体中自身最大对齐数为8,且结构体S3自身占16字节内存,所以我们将其放置在8-23字节处(总共16字节)
第三个成员变量为double d占8字节内存,对齐数应为8,所以我们将它放置在24(对齐数整数倍)-31字节处。
嵌套结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍,即为8的整数倍,0-31恰好为8的整数倍,即嵌套结构体的大小为32字节。
但是我们发现,这样的方式造成了很大程度上的空间浪费,以 test1 为例,12个字节的大小中有六个字节的空间申请了但却没有被使用。那么为什么还要采用这样的办法呢?主要有以下两个原因
1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
通俗来说结构体的内存对齐就是一种用空间换时间的处理方法。
而我们能做的,就只有以上面的 练习1 与 练习2 为例,尽可能的选取 练习2 这样,使占用空间小的成员尽可能的集中在一起。
5.🍍修改默认对齐数🍍
在我们的代码编写过程中,默认的对齐数可能会不够合适。而当这个时候,我们就可以通过使用下面这个预处理指令来修改我们的默认对齐数:
#pragma pack(8)
//修改默认对齐数为8
我们也可以通过该指令在修改过默认对齐数之后,取消设置的默认对齐数,将其还原:
#pragma pack()
//取消设置的默认对齐数,还原为默认
6.🤩结构体传参 🤩
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct TEST
{
int test;
};
//结构体传参
void Printf1(struct TEST test1)
{
printf("打印测试用例Printf1:%d\n", test1.test);
}
//结构体地址传参
void Printf2(struct TEST* test1)
{
printf("打印测试用例Printf2:%d\n", test1->test);
}
int main()
{
struct TEST test1 = {666};
Printf1(test1);
//传值调用:传结构体
Printf2(&test1);
//传址调用:传结构体地址
return 0;
各位读者思考一下,哪一种传参方式更优?😊
我们认为应当首选 Printf2 函数。原因是函数在传参时,参数是需要压栈的,即将整个结构体变量的所有成员数据全部拷贝至函数执行时所创建的临时空间。那么假设在传递结构体对象的时候,结构体过大,参数压栈的系统开销有可能会很大,这种情况下将会导致我们计算机系统的性能的下降:
结论:我们在进行结构体传参时,应当传递结构体的地址。
🍊总结🍊:
结构体的内容就此告一段落,君子坐而论道,少年起而行之😊!希望各位读者阅读后能有所收获,喜欢的可以点赞👍,👉收藏,👉关注,期待下一篇的博客再见🪐!