C语言基础教程:枚举、结构体、共用体

简单数据类型和复合数据类型都讲过了,简单类型是单一类型的数据,复合数据类型在C语言里也要求必须都一样,那如果要把身高体重姓名学号都放在一起存,得怎么办呢?这次咱们就讨论一下这些。

如果说数组是整整齐齐的储物柜,里面放的东西都得一样(全是整数或全是字符),那么今天我们要学的结构体就像是一个个人档案袋。在这个袋子里,你可以放照片(图像数据)、身份证(字符串)、体检表(浮点数)等等各种不同类型的东西。

为了解决更复杂的现实问题,C 语言提供了三种强大的构造类型:枚举(给数字起名字)、结构体(打包不同类型的数据)和共用体(节省内存)。

一、枚举(Enum):给数字起个好听的名字

在写代码的时候,我们经常遇到一些选项类的数据。比如交通灯有红、黄、绿三种状态,或者一周有周一到周日七天。

如果直接用数字 0, 1, 2 来表示红黄绿,代码写多了容易晕:if (light == 0) 到底是什么意思?是红灯还是绿灯?

这时候,枚举就派上用场了。它允许我们给这些枯燥的数字起一个有意义的名字,让代码像人话一样易读。

示例 1:交通灯与工作日

看看 ex1_enum.c

// [引用: code_examples/struct_enum_union/ex1_enum.c]
#include <stdio.h>

// 1. 定义枚举类型
// 就像给 "0, 1, 2" 这些数字起了有意义的名字
// 默认从 0 开始编号:RED=0, YELLOW=1, GREEN=2
enum TrafficLight {
    RED,
    YELLOW,
    GREEN
};

// 也可以手动指定数值
enum Weekday {
    MONDAY = 1, // 从1开始
    TUESDAY,    // 自动变成2
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
};

int main() {
    printf("=== 枚举演示:交通灯 ===\n");

    // 使用枚举变量
    enum TrafficLight current_light = RED;

    // 枚举本质上就是整数,所以可以用 %d 打印
    printf("红灯的数值是: %d\n", current_light);

    // 配合 switch 使用非常清晰
    switch (current_light) {
        case RED:
            printf("信号灯指示:停车!\n");
            break;
        case YELLOW:
            printf("信号灯指示:等一等!\n");
            break;
        case GREEN:
            printf("信号灯指示:通行!\n");
            break;
    }

    printf("\n=== 枚举演示:工作日 ===\n");
    enum Weekday today = FRIDAY;
    printf("今天是星期 %d\n", today);

    if (today == SATURDAY || today == SUNDAY) {
        printf("状态:休息日,睡懒觉。\n");
    } else {
        printf("状态:工作日,去搬砖。\n");
    }

    return 0;
}

运行结果

=== 枚举演示:交通灯 ===
红灯的数值是: 0
信号灯指示:停车!

=== 枚举演示:工作日 ===
今天是星期 5
状态:工作日,去搬砖。

代码解析

  1. 可读性提升case RED:case 0: 清楚太多了。以后维护代码的人(包括未来的你自己)会感谢现在的你。
  2. 本质是整数:枚举在 C 语言底层就是 int。你可以直接把它当数字用,但为了代码规范,最好只当枚举用。

二、结构体(Struct):打包不同类型的数据

这是 C 语言里最常用的工具之一。

想象你要在程序里描述一个学生。他有姓名(字符串)、年龄(整数)和成绩(浮点数)。 如果不用结构体,你得定义三个独立的变量:char name[], int age, float score。如果有 50 个学生,你就得维护 3 个大数组,很容易搞乱。

结构体允许你把这些相关联的数据,打包成一个整体,就像填写一张学生登记表

示例 2:定义和使用结构体

看看 ex2_struct_basic.c

// [引用: code_examples/struct_enum_union/ex2_struct_basic.c]
#include <stdio.h>
#include <string.h>

// 1. 定义结构体
// 就像设计一张 "学生信息登记表"
struct Student {
    char name[50];  // 姓名 (字符串)
    int age;        // 年龄 (整数)
    float score;    // 分数 (浮点数)
};

int main() {
    printf("=== 结构体基础演示 ===\n");

    // 2. 创建并初始化结构体变量
    // 就像填写一张具体的表格
    struct Student stu1 = {"张三", 18, 95.5};

    // 3. 访问结构体成员
    // 使用 . 运算符 (点号)
    printf("姓名: %s\n", stu1.name);
    printf("年龄: %d\n", stu1.age);
    printf("成绩: %.1f\n", stu1.score);

    printf("\n--- 修改数据后 ---\n");

    // 修改成员的值
    stu1.score = 98.0; 
    // 注意:字符串数组不能直接用 = 赋值,要用 strcpy
    strcpy(stu1.name, "张三丰"); 

    printf("姓名: %s\n", stu1.name);
    printf("成绩: %.1f\n", stu1.score);

    // 结构体的大小
    // 注意:可能会有 "内存对齐" 现象,所以大小可能比成员加起来略大
    printf("\n结构体总大小: %lu 字节\n", sizeof(stu1));

    return 0;
}

运行结果

=== 结构体基础演示 ===
姓名: 张三
年龄: 18
成绩: 95.5

--- 修改数据后 ---
姓名: 张三丰
成绩: 98.0

结构体总大小: 60 字节

2.1 结构体指针与箭头运算符 ->

我们在讲函数时说过,C 语言函数参数默认是值传递(复印件)。 结构体通常比较大(比如上面的 Student 有 60 字节)。如果每次传参都复印一份,不仅浪费内存,还很慢。

所以,我们通常传递结构体的指针(地址)。 但是,拿到指针后,怎么访问里面的成员呢? * 普通方式:(*p).name (先解引用,再访问,写起来麻烦) * 快捷方式p->name (箭头运算符,专门给指针用的,帅气又快捷)

看看 ex3_struct_pointer.c

// [引用: code_examples/struct_enum_union/ex3_struct_pointer.c]
#include <stdio.h>

struct Point {
    int x;
    int y;
};

// 函数参数传递结构体指针
// 这样做的好处:
// 1. 避免复制整个结构体(如果结构体很大,复制很慢)
// 2. 可以在函数内部修改外面的结构体
void move_point(struct Point *p, int dx, int dy) {
    // 方式一:解引用后用点号 (*p).x
    // 方式二:使用箭头运算符 p->x (推荐,更简洁)

    printf("  [函数内] 移动前: (%d, %d)\n", p->x, p->y);

    p->x += dx;
    p->y += dy;

    printf("  [函数内] 移动后: (%d, %d)\n", p->x, p->y);
}

int main() {
    printf("=== 结构体指针演示 ===\n");

    struct Point my_point = {10, 20};

    printf("原始坐标: (%d, %d)\n", my_point.x, my_point.y);

    printf("\n正在调用 move_point 函数...\n");
    // 传递地址 (&my_point)
    move_point(&my_point, 5, -3);

    printf("\n回到 main 函数,坐标变为: (%d, %d)\n", my_point.x, my_point.y);
    printf("结论:通过指针,函数成功修改了外部的结构体!\n");

    return 0;
}

运行结果

=== 结构体指针演示 ===
原始坐标: (10, 20)

正在调用 move_point 函数...
  [函数内] 移动前: (10, 20)
  [函数内] 移动后: (15, 17)

回到 main 函数,坐标变为: (15, 17)
结论:通过指针,函数成功修改了外部的结构体!

三、共用体(Union):省吃俭用的变色龙

共用体(也叫联合体)是一种非常特殊的类型。它的所有成员共用同一块内存空间。 这就像是一个多功能插座,虽然有三孔也有两孔,但同一时间你只能插一个插头,不能同时用。

  • 结构体:每个人都有自己的房间(内存叠加)。
  • 共用体:大家轮流使用同一个房间(内存重叠,大小取决于最大的那个成员)。

它通常用于节省内存,或者在底层开发中处理二进制数据转换。

示例 3:共用体演示

看看 ex4_union.c

// [引用: code_examples/struct_enum_union/ex4_union.c]
#include <stdio.h>

// 定义共用体
// 就像一个 "多功能插座",同一时间只能插一种插头
union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    printf("=== 共用体 (Union) 演示 ===\n");

    union Data data;

    printf("共用体占用内存大小: %lu 字节\n", sizeof(data));
    printf("(它的大小等于最大成员的大小,这里是 str[20] 的 20 字节)\n\n");

    // 1. 存入整数
    data.i = 10;
    printf("存入整数 10 后:\n");
    printf("data.i = %d\n", data.i);

    // 2. 存入浮点数
    // 注意:这会覆盖掉刚才存的整数!
    data.f = 220.5;
    printf("\n存入浮点数 220.5 后:\n");
    printf("data.f = %.1f\n", data.f);
    printf("data.i = %d (乱码了!因为内存被改写了)\n", data.i);

    // 3. 存入字符串
    // 这会覆盖掉刚才的浮点数
    sprintf(data.str, "Hello");
    printf("\n存入字符串 'Hello' 后:\n");
    printf("data.str = %s\n", data.str);
    printf("data.f = %.1f (乱码了!)\n", data.f);

    return 0;
}

运行结果

=== 共用体 (Union) 演示 ===
共用体占用内存大小: 20 字节
(它的大小等于最大成员的大小,这里是 str[20]  20 字节)

存入整数 10 后:
data.i = 10

存入浮点数 220.5 后:
data.f = 220.5
data.i = 1130135552 (乱码了!因为内存被改写了)

存入字符串 'Hello' 后:
data.str = Hello
data.f = 1143139122437582505939828736.0 (乱码了!)

四、结构体 vs 共用体

这两个挺像(都是用 {} 包起来),所以得注意区分异同。

特性 结构体 (Structure) 共用体 (Union)
内存分配 每个成员都有自己独立的房间 所有成员挤在同一个房间里
大小计算 至少是所有成员大小之(可能有填充) 等于最大那个成员的大小
同时存值 可以同时存储所有成员的值 同一时间只能存一个成员的值
互相影响 改了 A 成员,B 成员不受影响 改了 A 成员,B 成员的数据会被覆盖/破坏
生活比喻 公寓:你住你的,我住我的 试衣间:一次只能进一个人,你进去了我就得出来

什么时候用谁? * 绝大多数情况(99%)你需要的都是结构体。 * 只有在你极度缺内存(比如嵌入式开发),或者需要对同一段二进制数据进行不同方式的解读(比如网络协议解析)时,才会用到共用体

总结

今天我们学习了 C 语言中用来处理复杂数据的三个法宝:

  1. 枚举 (Enum):让代码里的数字变成有意义的单词,提高可读性(红灯、绿灯)。
  2. 结构体 (Struct):把不同类型的数据打包在一起,描述一个完整的对象(学生档案)。记住用 -> 来操作结构体指针。
  3. 共用体 (Union):一块内存轮流用,省空间,但要注意同一时间只能存一个有效值。

掌握了结构体,你现在可以把之前学的零散变量组织起来,去描述更真实、更复杂的世界了。比如写一个贪吃蛇游戏,蛇身就是一个结构体数组;或者写一个通讯录,每个联系人也是一个结构体。

Category: C
Category
Tagcloud
Hack RTL-SDR Lens SKill Book GPT-OSS Discuss Poem OpenWebUI Mac Conda FckZhiHu Python Junck Translate Data Virtual Machine OpenCL Communicate Shit Kivy IDE Software VTK Tape Programming MayaVi Chat Memory Cursor Learning Remote VM Prompt Nvidia Photo Code Game Hardware Raspbian AI,Data Science Science Microscope GlumPy 耳机 LTO Story Qwen3 Geology LlamaFactory Code Generation Radio C Windows Camera HBase QGIS Tools Ollama Windows11 Agent Hadoop Linux FuckZhihu n8n Translation ML Ubuntu AI CUDA PVE Video GIS RaspberryPi Photography ChromeBook History Hackintosh Virtualization PyOpenCL AIGC Mount&Blade 音频 SandBox PHD VirtualMachine TUNA Pyenv Visualization Tool macOS NixOS FuckChunWan Scholar 蓝牙 Life LLM University QEMU LTFS