这段时间迷迷糊糊学了指针,学校的考试范围还有结构体,然鹅我发现我学习了一些知识之后,并没有很好地掌握,于是乎,打算写一篇文章来巩固已学知识,并在文末总结学校作业里的写错的题目。(晚上还要苦苦复习高数和线代)
什么是结构?结构是一些值的集合。每个成员可以是不同类型的变量(数组是相同类型的集合)。
struct tag { member-list; }variable-list;
结构体类型的定义
写法1:
struct Stu { //学生的属性(成员列表) char name[20]; int age; };//无变量列表也可以
建立一个类型。
写法2:
struct Stu { //学生的属性(成员列表) char name[20]; int age; }s1,s2;//无变量列表也可以
s1和s2利用上面的结构体类型,创建一个结构体变量,是struct Stu类型的变量(此时是结构体的全局变量)。
int main() { Struct Stu s3; return 0; }
此时是局部变量。
一些特殊的声明
匿名结构体类型
struct { char name[20]; int age; }s1;
匿名结构体类型用一次就不能用了。
struct Node { int data;//定义一个节点的时候,既能够包含一个数值,又能找到下一个节点 struct Node *next; };
struct { int a; char b; float c; }x; struct { int a; char b; float c; }a[20], *p; int main() { p=&x; return 0; }
当两个匿名结构体变量相同时, 我们把x的地址放在p里。此时编译器会报警告:从*到*的类型不兼容。
在数据结构中有一个链表的概念,链表中有顺序表。我们只需要让每一节点包含下一个节点,就能使每一个节点找到下一个节点。
struct Node { int data;//定义一个节点的时候,既能够包含一个数值,又能找到下一个节点 struct Node next; };//但是这个结构体的字节无法计算,我们无法直接求出它的大小 int main() { return 0; }
但我们可以选择把下一个节点的地址给上一个节点,这样也可以使不同节点串起来。此时,一个节点被分为两个部分,一个部分存放数值,被称为数值域,另一个部分存放地址,被称为指针域。
struct Point { int x; int y; }p1 = { 3,2 }; struct score { int n; char ch; }; struct Stu { char name[20]; int age; char score s; }; int main() { struct Point p2 = { 3,4 }; struct Stu s1 = { "zhangsan",20,{100,'q'}}; printf("%s %d %d %c",s1.name,s1.age,s1.s.n,s1.s.ch); }
这样程序就可以打印了。
结构体内存对齐
这部分内容学校的书上甚至提都没有提,但是不妨碍我们要好好掌握。
struct S1 { char c1; int i; char c2; }; int main() { printf("%d\n", sizeof(struct s1)); return 0; }
计算s1所占字节数,我们可能会下意识认为:char占1个字节,int占8个字节,所以一共占10个字节。But totally wrong!结果是12。出现这个结果的原因是什么呢?
接下来我们介绍结构体对齐的情况:
1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的⼀个对⻬数 与 该成员变量大小的较小值。
- VS 中默认的值为 8 -
Linux中 gcc 没有默认对齐数,对齐数就是成员⾃⾝的大小
3. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对⻬数中最⼤的)的 整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
什么是偏移量?
第一个位置相对于起始位置的偏移量为0,第二个位置为1,以此类推。
以struct1为例,演示其在内存中的存储过程。首先,char的内存数为1,与vs编译器默认8,取较小值为1,同理int类型为4,char类型为1。c1先在内存中占偏移量为0的位置,再看i,i需要在偏移量为4的位置占4个字节,但目前其偏移量为1,所以需要浪费3个字节。接着i在偏移量为4的位置占4的字节,此时c2的偏移量也为1,无论如何,都满足偏移量为1,所以接着向下存。但是此时为9,并不是正确答案,这是为什么呢?再看第三条规则:结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对⻬数中最⼤的)的 整数倍。此时,三个对齐数分别为1,1,4.。 最大对齐数为4,其整数倍为12。所以,我们还需要浪费3个字节。
如果我们想看我们分析的结果是否正确?如何做呢。
我们可以使用这个函数,来计算其所占内存空间。
#include<stddef.h> struct S1 { char c1; int i; char c2; }; int main() { printf("%d\n", sizeof(struct s1,c1)); printf("%d\n", sizeof(struct s1,i)); printf("%d\n", sizeof(struct s1,c2)); return 0; }
结果为0,4,8。
struct2为8。这也就表明,让占用空间的成员放在一起,会减小内存。
当s3放在s4内部,结果会是什么样呢?我们来看第四条规则: 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结(含嵌套结构体中成员的对⻬数)的整数倍。s3中最大对齐数为8。
这样就可以计算出此结构体的内存数为32。
内存对齐存在的原因
1移植原因。
2数据结构应尽可能在自然边界对齐。
如果像左侧一样,不存在空间浪费地存放。编译器每次读取4个字节,像左侧还要读取两次才能读取到i,如果像右侧,只需要读取一次,中间必须浪费一些空间,得到效率上提升。
修改默认对齐数
#pragma pack(8)//设置默认对齐数为8
#pragma pack()//取消默认对齐数,还原为默认
结构体传参
struct S { int data[1000]; int num; }; void print1(struct S ss) { int i=0; for(i=0;i<3,i++) { printf("%d",ss.data[i]); } printf("%d\n", s.num); } //结构体地址传参 void print2(struct S *ps) { int i=0; for(i=0;i<3;i++) { printf("%d\n", ps->data[i]); } } int main() { struct S s = {{1,2,3}, 1000}; //结构体传参 print1(s); //传结构体 print2(&s); //传地址 return 0; }
print1(s)为传值调用,print2(&2)为传址调用。 一个地址的大小为4或8字节,效果更好。如果print1(s)传地址过去给ss,ss是不会影响s的,ps可能误操作s,这种实现方式不够安全。在struct S*p2前加上const就可以避免这种情况。函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下 降。使用指针的时候,这种情况就不会出现。
结论: 结构体传参的时候,要传结构体的地址。
以上就是结构体的全部内容,欢迎交流!2024快来了,新的一年也要加油呀。