搜索

C++ 教程

C++基础

第1章 搭建开发环境与第一个程序 第2章 基本数据类型与变量 第3章 运算符与表达式 第4章 流程控制语句 第5章 数组与字符串 第6章 函数基础 第7章 指针基础 第8章 引用与const限定 第9章 动态内存管理 第10章 结构体与联合体 第11章 函数进阶

面向对象

第12章 面向对象编程入门 第13章 封装与访问控制 第14章 运算符重载 第15章 继承与派生 第16章 多态与虚函数

STL与进阶

第17章 模板编程基础 第18章 标准模板库(STL)容器 第19章 STL算法与迭代器 第20章 异常处理机制 第21章 文件输入输出 第22章 字符串类string 第23章 命名空间与作用域 第24章 类型转换操作符

现代C++

第25章 智能指针 第26章 移动语义与右值引用 第27章 Lambda表达式 第28章 多线程编程基础 第29章 模板元编程入门 第30章 设计模式基础 第31章 C++11/14新特性 第32章 C++17/20核心特性

实战进阶

第33章 性能优化技巧 第34章 调试与测试 第35章 项目结构与构建 第36章 网络编程入门 第37章 数据库连接操作 第38章 图形界面开发基础 第39章 综合项目实战 第40章 进阶学习路线

第10章 结构体与联合体

第10章 结构体与联合体

入门 👤 已掌握数组和指针,需要组织复杂数据的开发者 🔧 游戏角色属性、传感器数据包、网络协议解析
本章你将学到
  • 如何用结构体将不同类型的数据打包成一个整体
  • 联合体的内存共享特性及其典型应用场景
  • 结构体与指针、数组的配合使用技巧
核心概念structunion

想象一下你的学生证,上面有你的学号、姓名、年龄、班级等信息。在C++里,如果你用单独的变量来存储这些信息,管理起来会很零散。结构体(struct)就像一张定制的信息卡片,让你能把所有相关数据打包成一个整体。

10.1 结构体:自定义的数据类型

结构体是一种用户自定义的数据类型,它允许你将多个不同类型的变量组合在一起,形成一个新的复合类型。定义结构体,就是为这种新类型设计一个“蓝图”。

结构体的定义与使用

定义一个结构体,需要使用关键字 struct,后面跟上结构体名称和一对花括号。花括号内是它的“成员变量”。

// 定义一个名为Student的结构体
struct Student {
    int id;          // 学号
    char name[20];   // 姓名
    int age;         // 年龄
    float score;     // 成绩
}; // 注意这里的分号不能少!

定义好蓝图后,就可以用它来创建具体的变量了,这称为“结构体变量”。访问其内部的成员,需要使用点运算符(.)。

// 创建结构体变量并初始化
Student stu1 = {1001, "张三", 18, 95.5};

// 访问和修改成员
stu1.age = 19;
cout << "姓名:" << stu1.name << endl;

常见误区

  • 忘记结尾分号:在结构体定义的大括号后必须加上分号,这是新手最常见的编译错误之一。
  • 混淆定义与声明:struct Student { ... }; 是类型定义,不分配内存。只有像 Student stu1; 这样声明变量时,内存才被分配。
  • 错误初始化:初始化列表中的值必须与成员变量的声明顺序和类型严格匹配,否则会导致数据错乱或编译错误。

结构体与数组、指针

结构体可以像基本类型一样,用于创建数组或与指针结合。这在处理批量数据时非常有用。

结构体数组
→
批量数据管理
→
如:学生名单
↓
结构体指针
→
动态内存分配
→
高效参数传递

使用结构体指针时,访问成员需要使用箭头运算符(->),它是解引用和点运算符的组合简写。

Student stuArray[50]; // 结构体数组,存储50个学生信息
stuArray[0].id = 1001;

Student *pStu = &stuArray[0];
// 以下两种访问方式等价
(*pStu).score = 90.0;
pStu->score = 90.0; // 更常用、更清晰

我在一个嵌入式传感器数据采集项目中踩过坑:定义了一个包含温度、湿度、时间戳的结构体,然后用指针数组管理多个传感器。一开始错误地用点运算符访问指针成员,编译器报了一堆错,排查了半天才发现是运算符用错了。

关键点:结构体的大小与内存对齐

结构体的大小并不简单地等于所有成员大小之和。编译器为了提升内存访问效率,会进行“内存对齐”。例如,一个包含 char (1字节) 和 int (4字节) 的结构体,在32位系统上大小可能是8字节而不是5字节。使用 sizeof(Student) 可以获取其实际大小。在涉及网络传输或二进制文件读写时,必须考虑这一点。

10.2 联合体:共享内存的“变体”

如果说结构体是多个房间的套房,那么联合体(union)就是同一个房间,在不同时间可以布置成卧室或书房。它允许你在同一块内存位置存储不同的数据类型,但任何时候只有一个成员是有效的。

联合体的定义与特性

联合体使用关键字 union 定义,语法与结构体类似,但其所有成员共享同一段内存空间。

union DataPacket {
    int intValue;
    float floatValue;
    char str[4];
};

DataPacket packet;
packet.intValue = 0x41424344; // 赋值给int成员
// 此时访问 packet.floatValue 或 packet.str 将解释同一段内存数据

联合体的大小至少能容纳其最大的成员。上面例子中,int 和 float 通常是4字节,char str[4] 也是4字节,所以 sizeof(DataPacket) 通常是4。

结构体(struct) vs 联合体(union)
特性结构体 (struct)联合体 (union)
内存分配每个成员拥有独立的内存空间所有成员共享同一块内存空间
总大小≥ 所有成员大小之和(考虑对齐)= 最大成员的大小(考虑对齐)
数据存储可同时存储所有成员的值同一时间只能存储一个成员的值
主要用途聚合相关数据(如学生信息)节省内存、类型转换、协议解析
访问方式使用 . 或 -> 运算符使用 . 或 -> 运算符

场景1:协议解析

  • 网络数据包头部可能有不同类型
  • 用联合体根据“类型”字段解释后续数据
  • 节省定义多个结构体的开销

场景2:寄存器映射

  • 嵌入式开发中,一个32位寄存器
  • 可整体访问,也可按位段访问
  • 联合体实现硬件寄存器的不同视图

场景3:变体数据

  • 数据可能是整数、浮点数或短字符串
  • 配合一个“类型标签”成员使用
  • 根据标签决定如何解释联合体数据

编者提示:联合体与指针结合时需格外小心,因为对其中一个成员的赋值会覆盖其他成员的值。这与第7章学习的指针解引用操作原理紧密相关,不当使用可能导致难以察觉的内存错误。

10.3 结构体与联合体的高级用法

结构体和联合体可以嵌套使用,以构建更复杂的数据模型。一个常见的模式是:结构体内包含一个联合体,并附带一个标签来指示当前联合体中哪个成员有效。

struct SensorData {
    enum DataType { INT, FLOAT, ERROR } type; // 类型标签
    union {
        int iVal;
        float fVal;
        char errCode;
    } value; // 匿名联合体,可直接访问其成员
};

SensorData data;
data.type = SensorData::FLOAT;
data.value.fVal = 36.5; // 直接访问联合体成员

// 根据类型标签安全地读取数据
if (data.type == SensorData::FLOAT) {
    cout << "温度:" << data.value.fVal << endl;
}

实际开发中,大部分团队会使用这种“标签联合”模式来处理变体数据,因为它比纯粹的联合体更安全,能避免误用无效成员。

工程实践:位域

在结构体中,可以对整型成员使用“位域”来指定其占用内存的位数。这在处理硬件寄存器或需要极致压缩数据的场景(如网络协议头)中非常有用。

struct StatusRegister {
    unsigned int errorFlag : 1;  // 1位
    unsigned int mode : 3;      // 3位
    unsigned int reserved : 28; // 28位
};

注意:位域的行为依赖于编译器和硬件平台,可移植性较差,仅在必要时使用。

动手试一试

1. 定义一个 Book 结构体,包含书名(字符数组)、ISBN号(长整型)、价格(浮点数)和库存量(整型)。创建一个包含3本书的数组,并编写一个函数 printBookInfo 来打印任意一本书的信息。

2. 创建一个联合体 Converter,包含一个 float 成员 f 和一个 unsigned char 数组成员 bytes[4]。给 f 赋值 3.14,然后循环打印 bytes 数组的每个元素(以十六进制格式),观察浮点数在内存中的字节表示。

3. (挑战)定义一个结构体 NetworkPacket,它使用匿名联合体。联合体包含两个结构体成员:一个 Ipv4Header(包含源IP、目的IP等)和一个 ArpHeader(包含硬件类型、协议类型等)。在 NetworkPacket 中添加一个 packetType 字段,并模拟根据类型解析不同协议头的过程。

检验你的理解

  1. 以下代码的输出是什么?为什么?
    struct Test { char a; int b; char c; };
    cout << sizeof(Test) << endl; // 假设在32位系统上
  2. 下面的联合体变量 u 在赋值后,成员 ch 的值是什么?
    union U { int num; char ch; };
    U u;
    u.num = 65;
  3. 判断对错:结构体变量可以作为函数参数传递,也可以作为函数返回值。
  4. 使用结构体指针时,访问其成员 id 的正确语法是 p->id 还是 p.id?

本章小结

  • 结构体(struct)是一种复合数据类型,用于将多个不同类型的变量捆绑成一个逻辑整体,便于管理。通过点运算符(.)访问成员,通过箭头运算符(->)访问指针指向的结构体成员。
  • 联合体(union)的所有成员共享同一块内存空间,其大小由最大成员决定。它用于节省内存、实现数据的多种解释,但使用时需配合类型标签以确保安全。
  • 结构体和联合体可以嵌套使用,构建复杂的数据模型。“标签联合”是处理变体数据的常用且安全的模式。
  • 理解结构体的内存对齐至关重要,它影响结构体的大小和内存布局,尤其在涉及底层操作(如网络通信、硬件交互)时。
  • 位域是结构体的高级特性,用于精确控制成员占用的比特数,但牺牲了可移植性,应谨慎使用。

意见反馈

微信二维码 微信扫码关注 抖音二维码 抖音扫码关注