目录
一、结构体的声明
1.1.结构的基础知识
概念:结构是一些值的集合,这些值称为结构变量。结构的每个成员可以是不同类型的变量。
1.2.结构的声明
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};//一定要有分号
1.3.结构成员的类型
结构的成员可以是标量、数组、指针,甚至是其他结构体(嵌套)
1.4.结构体变量的定义和初始化
以下是4种定义结构体变量的形式:
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
struct Point p3 = {x, y};//初始化:定义变量的同时赋初值
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
局部与全局的结构体变量:
struct Stu
{
//结构体成员
char name[20];
int age;
char sex[10];
float score;
} s4,s5;//s4, s5是结构体变量 - 全局的
struct Stu s6;//全局的
int main()
{
struct Stu s1, s2, s3;//s1,s2,s3是结构体变量的 - 局部的
return 0;
}
二、结构体成员的访问
结构体变量访问成员 结构变量的成员是通过点操作符(.)访问的
点操作符接受两个操作数
我们可以看到 s1和s2 有成员 name 、 age 等; 那我们如何访问s1,s2的成员?
#include <stdio.h>
struct Stu
{
//结构体成员
char name[20];
int age;
char sex[10];
float score;
};
int main()
{
struct Stu s1 = {"zhangsan", 20, "nan", 95.5f};
struct Stu s2 = { "旺财", 21, "保密", 59.5f };
printf("%s %d %s %.1f\n", s2.name, s2.age, s2.sex, s2.score);
return 0;
}
打印结果:
结构体指针访问指向变量的成员,有时候我们得到的不是一个结构体变量
而是指向一个结构体的指针,那该如何访问成员呢?
#include <stdio.h>
struct Stu
{
char name[20];
int age;
};
void Print(struct Stu* ps)
{
//使用结构体指针访问指向对象的成员
printf("name = %s age = %d\n", (*ps).name, (*ps).age);
printf("name = %s age = %d\n", ps->name, ps->age);
}
int main()
{
struct Stu s = { "zhangsan", 20 };
Print(&s);//结构体地址传参
return 0;
}
打印结果:
注意:
此处的(*ps).name不能写成*ps.name因为(.)操作符的优先级很高,编译器会理解为*(ps.name)
三、结构体传参
结构体传参的两种形式:
#include <stdio.h>
struct S
{
int data[1000];
int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
打印结果:
对于上面的print1函数和print2函数,分别是传值调用和传址调用,谁是更优选呢?
答案:首选print2函数
原因是:
函数传参的时候,参数是需要压栈的。 如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大(相当于重新拷贝一份结构体),所以会导致性能的下降
四、结构体对齐(计算结构体的大小)
结构体的所占内存空间的大小不是将其中每个元素所占内存相加得到,而是遵循严格的规则
来看以下示例:
typedef struct s1
{
char a;
char b;
int c;
}s1;
typedef struct s2
{
char a;
int b;
char c;
}s2;
int main()
{
printf("%zd\n", sizeof(s1));
printf("s1,char a=%zd\n", offsetof(s1, a));
printf("s1,char b=%zd\n", offsetof(s1, b));
printf("s1,int c=%zd\n\n", offsetof(s1, c));
printf("%zd\n", sizeof(s2));
printf("s2,char a=%zd\n", offsetof(s2, a));
printf("s2,int b=%zd\n", offsetof(s2, b));
printf("s2,char c=%zd\n\n", offsetof(s2, c));
return 0;
}
宏:offsetof是用来计算结构体成员和该结构体0偏移处的距离,需要包含<stddef.h>头文件
为了搞懂上面的结果,必须要理解结构体对齐的规则
1.结构体的第一个成员,对齐到结构体在内存中存放位置的0偏移处
2.从第二个成员开始,每个成员都要对齐到(一个对齐数)的整数倍处对齐数:结构体成员自身大小和默认对齐数其中的较小值
VS:默认对齐数数8
Linux gcc:没有默认对齐数,对齐数就是结构体成员的自身大小
3.结构体的总大小,必须是所有成员的对齐数中最大对齐数的整数倍
4.如果结构体中嵌套了结构体成员,要将嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处。结构体的总大小必须是最大对齐数的整数倍
(这里的最大对齐数是:包含嵌套结构体成员中的对齐数后,所有对齐数中的最大值)
这里我用excel表格画了结构体s1和结构体s2在内存中的分布,帮助理解
接下来再引用一个结构体嵌套的例子:
通过结构体对齐规则去理解上面两个例子,可以让我们快速的记住这个规则