结构体与联合体:C++中的复合数据类型

大家好,我是你们的老朋友。今天我们来聊聊C++里两个非常基础但又容易踩坑的概念——结构体(struct)和联合体(union)。嗯,别小看它们,我见过不少项目因为结构体对齐问题导致内存泄漏,也见过有人把union用错导致数据覆盖。咱们今天就把这些坑一个个填平。

一、结构体:struct的定义与使用

结构体说白了就是把多个不同类型的数据打包成一个整体。比如你要描述一个学生,有姓名、年龄、分数,用结构体就很方便。

struct Student {
    char name[32];
    int age;
    float score;
};  // 别忘了分号!

我个人习惯在定义结构体时,把成员变量按从大到小排列,这样能减少内存对齐带来的浪费。你想想看,如果先放char再放int,中间会多出几个字节的填充,白白浪费空间。

使用结构体也很简单:

Student stu1;  // C++中可以直接用Student,不用加struct
stu1.age = 20;
stu1.score = 95.5f;
strcpy(stu1.name, "张三");
小技巧: 我建议在定义结构体时,尽量使用初始化列表来赋值,避免遗漏成员:
Student stu2 = {"李四", 22, 88.0f};

二、内存对齐:看不见的填充字节

这里有个问题:结构体占用的内存大小,真的等于所有成员大小之和吗?

答案是否定的。因为CPU读取内存时,是按“字”来读的,不是按字节。为了让CPU读得快,编译器会在成员之间插入一些填充字节,这就是内存对齐。

举个例子:

struct Example1 {
    char a;    // 1字节
    int b;     // 4字节
    char c;    // 1字节
};

你猜这个结构体占多少字节?不是1+4+1=6,而是12!为什么会这样?因为int b需要4字节对齐,所以a后面会填充3个字节;c后面又会填充3个字节,让整个结构体大小是4的倍数。

成员 偏移量 实际占用
char a 0 1字节
填充 1-3 3字节
int b 4 4字节
char c 8 1字节
填充 9-11 3字节
避坑指南: 我曾经在嵌入式项目中,因为结构体对齐问题,导致两个设备通信时数据错位。后来才发现,发送端和接收端的结构体对齐方式不一样。解决办法是使用#pragma pack(1)强制1字节对齐,或者手动调整成员顺序。

如果你想让结构体更紧凑,可以把相同类型的成员放在一起:

struct Example2 {
    char a;    // 1字节
    char c;    // 1字节
    int b;     // 4字节
};  // 现在只占8字节,省了4字节!

三、联合体:union的概念与用途

联合体和结构体长得很像,但有一个本质区别:所有成员共享同一块内存。也就是说,union的大小等于它最大成员的大小。

union Data {
    int i;
    float f;
    char str[20];
};  // 大小是20字节(str最大)

你想想看,如果你同时给i和f赋值,后赋值的会覆盖前面的。所以union通常用于“多选一”的场景,比如一个变量在不同情况下表示不同类型的数据。

我在项目中遇到过用union解析网络协议的情况。比如一个数据包的前4个字节,可能是整数,也可能是IP地址的四个部分。用union就能轻松转换:

union IPAddress {
    uint32_t addr;
    uint8_t octets[4];
};

IPAddress ip;
ip.addr = 0xC0A80101;  // 192.168.1.1
// 现在ip.octets[0]是192,octets[1]是168,octets[2]是1,octets[3]是1
重要提醒: 使用union时,一定要清楚当前哪个成员是“活跃”的。我曾经在调试一个bug时,发现数据莫名其妙变了,就是因为代码里同时使用了两个成员,而它们互相覆盖了。

四、枚举类型:enum与enum class

枚举类型用来定义一组命名的整型常量。比如一周的七天、颜色、状态码等。

enum Color {
    RED,
    GREEN,
    BLUE
};  // RED=0, GREEN=1, BLUE=2

但传统的enum有个问题:它的作用域是全局的。如果你在另一个地方也定义了RED,就会冲突。而且它隐式转换为整数,容易引发bug。

C++11引入了enum class,解决了这些问题:

enum class Color {
    RED,
    GREEN,
    BLUE
};

enum class TrafficLight {
    RED,   // 不会和Color::RED冲突
    GREEN,
    YELLOW
};

// 使用时要加作用域
Color c = Color::RED;
// int x = c;  // 错误!不能隐式转换
int x = static_cast<int>(c);  // 必须显式转换

我个人强烈建议:新代码一律使用enum class。传统enum的隐式转换太容易出问题了。我记得有一次,一个同事把枚举值直接当数组下标用,结果数组越界,查了半天才发现。

小技巧: 你可以指定枚举的底层类型,比如enum class Color : uint8_t { ... },这样能节省内存,特别适合嵌入式开发。

五、知识体系总览

下面这张图帮你理清今天讲的内容:

结构体与联合体知识体系 结构体 (struct) 定义与使用 内存对齐 成员访问 填充字节 联合体 (union) 共享内存 多选一 协议解析 类型转换 枚举类型 (enum / enum class) 传统enum(全局作用域) enum class(强类型)

好了,今天的内容就到这里。结构体和联合体虽然基础,但用好它们能让你的代码更高效、更安全。记住:结构体用对齐,union用覆盖,枚举用class。下次写代码时,多想想这些细节,能省不少调试时间。


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