3 分钟吃透 C 语言自定义类型!结构体 + 共用体 + 枚举应用案例大合集
大家好,我是学嵌入式的小杨同学。在嵌入式开发中,除了基础数据类型,结构体、共用体、枚举是处理复杂数据的核心自定义类型。它们能帮我们把分散的数据组织成有逻辑的整体,还能节省内存、提升代码可读性,是驱动开发、设备配置等场景的必备技能。今天就结合资料,用通俗的语言 + 实战代码,把这三个知识点讲透。
一、结构体:复杂数据的 “打包箱”
1. 结构体的核心作用
结构体最核心的价值,是把多个不同类型的数据打包成一个整体,方便统一管理和操作。比如存储学生信息时,需要学号(int)、姓名(char 数组)、成绩(int),这些不同类型的数据无法用数组存储,结构体就能完美解决。
-
对比其他数据类型:
- 基本数据类型(int、char):只能存单个数据;
- 数组:只能存多个相同类型的数据;
- 结构体:可存多个不同类型的数据,且视为一个整体。
2. 结构体的定义与声明
结构体的声明有三种常见方式,按需选择即可:
c
运行
#include <stdio.h>
#include <string.h>
// 方式1:先声明类型,再定义变量(最常用)
struct Student {
int id; // 学号
char name[20]; // 姓名
int score; // 成绩
};
struct Student stu1; // 定义变量
// 方式2:声明类型的同时定义变量
struct Teacher {
int id;
char name[20];
} tea1; // 直接定义变量tea1
// 方式3:省略类型名(匿名结构体,仅需定义一次变量时使用)
struct {
int year;
int month;
int day;
} date; // 仅能通过date访问,无法再定义其他变量
3. 结构体的类型重定义(typedef)
每次定义变量都要写struct Student太繁琐,用typedef重定义类型,能简化书写,这是嵌入式开发的常用技巧:
c
运行
// 重定义结构体类型,STU等价于struct Student
typedef struct Student {
int id;
char name[20];
int score;
} STU;
// 直接用STU定义变量,简洁高效
STU stu2;
注意区分typedef和#define:typedef是类型重定义,会把结构体当成一个整体;#define只是文本替换,可能出现逻辑错误,嵌入式开发中优先用typedef。
4. 结构体的初始化与访问
(1)初始化方式
c
运行
// 方式1:定义时完整初始化
STU stu3 = {1001, "Jack", 95};
// 方式2:定义时部分初始化(未赋值成员默认为0)
STU stu4 = {1002, "Rose"}; // score默认为0
// 方式3:指定成员初始化(顺序可任意)
STU stu5 = {.name = "Tom", .score = 98, .id = 1003};
// 方式4:先定义后初始化(通过.成员名赋值)
STU stu6;
stu6.id = 1004;
strcpy(stu6.name, "Lily"); // 字符串需用strcpy赋值
stu6.score = 92;
(2)成员访问
- 普通变量:用
.访问,格式变量名.成员名; - 指针变量:用
->访问,格式指针名->成员名。
c
运行
// 结构体指针访问示例
STU stu7 = {1005, "Mike", 88};
STU *p = &stu7;
printf("学号:%d,姓名:%s,成绩:%d\n", p->id, p->name, p->score);
5. 结构体的内存大小(内存对齐)
结构体的内存大小不是成员变量大小之和,而是遵循 “内存对齐” 规则,目的是提升 CPU 访问效率:
- 找到结构体中占用内存最大的成员变量;
- 每个成员的起始地址必须是其自身大小的整数倍(不足则补空字节);
- 结构体总大小必须是最大成员变量大小的整数倍。
示例:
c
运行
struct Test {
char a; // 占1字节,起始地址0
int b; // 占4字节,需对齐到4的整数倍(地址4),中间补3字节
double c;// 占8字节,对齐到8的整数倍(地址8)
};
// 总大小:8(c) + 8(b+补3字节+a)= 16字节
printf("struct Test大小:%ld\n", sizeof(struct Test)); // 输出16
6. 结构体的嵌入式应用场景
- 设备信息存储:如传感器数据(温度、湿度、时间);
- 链表节点:如前文中的单链表、二叉树节点;
- 配置项管理:如系统参数(波特率、IP 地址、设备 ID)。
二、共用体:内存共享的 “节省能手”
1. 共用体的核心特点
共用体(union)和结构体类似,但所有成员共享同一块内存空间,同一时间只能存储一个成员的值,核心作用是节省内存。
2. 共用体的定义与使用
c
运行
// 定义共用体类型
typedef union Data {
int a; // 占4字节
char b; // 占1字节
double c; // 占8字节
} DATA;
int main() {
DATA data;
// 共用体大小 = 最大成员变量大小的整数倍(此处为8字节)
printf("共用体大小:%ld\n", sizeof(DATA)); // 输出8
// 同一时间只能有效存储一个成员的值
data.a = 300;
printf("data.a = %d\n", data.a); // 输出300
data.b = 'A';
printf("data.b = %c\n", data.b); // 输出A
printf("data.a = %d\n", data.a); // 输出乱码(a的值被b覆盖)
return 0;
}
3. 嵌入式核心应用:判断大小端存储
共用体最经典的用法是判断 CPU 的大小端存储模式,这在嵌入式数据传输(如串口、网络通信)中至关重要:
- 小端模式:地址低位存储数据低位,地址高位存储数据高位(x86、ARM 默认);
- 大端模式:地址低位存储数据高位,地址高位存储数据低位(网络传输、部分单片机)。
c
运行
typedef union EndianTest {
int a;
char b;
} ET;
int main() {
ET et;
et.a = 1; // 二进制:00000000 00000000 00000000 00000001
// 若b=1,说明地址低位存储的是数据低位(小端);否则为大端
if (et.b == 1) {
printf("当前系统为小端存储\n");
} else {
printf("当前系统为大端存储\n");
}
return 0;
}
三、枚举:意义明确的 “常量集合”
1. 枚举的核心作用
枚举(enum)用于定义一组有名字的常量,让代码更易读、易维护。比如表示状态(成功 / 失败)、操作(加法 / 减法)时,用枚举比直接用数字更清晰。
2. 枚举的定义与初始化
c
运行
// 方式1:定义枚举类型并初始化(默认从0开始递增)
typedef enum Operation {
ADD, // 0
SUB, // 1
MUL, // 2
DIV // 3
} OP;
// 方式2:指定部分成员的值(后续成员依次递增)
typedef enum Status {
ERROR = -1,
SUCCESS = 0,
RUNNING = 1,
STOPPED // 2(自动递增)
} STAT;
// 方式3:匿名枚举(直接定义常量,无需类型名)
enum {
MON, // 0
TUE, // 1
WED, // 2
THU, // 3
FRI, // 4
SAT, // 5
SUN // 6
};
3. 嵌入式应用场景
枚举常用于switch-case语句,让逻辑更清晰:
c
运行
// 枚举在switch-case中的应用
int calculate(OP op, int a, int b) {
switch (op) {
case ADD:
return a + b;
case SUB:
return a - b;
case MUL:
return a * b;
case DIV:
return b != 0 ? a / b : -1;
default:
return -1;
}
}
int main() {
OP op = ADD;
printf("3 + 5 = %d\n", calculate(op, 3, 5)); // 输出8
return 0;
}
四、三者核心区别对比
| 类型 | 核心特点 | 内存占用 | 适用场景 |
|---|---|---|---|
| 结构体 | 成员独立存储,互不影响 | 成员大小之和(含对齐字节) | 存储多个不同类型的关联数据 |
| 共用体 | 成员共享一块内存,互斥访问 | 最大成员大小(整数倍) | 节省内存、判断大小端、数据类型转换 |
| 枚举 | 定义有名字的常量集合 | 通常为 4 字节(int 类型) | 表示状态、操作、选项等固定常量 |
五、嵌入式开发注意事项
- 结构体使用时要注意内存对齐,若需节省内存,可按成员大小从小到大排序;
- 共用体同一时间只能操作一个成员,避免同时赋值多个成员导致数据混乱;
- 枚举成员是常量,不能直接修改其值,可通过枚举变量间接使用;
- 结构体指针需先指向有效内存再访问成员,避免野指针导致系统崩溃;
- 嵌入式设备内存有限,优先使用共用体存储互斥数据,减少内存浪费。
六、总结
结构体、共用体、枚举是嵌入式 C 语言的 “三大利器”:
- 结构体擅长 “打包” 复杂数据,让代码更有条理;
- 共用体擅长 “共享” 内存,在资源受限的嵌入式设备中不可或缺;
- 枚举擅长 “定义” 常量,让代码更易读、易维护。
掌握这三个自定义类型,能轻松应对嵌入式开发中的复杂数据处理场景,比如设备驱动中的数据传输、系统配置中的参数管理等。我是学嵌入式的小杨同学,关注我,后续会分享更多嵌入式实战技巧,一起进步!