结构体与联合体:C语言的数据组织艺术
各位同学,今天我们来聊聊C语言中一个非常实用的特性——结构体与联合体。说实话,我刚开始学C语言时,觉得结构体不就是把几个变量打包在一起吗?有什么了不起的。直到我在一个嵌入式项目中,需要管理几十个传感器的数据,才真正体会到结构体的威力。
结构体的定义与使用
结构体,说白了就是自定义的数据类型。你可以把不同类型的数据组合在一起,形成一个逻辑上的整体。比如你要描述一个学生,有姓名、年龄、成绩,用单独变量太零散,用结构体就清爽多了。
// 定义一个学生结构体
struct Student {
char name[20];
int age;
float score;
};
// 使用结构体
struct Student stu1;
strcpy(stu1.name, "张三");
stu1.age = 18;
stu1.score = 92.5;
我个人习惯在定义结构体时,把相关的成员放在一起,这样阅读代码时一目了然。你想想看,如果成员顺序乱糟糟的,维护起来多痛苦。
结构体数组
一个学生不够?那就来一群。结构体数组就是用来处理批量数据的。我在做智能家居项目时,就用结构体数组管理了32个传感器的状态,每个传感器有ID、类型、数值、报警阈值等字段。
// 定义结构体数组
struct Student class[50];
// 初始化
for (int i = 0; i < 50; i++) {
class[i].age = 18 + i % 5;
class[i].score = 60 + rand() % 40;
}
嗯,这里要注意:结构体数组的初始化可以像普通数组一样用花括号,也可以逐个赋值。我建议在数据量不大时用花括号初始化,代码更紧凑。
结构体指针
结构体指针是C语言的精髓之一。为什么要用指针?因为传值拷贝太慢了。你想想看,一个结构体可能几十个字节,每次函数调用都复制一遍,性能损失不小。用指针就快多了,只传4个字节(32位系统)的地址。
struct Student *pStu;
pStu = &stu1;
// 两种访问方式
(*pStu).score = 95.0; // 传统写法
pStu->score = 95.0; // 箭头写法,更常用
我曾经在调试一个通信协议时,发现数据总是对不上。查了半天,原来是结构体指针用错了,->写成了.。这种错误编译器不会报错,但运行结果就是不对。所以,用指针访问成员时,一定要用->。
typedef:给类型起个外号
每次写struct Student是不是很烦?用typedef给它起个简洁的名字吧。
typedef struct Student {
char name[20];
int age;
float score;
} Student_t;
// 现在可以这样用
Student_t stu2;
Student_t *pStu2;
我个人习惯在类型名后面加_t后缀,一眼就能看出这是个类型。当然,也有人用_s表示结构体,_u表示联合体。这个没有标准,团队统一就好。
为什么需要typedef?说白了就是为了代码的可移植性和可读性。如果你要修改结构体的定义,只需要改一处,所有使用Student_t的地方自动生效。
联合体union:共享内存的艺术
联合体和结构体长得很像,但有一个本质区别:联合体的所有成员共享同一块内存。也就是说,联合体的大小等于最大成员的大小,而不是所有成员大小之和。
union Data {
int i;
float f;
char str[20];
};
union Data data;
data.i = 10;
printf("%d\n", data.i); // 输出10
data.f = 3.14;
printf("%f\n", data.f); // 输出3.14
// 注意:此时data.i的值已经被覆盖了
联合体最常见的用途是数据解析。我在做串口通信时,经常收到一个数据包,里面可能是整数、浮点数或者字符串。用联合体就可以灵活地解析,而不需要关心具体是什么类型。
- 结构体:每个成员有独立的内存空间,可以同时存储所有成员的值
- 联合体:所有成员共享同一块内存,同一时刻只能存储一个成员的值
还有一个经典用法:用联合体实现字节序转换。比如你要把一个32位整数拆成4个字节发送,或者把4个字节拼成一个整数。用联合体就非常方便:
union {
uint32_t word;
uint8_t bytes[4];
} converter;
converter.word = 0x12345678;
// 现在converter.bytes[0]到converter.bytes[3]就是四个字节
// 注意大小端问题!
我曾经在移植一个网络协议栈时,就靠联合体解决了字节序的问题。不同平台的字节序不同,用联合体可以轻松地在大小端之间转换,代码还特别简洁。
知识体系总览
下面这张图总结了本章的核心内容,你可以把它当作一个快速参考:
实战建议
在实际项目中,我总结了几条经验:
- 结构体适合描述实体:比如学生、设备、配置参数等,这些对象有多个属性需要同时存在
- 联合体适合数据转换:比如协议解析、字节序处理、类型转换等场景
- 结构体指针是函数传参的首选:避免大结构体的拷贝开销
- typedef让代码更清爽:特别是当结构体类型在多个文件中使用时
好了,结构体和联合体就讲到这里。记住一句话:结构体是"和"的关系,联合体是"或"的关系。理解了这个本质,你就能在合适的场景用对它们。
公众号:蓝海资料掘金营,微信deep3321