3 分钟吃透 C 语言自定义类型!结构体 + 共用体 + 枚举应用案例大合集

58 阅读8分钟

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#definetypedef是类型重定义,会把结构体当成一个整体;#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 访问效率:

  1. 找到结构体中占用内存最大的成员变量;
  2. 每个成员的起始地址必须是其自身大小的整数倍(不足则补空字节);
  3. 结构体总大小必须是最大成员变量大小的整数倍。

示例:

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 类型)表示状态、操作、选项等固定常量

五、嵌入式开发注意事项

  1. 结构体使用时要注意内存对齐,若需节省内存,可按成员大小从小到大排序;
  2. 共用体同一时间只能操作一个成员,避免同时赋值多个成员导致数据混乱;
  3. 枚举成员是常量,不能直接修改其值,可通过枚举变量间接使用;
  4. 结构体指针需先指向有效内存再访问成员,避免野指针导致系统崩溃;
  5. 嵌入式设备内存有限,优先使用共用体存储互斥数据,减少内存浪费。

六、总结

结构体、共用体、枚举是嵌入式 C 语言的 “三大利器”:

  • 结构体擅长 “打包” 复杂数据,让代码更有条理;
  • 共用体擅长 “共享” 内存,在资源受限的嵌入式设备中不可或缺;
  • 枚举擅长 “定义” 常量,让代码更易读、易维护。

掌握这三个自定义类型,能轻松应对嵌入式开发中的复杂数据处理场景,比如设备驱动中的数据传输、系统配置中的参数管理等。我是学嵌入式的小杨同学,关注我,后续会分享更多嵌入式实战技巧,一起进步!