结构体与共用体:把数据组织起来

说实话,刚学C++那会儿,我总觉得结构体这玩意儿有点多余。不就是把几个变量捆在一起吗?后来做项目才明白——没有结构体,你写代码会累死。想想看,你要描述一个学生,有姓名、年龄、学号、成绩……难道每次都定义四个独立变量?那要是一百个学生呢?

结构体就是用来解决这个问题的。它让你能把不同类型的数据打包成一个整体。嗯,咱们今天就把这块彻底讲透。

结构体的定义与使用

结构体的定义很简单,用 struct 关键字就行。我个人习惯把结构体定义放在头文件里,这样多个源文件都能用。

struct Student {
    char name[32];   // 姓名
    int age;         // 年龄
    char id[20];     // 学号
    float score;     // 成绩
};  // 别忘了分号!

定义好之后怎么用?直接声明变量,然后用点号 . 访问成员:

Student stu1;                    // 声明一个学生
strcpy(stu1.name, "张三");
stu1.age = 20;
strcpy(stu1.id, "2024001");
stu1.score = 88.5f;

// 也可以声明时直接初始化
Student stu2 = {"李四", 21, "2024002", 92.0f};

小技巧:我建议结构体成员命名时加个前缀,比如 m_namename_,这样在代码里一眼就能看出是成员变量。这个习惯帮我省了不少调试时间。

结构体数组:批量管理数据

一个学生好办,一百个学生呢?用结构体数组啊。说白了,就是每个数组元素都是一个结构体。

Student class1[100];  // 一个班最多100人

// 给第一个学生赋值
strcpy(class1[0].name, "王五");
class1[0].age = 19;
class1[0].score = 78.0f;

// 遍历所有学生
for (int i = 0; i < 100; i++) {
    if (class1[i].score >= 60.0f) {
        printf("%s 及格了\n", class1[i].name);
    }
}

我在项目中遇到过一个问题:结构体数组初始化时忘了清零,结果有些成员是随机值,排查了好久才发现。所以建议你声明数组后,用 memset 或循环初始化一下。

结构体指针:灵活又高效

结构体指针这东西,刚开始容易搞混。其实记住一点就行:指针用 -> 访问成员,普通变量用 .

Student stu = {"赵六", 22, "2024003", 85.0f};
Student* pStu = &stu;

// 两种写法等价
printf("姓名:%s\n", (*pStu).name);   // 先解引用,再访问
printf("姓名:%s\n", pStu->name);     // 直接用箭头,更简洁

为什么要用指针?两个场景:

  • 函数传参:传指针比传整个结构体快得多,尤其是结构体很大时
  • 动态内存:new 在堆上创建结构体,必须用指针操作
// 函数传参用指针
void printStudent(const Student* p) {
    printf("%s, %d岁, 成绩%.1f\n", p->name, p->age, p->score);
}

// 动态创建
Student* p = new Student;
strcpy(p->name, "孙七");
p->age = 23;
printStudent(p);
delete p;  // 别忘了释放

注意:new 创建的结构体,一定要用 delete 释放。我曾经在项目里漏了一次,结果内存泄漏,程序跑了两天就崩了。嗯,从那以后我每次 new 完立刻写 delete,形成肌肉记忆。

共用体:省空间的妙招

共用体 union 和结构体长得很像,但有个关键区别:所有成员共享同一块内存。也就是说,你改了其中一个,其他的也跟着变。

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

Data d;
d.i = 10;
printf("d.i = %d\n", d.i);   // 10

d.f = 3.14f;
printf("d.f = %f\n", d.f);   // 3.14
printf("d.i = %d\n", d.i);   // 乱码!因为内存被f覆盖了

共用体的大小等于最大成员的大小。上面这个 Data 占 20 字节(因为 str 最大)。

什么时候用共用体?我一般在两种场景用:

  • 协议解析:同一个数据块,有时当整数读,有时当字节数组读
  • 节省内存:多个成员不会同时使用,比如一个设备要么是温度传感器,要么是湿度传感器
// 实际项目中的例子:解析网络数据包
union PacketHeader {
    uint32_t raw;        // 当作32位整数
    uint8_t bytes[4];    // 当作4个字节
};

PacketHeader h;
h.raw = 0x12345678;
// 可以通过h.bytes[0]拿到最低字节,方便做字节序转换

核心区别总结:

特性 结构体 struct 共用体 union
内存分配 各成员独立,总大小 = 各成员之和(考虑对齐) 所有成员共享,总大小 = 最大成员
同时使用 所有成员可同时有效 同一时刻只有一个成员有效
典型用途 组织复杂数据 节省内存、类型转换

知识体系总览

下面这张图把结构体和共用体的核心知识点串起来了。你看一眼就能明白它们之间的关系。

结构体与共用体知识体系 结构体 struct 定义:struct 关键字 + 成员列表 使用:变量 . 成员 / 指针 -> 成员 数组:批量管理同类结构体 指针:高效传参 + 动态内存 共用体 union 定义:union 关键字 + 成员列表 特点:所有成员共享同一块内存 大小:等于最大成员的大小 用途:协议解析 / 节省内存 共同点:都是用户自定义类型,都可以包含多种数据类型

我的建议:刚开始学的时候,先把结构体用熟。共用体在实际项目中用得相对少一些,但一旦遇到协议解析、数据转换的场景,它比任何方法都优雅。我做过一个嵌入式项目,用共用体解析串口协议,代码量直接减半。

好了,结构体和共用体就讲到这里。记住一句话:结构体是把数据组织起来,共用体是把内存省下来。多写几个例子,自然就熟练了。


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