文章目录
一、什么是结构体?
1. 结构体的概念
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构体是C语言中一种重要的数据类型,该数据类型由一组称为成员(或称为域,或称为元素)的不同数据组成,其中每个成员可以具有不同的类型。结构体通常用来表示类型不同又相关的若干数据。
结构体类型不是由系统定义好的,而是需要程序设计者自己定义的。C语言提供了关键字struct来标识所定义的结构体类型。
2. 结构体的声明
- 图例:
- 举例:
structPeople{char name[20];//名字int age;//年龄char sex[5];//性别char id[30];//身份证号};//分号不能丢
3. 匿名结构体类型
在声明结构体的时候,可以不完全声明,不定义结构体类型名,这种结构体类型称为匿名结构体类型。
匿名结构也称为未命名结构,由于没有名称,因此不会创建它们的直接对象(或变量),通常我们在嵌套结构或联合中使用它们。
举例:
struct//此处省略了结构体标签(tag){int a;char b;float c;}x;//在此处创建结构体对象struct{int a;char b;float c;}a[20],*p;
提问:以上两个结构体成员变量都相同,它们的类型是否也是相同的?
4. 结构体变量的定义和初始化
- 声明类型的同时定义变量
structStu//类型声明{char name[15];//名字int age;//年龄}s;//定义变量s
- 声明结束后定义变量
structPoint s;//定义变量s
- 定义变量的同时初始化
structStu s1 ={"zhangsan",19};//对变量s1初始化structStu//类型声明{char name[15];//名字int age;//年龄}s2={"lisi",20};//对变量s2初始化
- 结构体嵌套初始化
structNode{int data;structPoint p;structNode* next;}n1 ={10,{4,5},NULL};//对变量n1初始化structNode n2 ={20,{5,6},NULL};//对变量n2初始化
二、结构体的自引用
提问:在结构体中是否可以包含一个类型为该结构体本身的成员呢?
- 图例:
如上图,是否可以通过链表的方式自引用找到其他成员变量呢?
- 如若想通过链表的方式找到下一个节点,每个节点中必须要包括该节点中的数据,还要有能找到下一个节点的方式。所以,可以给每个节点都定义为一个结构体成员,那么成员变量就应该包括该节点本身的数据data和能找到下一个节点的结构体指针 struct node*。
代码示例:
structNode{int data;structNode* next;};
- 如果觉得在定义结构体对象的时候写一长串代码很麻烦,可以利用typedef对结构体重命名
代码示例:
typedefstructNode{int data;structNode* next;}Node;//在此处写上重命名后的类型名
注意:必须在重命名之后才能使用Node(重命名后的类型名)。
三、结构体的大小
1. 结构体内存对齐
- 结构体内存对齐规则
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认对齐数的值为8
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
举例1:
structS1{char c1;int i;char c2;};printf("%d\n",sizeof(structS1));
运行结果:
分析图:
举例2:
structS2{char c1;structS1 s1;double d;};printf("%d\n",sizeof(structS2));
运行结果:
分析图:
2. 为什么存在内存对齐?
- 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于:为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
图例:
总结:结构体的内存对齐是拿空间来换取时间的做法。
因此,在设计结构体的时候,我们既要满足对齐,又要节省空间,应该让占用空间小的成员尽量集中在一起。
举例:
structS3{char c1;int i;char c2;};//S3大小为12structS4{char c1;char c2;int i;};//S4大小为8
3. 修改默认对齐数
结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。
- 更改默认对齐数
#pragmapack(1)//设置默认对齐数为1
- 还原默认对齐数
#pragmapack()//取消设置的默认对齐数,还原为默认
四、结构体传参
- 结构体传参
voidprint1(structS s)//直接传结构体{printf("%d\n", s.num);}
- 结构体地址传参
voidprint2(structS* ps)//传结构体的指针{printf("%d\n", ps->num);}
提问:print1 和 print2 函数哪个好些?
首选print2函数。
- 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
- 如果传递一个结构体对象的时候,则形参也需要开辟和结构体对象一样大小的空间,若结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
总结:结构体传参的时候,要传结构体的地址。
版权归原作者 小周在忙_ 所有, 如有侵权,请联系我们删除。