

前言
结构体是C语言实现数据封装的核心工具,广泛应用于复杂对象描述(如学生信息、链表节点)与底层开发(如协议解析)。本文将从语法基础到性能优化,提炼核心知识点与实战技巧,帮你高效掌握这一高频考点与开发利器。
一、结构体的声明及初始化
1.1 声明与变量定义
结构体声明的核心格式:
struct 标签 { 成员列表 } [变量列表];(分号不可省略)
c
// 标准声明(推荐,可复用)
struct Stu {
char name[20]; // 名字(数组避免指针悬空)
int age; // 年龄
char id[20]; // 学号
};
// 变量创建:全局/局部均可
struct Stu s1; // 局部变量
struct Stu s2[10]; // 结构体数组(批量存储)
1.2 初始化
-
顺序初始化 :按成员顺序赋值,简洁直接:
cstruct Stu s = {"张三", 20, "20230818001"}; -
指定成员初始化 :跳过无关成员,灵活适配于部分赋值场景:
cstruct Stu s = {.age = 18, .name = "李四"}; // 未赋值成员默认初始化为0/空
1.3 关键点
-
匿名结构体 :仅能使用一次,多次声明会被视为不同类型(慎用):
cstruct { int a; char b; } x; // x是唯一变量,无法再定义同类型变量 -
自引用正确写法 :必须用指针(避免无限递归内存占用):
c// 错误:struct Node未定义完成,直接用类型会报错 struct Node { int data; struct Node next; }; // 正确:指针仅占4/8字节,无需知道结构体完整大小 struct Node { int data; struct Node* next; }; -
typedef重命名 :结合标签使用,避免匿名结构体的局限:
ctypedef struct Node { int data; struct Node* next; } Node; // 可直接用Node代替struct Node
二、结构体内存对齐
内存对齐是"空间换时间"的优化策略,计算结构体大小需遵循以下规则:
2.1 对齐规则 (VS默认对齐数8,GCC无默认对齐数,按成员最大大小对齐)
- 第一个成员从偏移量
0开始。 - 其他成员对齐到「对齐数」的整数倍地址(对齐数=min(编译器默认对齐数, 成员大小))。
- 结构体总大小为「所有成员对齐数最大值」的整数倍。
- 嵌套结构体时,嵌套部分对齐到自身最大对齐数的整数倍。
2.2 实战计算(VS环境)
| 结构体定义 | 成员偏移与大小 | 总大小 | 优化点 |
|---|---|---|---|
struct S1 { char c1; int i; char c2; } |
c1(0,1)→i(4,4)→c2(8,1) | 12 | 小成员分散,浪费空间 |
struct S2 { char c1; char c2; int i; } |
c1(0,1)→c2(1,1)→i(4,4) | 8 | 小成员集中,节省4字节 |
struct S3 { double d; char c; int i; } |
d(0,8)→c(8,1)→i(12,4) | 16 | double占8字节,对齐到8 |
struct S4 { char c1; struct S3 s3; double d; } |
c1(0,1)→s3(8,16)→d(24,8) | 32 | 嵌套结构体对齐到自身最大对齐数(8) |
2.3 对齐优化技巧
-
成员排序原则:小成员集中存放(如S2 vs S1)。
-
修改默认对齐数 :用
#pragma pack(n)调整(n=1时取消对齐)。c#pragma pack(1) // 强制按1字节对齐 struct S { char c1; int i; char c2; }; // 总大小=1+4+1=6 #pragma pack() // 还原默认
三、结构体传参
3.1 传参方式对比
| 传参方式 | 实现代码 | 性能开销 | 适用场景 |
|---|---|---|---|
| 传值调用 | void print(struct Stu s) |
拷贝整个结构体(栈空间+时间) | 结构体极小(<16字节) |
| 传址调用 | void print(struct Stu* ps) |
仅传指针(4/8字节) | 绝大多数场景(推荐) |
3.2 代码示例
c
// 高效写法:传址+const保证只读
void printStu(const struct Stu* ps) {
printf("姓名:%s,年龄:%d\n", ps->name, ps->age); // 用->访问成员
}
int main() {
struct Stu s = {"张三", 20, "20230818001"};
printStu(&s); // 传递地址
return 0;
}
四、位段的使用与实战
位段是结构体的"空间压缩版",通过指定成员占用的二进制位数,大幅减少内存占用(适用于硬件寄存器、网络协议等场景)。
4.1 位段声明与大小计算
c
// 位段声明:成员为整数类型,后接:位数
struct A {
int a:2; // 占2bit(范围:-2~1或0~3,取决于有符号)
int b:5; // 占5bit
int c:10; // 占10bit
int d:30; // 占30bit
}; // 总大小:2+5+10+30=47bit → 占2个int(8字节)
4.2 核心限制与注意事项
-
成员类型仅限
int/unsigned int/char。 -
位数不能超过类型总位数(如char不能超过8bit)。
-
无独立地址,不能用
&取地址(需通过中间变量赋值)。cstruct A sa; // 错误:&sa.a非法 // scanf("%d", &sa.a); // 正确 int tmp = 0; scanf("%d", &tmp); sa.a = tmp;
4.3 实际应用:IP头位段定义
c
// IPv4头部的位段实现(简化版)
struct IpHeader {
unsigned int version:4; // 版本(4bit)
unsigned int ihl:4; // 首部长度(4bit)
unsigned int tos:8; // 服务类型(8bit)
unsigned int total_len:16; // 总长度(16bit)
// ... 其他字段
}; // 仅用4字节存储多个字段,比普通结构体节省空间
结构体作为C语言中连接基础数据类型与复杂数据结构的桥梁,从简单的数据封装到底层的内存优化,每一处细节都体现着"效率"与"灵活"的平衡。无论是应对面试中的内存计算考题,还是在项目中设计高效的协议解析、数据存储模块,掌握这些知识点都能让你事半功倍。如果在实际开发中遇到结构体的疑难问题(比如特殊场景的内存对齐优化、位段的跨平台适配),不妨回头再梳理这些规则,看似琐碎的细节,恰恰是写出高性能、高可读性代码的关键。
至此,我们已梳理完"数据在内存中的存储"的全部内容了。最后我们在文末来进行一个投个票,告诉我你对哪部分内容最感兴趣、收获最大,也欢迎在评论区聊聊你的学习感受。
以上就是本期博客的全部内容了,感谢各位的阅读以及关注。如有内容存在疏漏或不足之处,恳请各位技术大佬不吝赐教、多多指正。

