一、结构体类型的声明

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.结构体自引用的应用

数据结构中的链表

若有成员类型完全相同的结构体,分布在内存中的不同位置,但他们之间存在链式关系,根据上一个结构体,可以找到下一个结构体,我们可以通过自引用结构体本身的指针实现。

结构体_压栈_02

代码实现:

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;
}

结构体_结构体_03

3.结构体内存对齐的原因

结构体_结构体_04

4.减少结构体内存的方法

(1)调整成员变量的顺序,如把占内存大的类型放前面;把内存小的数据集中放置(对于s1,顺序可调为b,a,c)

(2)修改默认对齐数

#pragma pack(4) //设置默认对齐数为4
#pragma pack()  //取消设置的默认对齐数,还原为8

5.补充知识

利用offsetof函数查看偏移量

offsetof(struct S1,b) //检查结构体S1的成员b的偏移量