写 C 语言的时候char、int、float 这些都是基础类型。但现实世界中的信息往往是成套出现的一个学生有姓名、年龄、性别、学号一本书有书名、作者、价格。总不能单独定义一堆零散变量name1、age1、sex1……写起来乱糟糟还容易搞混。结构体struct就是专门用来把多种不同类型的数据打包到一起的自定义类型。一、结构体怎么写声明与初始化先来看结构体的语法模板struct 结构体标签名 { 成员1; 成员2; 成员3; }; //⚠️最后的分号千万不能丢举个例子用结构体描述一名学生struct Stu { char name[20]; //名字 int age; //年龄 char sex[5]; //性别 char id[20]; //学号 };定义变量和初始化有两种方式按成员顺序初始化struct Stu s {张三, 20, 男, 202601001};指定成员初始化顺序可以随便写C99 支持struct Stu s2 {.age18, .name李四, .sex女, .id202601002};访问结构体成员时普通变量用.点操作符如果是结构体指针则用-箭头操作符。printf(%s\n, s.name); //结构体变量 . 成员 struct Stu* ps s; printf(%d\n, ps-age); //结构体指针 - 成员匿名结构体用完即走声明时不写标签名就是匿名结构体。struct { int a; char b; float c; } x;⚠️坑两个长得一模一样的匿名结构体编译器会当成完全不同的两种类型既不能互相赋值指针也不能互相指向。匿名结构体一般只用一次不适合复用。结构体自引用写链表必会很多同学写链表时会犯下面这个错误//错误写法 struct Node { int data; struct Node next; //结构体里面放一个完整自己大小无限递归 };结构体内部不能放自身类型的完整变量否则大小根本算不出来。✅正确做法是存放结构体指针因为指针大小是固定的。struct Node { int data; struct Node* next; };用 typedef 重命名时也要注意不要用匿名结构体做自引用此时 typedef 还没生效结构体内部识别不到新名字。//不行Node还没定义出来 typedef struct { int data; Node* next; } Node; //标准正确写法 typedef struct Node { int data; struct Node* next; } Node;二、重点难点结构体内存对齐笔试高频问下面两个结构体成员完全一样sizeof大小却不一样struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; };在 VS 下sizeof(S1)12sizeof(S2)8。成员一模一样只是顺序不同占用的内存大小就不一样这就是内存对齐在起作用。四条对齐规则记牢做题无敌结构体第一个成员放在偏移为 0 的地址。后面每一个成员都要对齐到对齐数的整数倍地址。对齐数 编译器默认对齐数和该成员自身大小中较小的那一个。VS 默认对齐数是 8Linux GCC 没有默认对齐数对齐数就等于成员自身大小。结构体总大小必须是结构体“最大对齐数”的整数倍不够就补填充字节空洞。如果结构体里面嵌套了另一个结构体嵌套的结构体要对齐到它自身最大对齐数的整数倍整体大小则是全部最大对齐数的整数倍。演算 S1char c1; int i; char c2;c1char 占 1 字节偏移 0。iint 大小 4对齐数 min(8,4)4要放在 4 的倍数地址上。0 地址已被占用中间补 3 个空洞i 放在偏移 4。c2char 占 1 字节放在偏移 8。此时成员占用到 9 字节。最大对齐数是 4总大小必须是 4 的倍数9 向上取整到 12所以sizeof(S1)12。S2char c1; char c2; int i;c1 偏移 0c2 偏移 1i 对齐数 4放到偏移 4占用到 7 字节向上对齐到 8所以sizeof(S2)8。小技巧写结构体想省内存就把占用字节小的成员尽量放在一起减少填充空洞。修改默认对齐数#pragma pack我们也可以手动修改编译器的默认对齐数。#pragma pack(1) //设置默认对齐数为1 struct S { char c1; int i; char c2; }; #pragma pack() //取消设置恢复编译器默认pack(1) 的时候对齐数全部为 1不再有空洞上面这个结构体的大小就是 6。一般业务代码不建议随便改 pack只有驱动、协议解析这类场景才会用到。为什么要有内存对齐拿空间换时间硬件平台限制有些 CPU 硬件不支持从任意地址读取 4 字节 int地址不对齐会直接触发硬件报错。访问性能提升如果数据没对齐CPU 需要分两次内存读取再拼接数据对齐之后一次就能读完。简单讲牺牲一点点空闲的填充字节换取读取速度和硬件兼容性。三、结构体传参尽量传地址不要传整个结构体struct S { int arr[1000]; int num; }; //直接传整个结构体不推荐 void print1(struct S s) { printf(%d, s.num); } //传结构体的地址推荐 void print2(struct S* ps) { printf(%d, ps-num); }调用方式如下struct S s; print1(s); //把整个结构体拷贝一份压入栈如果结构体很大栈开销巨大 print2(s); //仅仅压入一个8字节/4字节指针开销极小结论结构体传参优先传递指针。如果函数内部不想修改原结构体可以写成const struct S* ps保护内容不被修改。四、结构体延伸位段bit-field位段是结构体的特殊玩法可以直接按 bit 位分配空间。冒号后面写数字表示这个成员占用多少 bit。struct A { int _a :2; int _b :5; int _c :10; int _d :30; };位段要点位段成员必须是整型家族char、int、unsigned int。空间开辟按int(4字节)或者char(1字节)为单位开辟内存。能塞就塞塞不下就新开一块空间。❗位段跨平台不具备可移植性到底是从高位往低位放 bit还是从低位往高位放C 标准没有强制规定VS 和 GCC 的行为并不一样。剩余的 bit 是丢弃还是复用标准同样未定义。位段适合做网络协议、硬件寄存器解析追求跨平台的业务代码尽量避开位段。还有一个坑bit 位没有内存地址不能取地址 struct A sa; scanf(%d, sa._b); //❌错误不能对位段取地址正确做法是先读到临时变量再赋值给位段成员。int temp; scanf(%d, temp); sa._b temp;本篇快速总结struct 结构体可以把不同类型的成员打包在一起用.访问成员指针则用-。结构体自引用只能放同类型指针不能放完整的结构体变量。内存对齐 4 条规则是计算 sizeof 结构体的核心小成员放在一起可以节省内存。结构体传参优先传指针减少栈拷贝开销。位段可以按 bit 使用内存、节省空间但跨平台行为不确定也不能用 取地址。