目录:
1、结构体类型的声明
2、结构体的变量的创建和初始化
3、结构体内存的对齐
4、结构体实现位段
一、 结构体的声明
在声明结构体的时候,可以不完全的声明
struct
{
int a;
char b;
float c;
}s;
struct
{
int a;
char b;
float c;
}* ps;
int main()
{
ps = &s;
//这种写法是有问题的,虽然上面两个匿名结构体类型成员一摸一样,
// 但是在编译器看来,是两个不同的类型
return 0;
}
二、结构体的自引用
struct Node
{
int data;
struct Node* next; // 👈 这就是自引用
};
重点:
必须用指针 struct Node,不能用 struct Node!
为什么要用自引用?(用来做链表!)
你可以把它理解成:
火车车厢
- 每节车厢 = 一个结构体
- 车厢里有一个 "钩子" = 指针
- 钩子钩住下一节车厢
这就是链表 !结构体自引用 = 造链表的核心。
结构体自引用 = 结构体内部包含一个指向自己的指针
作用:用来造链表、二叉树、图等复杂数据结构!
三、结构体内存对齐
结构体成员默认情况下不一定是连续存放的,由下面这段代码可以证明:
struct s1
{
char c1;
int i;
char c2;
};
struct s2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%zu\n", sizeof(struct s1));
printf("%zu\n", sizeof(struct s2));
return 0;
}
为什么要对齐?
为了让 CPU读得更快 。结构体不是挨个紧挨着存的,而是按规则排队、有空位。
结构体对齐规则
1. 第一个成员 从偏移 0 开始
2. 从第二个成员开始
对齐到 它自己大小 的整数倍地址
- char (1) → 对齐到 1 的倍数
- int (4) → 对齐到 4 的倍数
- double (8) → 对齐到 8 的倍数
3. 结构体总大小
必须是 最大成员类型大小 的整数倍
4. 如果有嵌套结构体
嵌套的内部成员对齐到 自己最大成员的大小
四、结构体的位段
位段类型:
:冒号 后面加上 数字 (这个数字表示这个成员需要占用多少个二进制位)
当剩余空间不够时,会新开一个单元
放不下就重新开一块空间,不跨单元存放
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
}; // 当你这么设计位段成员的时候,就意味着这些成员的长度足够自己使用
位段的内存分配
为什么要用位段?
省空间!
- 正常 int 要 4 字节(32 位)
- 但很多数据根本用不到那么多位比如:性别(0/1)、状态(0~7)
用位段可以把多个变量挤在同一个字节里。
位段的特点(超级重要)
- 节省内存
- 不能取地址 & (原因:位段的几个成员共有同一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的,所以不能对位段成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员)
- 不能跨平台,移植性差
- 不能用数组
- 主要用于协议、硬件寄存器、网络包等对空间要求极高的地方
位段分配内存的规则
- 位段是按 "位" 分配,不是按字节
- 先从低位往高位填(从右往左填)
- 当前单元剩下的位不够放 → 浪费掉,新开一个单元
- 单元大小由 "位段的类型" 决定
int型位段 → 单元 = 4 字节(32 位)char型位段 → 单元 = 1 字节(8 位)
