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

说实话,C++ 里最让我觉得「贴心」的设计之一,就是结构体。为什么?因为现实世界里的数据,很少是孤零零的。比如一个学生,有姓名、年龄、成绩——这三样东西你总不能各管各的吧?结构体就是用来干这个的:把相关的数据打包成一个整体。

今天我们就来聊聊结构体和它的「表兄弟」共用体。我会结合我这些年踩过的坑,给你讲明白。

11.1 结构体的定义与使用

结构体,说白了就是自定义的数据类型。你可以把不同类型的数据组合在一起。

// 定义一个学生结构体
struct Student {
    char name[20];
    int age;
    float score;
};

int main() {
    // 声明一个结构体变量
    Student stu1;
    
    // 访问成员
    strcpy(stu1.name, "张三");
    stu1.age = 20;
    stu1.score = 89.5;
    
    // 也可以直接初始化
    Student stu2 = {"李四", 22, 95.0};
    
    return 0;
}

我个人习惯在定义结构体时,把成员变量按「从大到小」的顺序排列。为什么?因为内存对齐。嗯,这个后面会细说。

小技巧: C++ 里,结构体名可以直接当类型用,不用像 C 语言那样加 struct 关键字。这是 C++ 的语法糖。

11.2 结构体数组

一个学生好办,一群学生呢?用数组呗。结构体数组就是每个元素都是一个结构体。

// 定义一个班级,最多 50 人
Student class1[50];

// 初始化部分学生
Student class2[3] = {
    {"王五", 19, 88.0},
    {"赵六", 20, 92.5},
    {"孙七", 21, 76.0}
};

// 遍历访问
for (int i = 0; i < 3; i++) {
    cout << class2[i].name << " " << class2[i].score << endl;
}

我在项目中遇到过一个问题:用结构体数组存传感器数据,结果数组越界了。嗯,C++ 不检查数组边界,你得自己小心。我建议用 std::vector 代替原生数组,安全得多。

11.3 结构体指针

结构体指针,就是指向结构体的指针。用指针操作结构体,效率更高,尤其是传参的时候。

Student stu = {"小明", 18, 90.0};
Student* pStu = &stu;

// 两种访问方式
cout << (*pStu).name << endl;  // 先解引用,再访问
cout << pStu->name << endl;   // 用 -> 更直观

你想想看,如果结构体很大,每次传参都复制一份,多浪费内存。用指针传地址,就快多了。

注意: 用结构体指针时,一定要确保指针指向了有效的内存。我曾经犯过一个错:声明了指针就直接用,结果程序崩溃了。记住,指针要初始化。

11.4 位域

位域,这是个有意思的东西。它让你可以精确控制每个成员占几个二进制位。说白了,就是省内存。

struct Flags {
    unsigned int isReady : 1;   // 只占 1 位
    unsigned int isError : 1;   // 只占 1 位
    unsigned int mode : 2;      // 占 2 位,可以表示 0~3
    unsigned int count : 4;     // 占 4 位,可以表示 0~15
};

int main() {
    Flags f = {1, 0, 2, 10};
    cout << f.isReady << " " << f.mode << endl;
    return 0;
}

位域在嵌入式开发里特别常见。比如你要跟硬件寄存器打交道,每个位都有特定含义。我当年做通信协议时,就用位域来解析数据包头部,省了不少功夫。

核心要点: 位域的总位数不能超过底层类型(比如 unsigned int)的位数。而且不同编译器对位域的内存布局可能不一样,跨平台时要小心。

11.5 共用体

共用体,英文叫 union。它和结构体很像,但有一个关键区别:所有成员共享同一块内存。

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

int main() {
    Data d;
    d.i = 42;
    cout << d.i << endl;  // 输出 42
    
    d.f = 3.14;
    cout << d.f << endl;  // 输出 3.14
    // 注意:此时 d.i 的值已经被覆盖了
    
    return 0;
}

为什么会这样?因为共用体里所有成员都从同一个地址开始存储。你给一个成员赋值,其他成员的值就变了。共用体的大小,等于最大成员的大小。

我建议你在以下场景用共用体:

  • 需要节省内存,且同一时刻只用到一个成员
  • 做数据类型的转换(比如把 float 的字节拆开看)
  • 解析网络协议或文件格式时
避坑指南: 我曾经在项目里用共用体存不同类型的数据,结果忘了当前存的是哪种类型,读出来全是乱码。记住,共用体不记录当前存的是哪个成员,你得自己维护一个标志位。

11.6 结构体与共用体的对比

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

11.7 知识体系图

下面这张图帮你理清结构体和共用体的核心脉络:

结构体与共用体 结构体 (struct) 定义与使用 结构体数组 结构体指针 共用体 (union) 共享内存 节省空间 类型转换 位域 (bit field)

这张图把今天的内容串起来了。结构体和共用体是两种不同的数据组织方式,位域则是结构体里的一个特殊功能。你掌握了这些,C++ 的数据管理能力就上了一个台阶。


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