目录
一. 结构体类型的声明
在我们前面的学习过程中,我们也提到过结构体,结构体是一些值的集合,这些值称为成员变量,而且每个成员可以是不同类型的变量。
1.1结构的声明
struct tag
{
member-list;//成员变量
}variable-list;变量列表
假如我们来描述一个学生:
struct student
{
char name[20];//名字
char sex[5];//性别
int age;//年龄
float grade;//成绩
};
像上面这样,我们的一个简单的结构体变量就已经创建好了,下面就讲讲我们如何使用以及如何初始化我们的结构体。
1.2 结构体变量的创建和初始化
struct student
{
char name[20];//名字
char sex[5];//性别
int age;//年龄
float grade;//成绩
};
int main()
{
struct student s1 = { "乐乐","女",18,98 };
printf("%s", s1.name);
printf("%s", s1.sex);
pritnf("%d", s1.age);
pritnf("%d", s1.grade);
return 0;
}
对于我们之前的学习,我们知道调用结构体的成员可以使用变量名加上. 的方式,或者示使用箭头的方法,上面的代码我们是按照结构体成员变量的顺序来写的,但是如果我们不想按照这样的顺序,可以试试这样来写:
int main()
{
struct student s2 = { .age = 18, .name = "lisi", .grade = 98, .sex = "女" };
printf("%s ", s2.name);
printf("%s ", s2.sex);
printf("%d ", s2.age);
printf("%d ", s2.grade);
return 0;
}
综合上面的内容,我们可以总结很多比如,在C语言中,字符串字面量可以直接初始化字符数组,但是整数类型不能用这种方式初始化,我们就直接写18、98等,另外也要注意我们的潜在的缓冲区溢出风险,就比如我们的char name[20],我们可以考虑适当增加数组的大小以避免潜在的缓冲区溢出风险。还有就是如果我们不按照结构体成员顺序来写的话,我们记得在初始化的时候要用对方式,以上就是我们结构体的简单介绍,下面我们来继续学习新的内容。
1.3 结构的特殊声明
什么是结构体的特殊的声明?其实也很容易理解,比如:
struct
{
int a;
char b;
int c;
}s1,s2;
struct
{
int a;
char b;
int c;
}*pa;
上面的代码我们也很容易发现它缺少了结构体名称,也就是说我们不能再像往常一样使用结构体创建多个变量,而只能使用我们在创建结构体时候加上的变量,比如上述代码中的s1,s2,我们再设想假如这个时候我们写pa=&s1;是否正确呢,答案是不正确的,因为编译系统会认为这是两个不一样类型的内容,即使它们是一样的结构体,所以匿名的结构体类型,如果没有重新命名的话,基本上只能使用一次。
1.4 结构的自引用
![](https://i-blog.csdnimg.cn/direct/fc0875743c474cda9631d154d3829982.png)
如上图所以,我们如果要找到每一个数字的位置,我们就需要一个链表的东西,我们能够继续找到下一个数字的位置,现在我们假设代码这样去写:
struct Node
{
int data;
struct Node next;
};
仔细观察上面的代码是正确的吗?如果认为正确那么sizeof(struct Node)的大小是多少呢?结果表明它是无穷大的,计算不出来,所以正确的自引用应该是使用指针的方式来找到下一个数字的位置:
struct Node
{
int data;
struct Node* next;
};
在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引入问题,看看 下面的代码,可行吗?
typedef struct
{
int data;
Node* next;
}Node;
typedef struct Node
{
int data;
struct Node* next;
}Node;
可能我们会想不是已经给struct Node重命名为Node了吗,为什么下面还是要用struct Node,原因也是很简单的,我们认真观察我们使用Node重命名是针对整个结构体来说的,那么在这个结构体中原本就先包含的是struct Node这个整体,所以如果我们在结构体中直接使用了Node是很危险的。
1.5 结构体内存对齐
什么是结构体的内存对齐呢?在认识这个之前我们要先学习计算结构体的大小,也是很热门的一个考点就是结构体内存对齐。对齐规则:
#include <stdio.h>
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%zd\n", sizeof(struct S1));
printf("%zd\n", sizeof(struct S2));
return 0;
}
在上面的代码中我们大胆先自己计算一下,那么我们自己来计算的话S1和S2的大小应该是多少呢?可能大家会想这不就是6吗?我们来看一下结果:
这就奇怪了竟然不是6,而且两个结果竟然还不一样,究竟是为什么呢?这就要说说我们的内存对齐规则了,那么究竟是怎样去理解和使用的呢?先给大家一张图,方便我的后面讲解: 在这张图的基础上我来给大家讲解,首先我们要认识一个宏——offsetof,offsetof(type,member)头文件是#include <stddef.h>它是计算结构体成员较于结构体变量起始位置的偏移量:
#include <stddef.h>
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%zd\n", offsetof(struct S2,c1));
printf("%zd\n", offsetof(struct S2, c2));
printf("%zd\n", offsetof(struct S2, i));
return 0;
}
也就是说我们的c1相较于起始位置偏移量为0,c2为1,i为4,这中间有什么原理吗,其实很简单我们来看对齐规则的第一条:结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处,所以我们的c1偏移量是0,而对于c2来说我们看第二条:其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值。 比如:VS 中默认的值为 8。VS中的对齐数是8,而8和char类型的1比较,自然是1小,所以选择1,那么1的倍数可以是任何数,所以对应上面的图,也就是橙色的,我们的第二个变量c2可以直接在c1下面直接挨着,但是对于i来说,int类型的4也是较小值,但是必须对齐为4的倍数,所以要选择偏移量为4的倍数,所以橙色的整个占了8个字节,也就是我们得出的结果8,但是为什么红色的占了9个字节,但是结果确是12呢?我们来看第三条:结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。在S1中int的类型是4个字节但是实际占了9个字节,但是不是4的倍数,所以只能是4的3倍,所以结果就是12。