一、结构体类型的声明
1.正常结构体声明
下面声明两个结构体,一个为学生结构体student,一个为成绩单结构体transcript。transcript为student的成员之一。
struct称为“结构体关键字” ,student称为“结构体标签“,将二者合并struct student称为”结构体类型“,级别等同于int char等类型名
student里的name,age,grade叫做结构体的“成员”或“成员变量”
struct transcript {
short math;
short English;
short physics;
};
struct student {
char name[20];
short age;
struct transcript grade;
};
int main() {
return 0;
}
2.匿名结构体的声明
因为没有名称,匿名结构体只能在结构体声明处创建变量(该变量为全局变量),在其他地方无法使用。
struct {
int a;
char b;
}s1; //s1为全局变量
二、typedef重命名结构体
typedef为类型定义函数,可以把上述类型struct student 重命名为stu,以达到简化效果。
typedef struct transcript {
short math;
short English;
short physics;
}trs;
typedef struct student {
char name[20];
short age;
trs grade;
}stu;
int main() {
return 0;
}
三、结构体初始化
创建“结构体变量”s1,s2,进行初始化
int main() {
stu s1 = { "张三",20,{97,88,75} };
stu s2= { "李四",19,{92,73,67} };
return 0;
}
四、结构体成员访问
有两种访问方式
(1)直接用 . 来访问
int main() {
stu s1 = { "张三",20,{97,88,75} };
stu s2= { "李四",19,{92,73,67} };
printf("%s ", s1.name);
printf("%d ", s2.age);
printf("%d ", s1.grade.math);
return 0;
}
(2)通过创建结构体指针,用 -> 访问
int main() {
stu s1 = { "张三",20,{97,88,75} };
stu s2= { "李四",19,{92,73,67} };
stu* p_s1 = &s1;
stu* p_s2 = &s2;
printf("%s ", p_s1->name);
printf("%d ", p_s2->age);
printf("%d ", p_s1->grade.math);
return 0;
}
五、结构体传参即压栈简介
两种传参方式
(1)直接传整个结构体
void test(stu s1,stu s2) {};
int main() {
stu s1 = { "张三",20,{97,88,75} };
stu s2= { "李四",19,{92,73,67} };
test(s1, s2);
return 0;
}
(2)传递结构体指针
void test(stu* p_s1,stu* p_s2) {};
int main() {
stu s1 = { "张三",20,{97,88,75} };
stu s2= { "李四",19,{92,73,67} };
stu* p_s1 = &s1;
stu* p_s2 = &s2;
test(p_s1, p_s2);
return 0;
}
应优先选用指针传参
原因:函数传参时,参数在栈区进行压栈存储,直接传整个结构体,参数太多,存取时对系统开销较大,会导致性能下降。
六、结构体自引用
如果一个结构体的成员为与之相同的结构体,则称为结构体自引用
1.自引用结构体本身(错误做法)
struct node {
int data;
struct node next;
};
导致死循环,结构体node所占内存为无限大
2.自引用结构体本身的指针
struct node {
int data;
struct node* next;
};
此方法可行,因为对于32位系统,指针大小固定为4字节;对于64位系统,指针大小固定为8字节。所以该方法下的结构体node占用的内存为定值。
3.结构体自引用的应用
数据结构中的链表
若有成员类型完全相同的结构体,分布在内存中的不同位置,但他们之间存在链式关系,根据上一个结构体,可以找到下一个结构体,我们可以通过自引用结构体本身的指针实现。
代码实现:
struct node {
int data;
struct node* next;
};
七、结构体内存对齐
1.规则
(1)第一个成员在结构体地址偏移量为0的位置存储
(2)其他成员要对齐到对齐数(编译器默认的对齐数8和该成员类型所占字节大小的较小值)的整数倍位置存储
(3)结构体总大小为最大对齐数(每一个成员都有一个对齐数,取其中的最大值)的整数倍。
(4)如果存在结构体嵌套结构体的情况,嵌套结构体的对齐数为其内部的最大对齐数。
2.例题
struct S1 {
char a; //偏移量为0,表示从首地址处往下写
int b; //对齐数为4,空置上面3个位
char c; //对齐数位1
}s1;
struct S2 {
char a;
char b;
int c;
}s2;
struct S3 {
double a;
char b;
int c;
}s3;
struct S4 {
char a;
struct S3 b; //最大对齐数为8
double c;
}s4;
int main() {
printf("%d\n", sizeof(s1)); //12 ,本来是9,因为规则(3),该结构体应该是4的整数倍,所以为12
printf("%d\n", sizeof(s2)); //8
printf("%d\n", sizeof(s3)); //16
printf("%d\n", sizeof(s4)); //32
return 0;
}
3.结构体内存对齐的原因
4.减少结构体内存的方法
(1)调整成员变量的顺序,如把占内存大的类型放前面;把内存小的数据集中放置(对于s1,顺序可调为b,a,c)
(2)修改默认对齐数
#pragma pack(4) //设置默认对齐数为4
#pragma pack() //取消设置的默认对齐数,还原为8
5.补充知识
利用offsetof函数查看偏移量
offsetof(struct S1,b) //检查结构体S1的成员b的偏移量