一、结构体的声明
说到结构体,就不得不说一下数组,数组的类型有整型、字符型、浮点型等等。在数组中,所有的数据,数据类型是一样的。结构体它的数据,数据类型可以不一样,可以整型、浮点型、字符型等等。下面我们代码演示。
//创建一个关于学生信息的结构体
struct Stu//Stu这个是结构体标签
{
char name[20]; //这是定义了一个学生的姓名
int age; //这是定义了一个学生的年龄
}s1,s2; //s1,s2是定义的结构体变量
//struct Stu这个是结构体的类型
//大括号后面的;不能丢
int main()
{
return 0;
}
这就是一个结构体的声明,下面介绍一下匿名结构体。什么是匿名结构体呢?简而言之就是没有名字。
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], * p;
//上面就是两个匿名结构体,他们都没有结构体标签。
//p=&x这样是不可取的,编译器会报错,两个结构体类型不同
//在使用的时候,创建结构体变量时只能在大括号后分号前定义
二、结构体变量的定义及初始化
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
typedef struct Point
{
int x;
int y;
}Point;
Point p3; //重命名定义
下面来讲初始化
typedef struct Stu
{
char Name[20]; //姓名
int Age; //年龄
char Sex[5]; //性别
}Stu; //重命名为Stu
int main()
{
Stu s1 = { "zhangsan",16,"man" }; //定义一个结构体变量s1并初始化
struct Stu s2 = { "cuihua",17,"nv" };//定义一个结构体变量s2并初始化
printf("%s %d %s\n", s1.Name, s1.Age, s1.Sex);
printf("%s %d %s\n", s2.Name, s2.Age, s2.Sex);
return 0;
}
三、结构体的自引用
从标题中就可以理解“自引用”,就是在结构体里面把这个结构体再放进去,类似于函数的递归,下面我们代码讲解。
struct Node1
{
int data;
struct Node1 next;
};
//sizeof(struct Node1)的大小是多少吗?
//我们来分析这个,首先结构体成员有int data和自用的变量next
//但是这个变量next还包含了int data和自用的变量next
//变量next还包含了int data和自用的变量next
//。。。。。。依次类推
//像是一个无限递归
//所以这种自引用是犯法的!!!
//那怎么才可以合法的使用自引用呢?
//我们可以使用指针来指向下一个变量next
//而指针的大小是确定的!!
//看下面这个就是合法的自引用
struct Node2
{
int data;
struct Node2* next;
};
注意一下情况:
typedef struct
{
int data;
Node* next;
}Node;
//上述代码是否可行?
//答案:不行
//原因:首先typedef对结构体进行了重命名为Node
//重命名完成是在大括号后面的分号,才算重命名完可以使用
//在自引用时使用的Node是不可以的,因为编译器不知道Node是什么
//下面为改好的代码
typedef struct Node
{
int data;
struct Node* next;
}Node;
四、结构体内存对齐
结构体内存对齐是指在为结构体分配内存时,成员变量并非简单地按照顺序依次排列,而是会遵循一定的规则进行排列,以提高内存访问效率和硬件的兼容性。
具体的内存对齐规则如下:
- 第一个成员在与结构体变量偏移量为0的地址处(即结构体的首地址处,对齐到0处);
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数等于编译器默认的一个对齐数与该成员大小的较小值。在 Visual Studio 中默认对齐数为8;在 Linux 环境默认不设对齐数,此时对齐数就是结构体成员自身的大小;
- 结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍;
- 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
简而言之的理解就是,计算结构体的大小,以及在内存中如何存储。
首先我们看上面的图片,我们可以发现两个结构体的成员是一样的,但是他们的排序不一样,导致的结构体大小都不一样,这是为什么呢?
1. 第一个成员在与结构体变量偏移量为0的地址处(即结构体的首地址处,对齐到0处)
(偏移量就是结构体开始地址和成员地址之间的大小)
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数等于编译器默认的一个对齐数与该成员大小的较小值。在 Visual Studio 中默认对齐数为8;在 Linux 环境默认不设对齐数,此时对齐数就是结构体成员自身的大小;
成员的对其数即成员大小
3.结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
struct S1 s1; //嵌套结构体后大小为多少呢?
};
int main()
{
printf("%d\n", sizeof(struct S2));
return 0;
}
4. 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
(就是结构体内的所有成员对齐数包括嵌套的结构体内所有成员的最大的对齐数)
//刚才演示了struct S1的大小字节为12;
//在struct S2中包含char c1,char c2,struct S1 s1(按顺序)
//又因为char c1和char c2的对齐数是1,所以12+2=14个字节
//
//如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
// 结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
//在struct S2中最大的对齐数为4,14不是4的倍数,往后走到16字节,
//所以大小为16字节
为什么存在内存对齐 ?
大部分的参考资料都是如是说的:
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
五、结构体传参
typedef struct U
{
int x;
int y;
}U;
struct S
{
int data[1000];
int num;
U u;
};
struct S s = { {1,2,3,4}, 1000 ,{2,3} };
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
printf("%d\n", s.u.x);
printf("%d\n", s.u.y);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
printf("%d\n", ps->u.x);
printf("%d\n", ps->u.y);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
六、修改默认对齐数
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}