📬结构体的声明
🔎1.结构的基础知识
🚩 结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
🔎2.结构的声明
🌰如描述学生👇
//定义学生类型 struct Stu { //成员变量 char name[20]; int age; float weight; }s4,s5,s6;//全局变量 int main() { struct Stu s1;//局部变量 struct Stu s2; struct Stu s3; return 0; }
🔎3.特殊的声明
🚩 在声明结构的时候,可以不完全的声明
👉匿名结构体类型👈
🌰举个例子👇
//匿名结构体类型 struct { char c; int a; double d; }s1; struct { char c; int a; double d; }a[20],*p;
上面的两个结构在声明的时候省略掉了结构体标签(tag)
❓问题来了
在上面代码的基础上,下面的代码合法吗?
p = &s1;
🚨编译器会把上面的两个声明当成完全不同的两个类型
🚨所以是非法的
🔎4.结构的自引用
在数据的存储中,可以使用顺序表的形式对数据进行存放;
顺序表,是计算机内存储存数据的一种方式;
即用一组地址连续的存储单元依次存储线性表中的各个元素;
而除了顺序表以外,还可以使用非顺序的形式的链表的方式对数据进行存储;
即使用不同的地址分别对数据进行存储,链表内的各个数据称为 “ 结点 ” ;
当需要使用或查找某个数据时,只需找到最初的结点即可访问需要访问的数据;
链表与顺序表都是以线性的方式对数据进行存储
若是使用结构体,如何创建一个简单的链表?
🚩在结构中包含一个类型为该结构本身的成员是否可以呢?
//错误示范 struct Node { int data; struct Node next; };
这样是否可以呢?
如果可以,那sizeof(struct Node)是多少呢?
上面的代码为创建一个结构体,并对结构体进行自引用达到通过一个数据访问下一个数据。
若是如此进行编译,在结构体内部的结构体变量还存在着一个结构体,周而复始,程序将会像死递归一样不停调用该结构体;
故该段代码为错误示范✖️。
正确的自引用方法✅👇
struct Node { int data;//4 struct Node* next;//4/8 }; int main() { struct Node n1; struct Node n2; n1.next = &n2; return 0; }
🔎5.结构体变量的定义和初始化
🚩 请看代码(1)👇
struct S { int a; char c; }s1; struct S s3; struct B { float f; struct S s; }; int main() { struct S s2 = { 100,'q' }; struct S s3 = { .c = 'r',.a = 2000 }; struct B sb = { 3.14f,{200,'w'} }; printf("%f,%d,%c\n", sb.f, sb.s.a, sb.s.c); return 0; }
🚩 请看代码(2)👇
struct S { char name[100]; int* ptr; }; int main() { int a = 100; struct S s = { "abcdef",NULL }; return 0; }
🔎6.结构体内存对齐
在声明结构体的时候,往往不同的结构体成员不同,结构体的大小也不同,若是存在以下的结构体,他们相应的大小是多少??
struct S1 { int a; char c; }; struct S2 { char c1; int a; char c2; }; struct S3 { char c1; int a; char c2; char c3; }; int main() { printf("%d\n", sizeof(struct S1)); printf("%d\n", sizeof(struct S2)); printf("%d\n", sizeof(struct S3)); return 0; }
答案会是什么呢?
5 6 7 ???
那为什么和预想中的结果不一样呢?
🚩首先得掌握结构体的对齐规则:
🔴第一个成员在与结构体变量偏移量为0的地址处。
🔴其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
🔴对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
👉VS 中默认的值为8
👉Linux 中没有默认对齐数,对齐数就是成员自身的大小
🔴结构体总大小为所有成员的对齐数中最大对齐数(每个成员变量都有一个对齐数)的整数倍。如果不够,则浪费空间对齐。
🔴如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
🔴如果嵌套了结构体,嵌套的结构体成员要对齐自己成员中的最大对齐数的整数倍处
🔴整个结构体大小,必须是最大对齐数的整数倍,最大对齐数包含嵌套的结构体成员中的对齐数
在这里我们可以使用offsetof宏来看一下各个成员的偏移量(使用offsetof求偏移量时,应包括头文件 <stddef.h> )
#include<stddef.h> struct S { char c; int a; }; int main() { struct S s = { 0 }; printf("%d\n", offsetof(struct S, c)); printf("%d\n", offsetof(struct S, a)); return 0; }
我们再看一个👇
struct S3 { double d; char c; int i; }; int main() { printf("%d\n", sizeof(struct S3)); return 0; }
在上段代码的基础上,我们再来一个👇
struct S3 { double d; char c; int i; }; struct S4 { char c1; struct S3 s3; double d; }; int main() { printf("%d\n", sizeof(struct S4)); return 0; }
🔴如果嵌套了结构体,嵌套的结构体成员要对齐自己成员中的最大对齐数的整数倍处
🔴整个结构体大小,必须是最大对齐数的整数倍,最大对齐数包含嵌套的结构体成员中的对齐数
为什么存在内存对齐?
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常.
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐;
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问.
🔴总体来说:
结构体的内存对齐是拿空间来换取时间的做法
在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起
struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; };
S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别
🔎7.修改默认对齐数
🚩使用 #pragma 这个预处理指令,可以改变我们的默认对齐数
👇请看代码与注释👇
#pragma pack(8)//设置默认对齐数为8 struct S { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认
🥰结论:
结构在对齐方式不合适的时候,我们可以自己更改默认对齐数
🔎8.结构体传参
struct S { int data[1000]; int num; }; struct S s = { {1,2,3,4},1000 }; //结构体传参 void printf1(struct S s) { printf("%d\n", s.num); } //结构体地址传参 void printf1(const struct S* ps) { printf("%d\n", ps->num); } int main() { print1(s);//传结构体 print2(&s);//传地址 return 0; }
print2 相比于 print1 更好一些
原因:
🔴函数传参的时候,参数时需要压栈,会有时间和空间上的系统开销.
🔴如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降.
结论:
🔴结构体传参的时候,要传结构体的地址.