- 结构体的声明
- 结构体的定义
- 结构体的声明
- 结构体的特殊声明
- typedef的使用(结构体别名)
- 结构体的自引用
- 结构体变量的初始化
- 结构体内存对齐
- 结构体对其规则
- 试题
- 题目一
- 题目二
- 题目三
- 修改默认对齐数
- 结构体传参
- 代码
结构体是一些值得集合,这些值被称为成员变量。成员变量可以是不同类型的值。
结构体的声明struct tag
{
member_list;
}variable_lest;
struct student
{
char name[20];
int age;
char id[15];
}s;
结构体的特殊声明
此种声明又可以称为结构体的不完全声明,或叫做匿名结构体类型。
结构体声明时可以去掉结构体标签(tag);
如
struct
{ char name[20];
int age;
char id[15];
}s;
/*注:此种声明创建变量时,只能在variable_list处创建,且为全局变量,不能再其他地方创建新的变量。
typedef的使用(结构体别名)我们如果觉得在定义结构体变量时,书写过于麻烦,我们可以给它定义一个新的名称。
//typedef的使用
typedef struct student
{
char name[20];
int age;
char id[20];
}stu;
int main()
{
stu s1 = { "lisi", 30, "201817" };
printf("%s %d %s\n", s1.name, s1.age, s1.id);
- 这里我们就可以用 stu 来代替 struct student ,这样就方便结构体变量的创建;
- 但是这样我们无法直接在结构体后面创建变量,取而代之的是结构体的别名;
- 如果存在结构体嵌套的问题,我们在定义结构体时,是不使用别名的(要用全名),只有在定>义完结构体后,我们才能使用结构体。
结构体的自引用
在结构体中包含一个类型为该结构体本身的成员。
struct student
{
char name[20];
int age;
struct student* s;
};
/*注:这里我们不能直接把结构体放进去,如果这样做将导致结构体嵌套,结构体s里又有个结构体,这样无限嵌套,无限循环下去,同时也无法计算大小。
结构体变量的初始化
#include
struct student
{
char name[20];
int age;
char id[15];
}s1={"wangwu", 20, "201817"};
int main()
{
struct student s2={"lisi", 21, "201816"};
printf("%s %d %s\n", s1.name, s1.age, s1.id);
printf("%s %d %s\n", s2.name, s2.age, s2.id);
return 0;
}
结构体内存对齐
结构体的内存对齐,有利于对数据进行存储和读取,但是会浪费相对的空间。这导致结构体成员的大小之和不一定等于结构体总大小。
为什么结构体对齐?
结构体对其规则平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台在某些地址处访问访问某些特定类型的数据,否则抛出硬件异常。
性能原因
数据结构(尤其栈)应当尽可能的在边界上对齐。
原因在于为了访问未对齐的内存,处理器需要做两次内存访问,;而对齐的内存只需访问一次。
试题
- 第一个成员与结构体变量偏移量为0的地址处。
- 其他结构体成员变量要对其到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小 的较小值。- 结构体总大小为最大对齐数(每个结构体成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍。
我们计算下面结构体的大小。
题目一//结构体内存对齐的问题
//默认对齐数是8
struct s
{
char a;
int b;
char c;
int d;
}s1;
int main()
{
printf("%d\n", sizeof(s1));
return 0;
}
结果展示
题目解析
s是结构体起始的位置,以s开始计算偏移量。
a的对齐数为1(自身的大小)或8(默认对齐数)中的较小值,即为1,0是1的整数倍,放在偏移量为0的地方,占1个字节;
b的对齐数是4或8较小值为4,找偏移量为4的倍数处,把b放在其中,占4个字节,前面浪费3字节;
c的对齐数是1或8较小值为1,找偏移量为1的倍数处,把c放入其中,占1个字节,前面不浪费空间;
d的对齐数是4或8较小值为4,找偏移量为4的倍数处,把d放入其中,占4个字节,前面浪费3字节;
此时的总大小为16刚好为最大偏移量的整数倍,所以结构体总大小为16个字节。
//试题二
//默认对齐数是8
struct s1
{
char a;
int b;
char c;
int d;
}s1;
struct s2
{
int a;
int b;
char c;
char d;
}s2;
int main()
{
printf("%d\n", sizeof(s1));
printf("%d\n", sizeof(s2));
return 0;
}
结果展示
题目解析
我们发现其实 s1 和 s2 成员变量其实类型和数量是相同的(两个 char 类型和两个 int 类型);
但是两个结构体所占的空间大小不同,我们重点分析一下 s2;
a的对齐数是4,在偏移量为0处,占4个字节;
b的对齐数是4,在偏移量为4处,占4个字节,前面浪费0个空间;
c的对齐数是1,在偏移量为8处,占1个字节,前面浪费0个空间;
d的对齐数是1,在偏移量为9处,占1个字节,前面浪费0个空间;
此时 s2 的最大偏移量为4,所以所占总空间为12个字节。
我们发现把相同类型数据放到一起会节省部分空间,从而提高代码的质量。
//试题三
//默认对齐数为8
struct s1
{
int a;
int b;
double c;
}s1;
struct s2
{
char a;
int b;
struct s1 s1;
}s2;
int main()
{
printf("%d\n", sizeof(s2));
return 0;
}
结果展示
题目解析
我们知道 s1 所占空间为8个字节;
s2前两个结构体成员变量所占大小为8个字节;
第三结果提成员变量为另一个结构体,这个结构体最大对齐数为8;
s2总大小为16 + 8 = 24个字节,刚好为最大对齐数的8 (这个8来源于结构体s1的最大对齐数)的倍数。所以总大小为24个字节。
我们能使用 pragma 这个预处理指令来修改默认的对齐数。
//对齐数的修改
#pragma pack(4)//设置默认对齐数为4
struct s
{
char a;
double b;
}s;
int main()
{
printf("%d\n", sizeof(s));
return 0;
}
结果展示
这里我们把VS2019平台的默认对齐数8(每个平台默认对齐数不一样)改为了 4;
由此我们计算出结构体 s 的大小为12个字节。
结构体传参
当我们需要把结构体当作参数传递给函数时,我们需要结构体传参。
这里我建议把结构体的地址当参数传递给函数,这样就会避免参数压栈导致性能下降的问题。
//结构体传参
struct s {
int a;
int b;
}s1;
void scanf_s1(struct s* s1)
{
scanf("%d %d", &(s1->a), &(s1->b));
}
void printf_sum(struct s* s1)
{
printf("%d\n", s1->a + s1->b);
}
int main()
{
scanf_s1(&s1);
printf_sum(&s1);
return 0;
}
结果展示
代码
本片所有代码供大家使用。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)