一、共用体(union)——让不同变量共享同一块内存

共用体,说白了就是让几个不同类型的变量共用同一块内存空间。我刚开始学C语言时,总觉得这东西有点鸡肋——既然有结构体了,为什么还要搞个共用体?直到后来做嵌入式开发,才真正体会到它的妙处。

1.1 共用体的定义

共用体的定义方式和结构体非常像,只是关键字从 struct 换成了 union

union Data {
    int i;
    float f;
    char str[20];
};

这里定义了一个叫 Data 的共用体类型,它包含三个成员:一个整型、一个浮点型、一个字符数组。但有意思的是,这三个成员在内存中是从同一个地址开始存放的。

关键区别:结构体的每个成员都有独立的存储空间,而共用体的所有成员共享同一块存储空间。

1.2 共用体的特点

共用体有几个特点,我当年踩过不少坑,这里给你总结一下:

  • 所有成员共享同一块内存——共用体的大小等于它最大成员的大小。上面那个 union Data,最大成员是 char str[20](20字节),所以整个共用体就是20字节。
  • 同一时刻只能存放一个成员的值——你给 i 赋了值,再去读 f,得到的就是一堆乱码。嗯,这个我吃过亏。
  • 修改一个成员会影响其他成员——因为它们共用内存,改了一个,其他的自然也跟着变。

我曾经在项目里用共用体做数据解析,结果忘了初始化就直接读取浮点成员,调试了一下午才发现是垃圾值在作怪。所以,使用共用体前一定要先赋值!

1.3 共用体的实际应用

共用体在嵌入式开发中特别常用。我举个例子,比如你要解析一个串口数据包,数据可能是整数也可能是浮点数:

union PacketData {
    uint8_t  bytes[4];
    uint32_t integer;
    float    decimal;
};

// 接收数据
union PacketData rxData;
rxData.bytes[0] = 0x41;
rxData.bytes[1] = 0xA0;
rxData.bytes[2] = 0x00;
rxData.bytes[3] = 0x00;

// 直接按浮点数读取
printf("浮点数值: %f\n", rxData.decimal);  // 输出 20.0

你看,通过共用体,我们可以用字节数组接收原始数据,然后直接按浮点数解读,省去了手动转换的麻烦。

二、枚举(enum)——给整数起个名字

枚举,说白了就是给一组整数常量起个有意义的名字。我个人习惯用枚举来替代宏定义,因为枚举有类型检查,更安全。

2.1 枚举的定义

enum Weekday {
    MON,     // 默认 0
    TUE,     // 默认 1
    WED,     // 默认 2
    THU,     // 默认 3
    FRI,     // 默认 4
    SAT,     // 默认 5
    SUN      // 默认 6
};

默认情况下,枚举常量从0开始依次递增。你也可以手动指定值:

enum Status {
    OK = 0,
    ERROR = -1,
    TIMEOUT = -2,
    BUSY = 1
};

2.2 枚举的使用

枚举用起来很直观,你想想看:

enum Weekday today = WED;

if (today == SAT || today == SUN) {
    printf("周末啦!\n");
} else {
    printf("继续搬砖...\n");
}

比起直接用数字0~6,用 MONTUE 这样的名字,代码可读性一下子就上去了。

我建议在 switch-case 语句中尽量用枚举,这样编译器能帮你检查是否遗漏了某个分支。比如你定义了7天的枚举,如果 switch 里只写了5个 case,编译器会给出警告。

2.3 枚举的注意事项

  • 枚举本质上是整数——你可以把一个整数值赋给枚举变量,但最好别这么干,容易出问题。
  • 枚举常量是全局可见的——不同枚举类型中不能有同名的常量。比如你不能在 enum Aenum B 里都定义 OK
  • C语言中枚举的大小——通常是 int 大小,但具体取决于编译器。

三、typedef——给类型起个外号

typedef 的作用就是给已有的类型起个新名字。我刚开始觉得这功能没啥用,后来写复杂项目才发现,它能让代码简洁不少。

3.1 typedef 的基本用法

typedef unsigned long long uint64_t;
typedef struct {
    int x;
    int y;
} Point;

uint64_t bigNumber = 1234567890ULL;
Point p1 = {10, 20};

你看,用 typedef 定义后,每次声明变量就不用写 struct 关键字了,代码清爽很多。

3.2 typedef 与函数指针

函数指针的声明本来就够绕的,用 typedef 能简化不少:

// 定义一个函数指针类型
typedef int (*Operation)(int, int);

// 使用
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

Operation op = add;
printf("结果: %d\n", op(5, 3));  // 输出 8

op = sub;
printf("结果: %d\n", op(5, 3));  // 输出 2

我个人习惯在嵌入式驱动开发中,用 typedef 定义寄存器地址类型、回调函数类型等,这样代码移植起来也方便。

3.3 typedef 与 #define 的区别

对比项 typedef #define
处理时机 编译阶段 预处理阶段(文本替换)
类型检查 有编译器类型检查 无类型检查
作用域 遵循作用域规则 全局有效(直到 #undef)
指针声明 typedef int* PINT; PINT a, b; // a和b都是指针 #define PINT int* PINT a, b; // a是指针,b是int

嗯,这里要注意,#define 只是简单的文本替换,容易出一些隐蔽的bug。我建议能用 typedef 的地方就别用 #define

四、知识体系总览

下面这张图帮你梳理了本章的核心内容:

共用体、枚举与typedef知识体系 共用体 union 枚举 enum typedef 所有成员共享同一块内存 大小 = 最大成员的大小 同一时刻只存一个成员 给整数常量起名字 默认从0开始递增 可手动指定枚举值 给已有类型起别名 简化复杂类型声明 有编译器类型检查 三者结合使用,让C代码更安全、更易读、更易维护

五、综合示例

最后,我把这三个知识点串起来,写一个实际点的例子:

#include <stdio.h>

// 定义数据类型枚举
typedef enum {
    TYPE_INT,
    TYPE_FLOAT,
    TYPE_STRING
} DataType;

// 定义共用体存放数据
typedef union {
    int i;
    float f;
    char str[32];
} DataValue;

// 定义结构体包装数据和类型
typedef struct {
    DataType type;
    DataValue value;
} DataPacket;

void printData(DataPacket *pkt) {
    switch (pkt->type) {
        case TYPE_INT:
            printf("整数: %d\n", pkt->value.i);
            break;
        case TYPE_FLOAT:
            printf("浮点数: %f\n", pkt->value.f);
            break;
        case TYPE_STRING:
            printf("字符串: %s\n", pkt->value.str);
            break;
        default:
            printf("未知类型\n");
    }
}

int main() {
    DataPacket pkt1 = {TYPE_INT, .value.i = 42};
    DataPacket pkt2 = {TYPE_FLOAT, .value.f = 3.14f};
    DataPacket pkt3 = {TYPE_STRING, .value.str = "Hello"};

    printData(&pkt1);
    printData(&pkt2);
    printData(&pkt3);

    return 0;
}

这个例子把枚举、共用体和typedef结合在了一起,实现了一个可以存放不同类型数据的通用数据包。我在做物联网项目时,经常用这种模式来处理不同传感器的数据。

避坑指南:使用共用体时,一定要通过类型标记(比如上面的 DataType)来记录当前存放的是哪种类型的数据。我曾经在项目中忘了做这个标记,结果读取数据时类型对不上,排查了好久才发现问题。


公众号:蓝海资料掘金营,微信deep3321