🔷 第八章 结构体与共用体
📄 一句话概括

结构体 = 把几个"不同类型"的变量打包成一个整体,起一个名字。 第七章的数组解决的是"一排同样类型的格子"(50 个分数),而现实里的"一条记录"往往是混着来的:一个学生的姓名是文字、年龄是整数、成绩是小数。数组装不下这种"混装",结构体就是为这种"一条记录"准备的口袋。

📝 本节和前面几节的关系

第七章我们一直在处理同类型的数据:int a[50] 是 50 个整数,char name[20] 是 20 个字符。可只要你想表达"一个学生",就立刻卡住——它由三种类型拼起来。这一章把这个短板补上:8.1 讲结构体是什么,8.2 讲给它起别名,8.3 让它进函数,8.4 让它组成数组,8.5 认识它的"共享内存版兄弟"共用体,8.6 认识枚举。这五节全建立在第七章"地址"的地基上(尤其是 8.3.2 和 8.4.2 要复用 7.6.1、7.5.1 的指针套路)。

一、数组装不下的那种数据

先看一段"没有结构体时"的代码。假设要记 3 个学生的姓名、年龄、成绩:

char name1[20] = "张三";   int age1 = 18;   double score1 = 92.5;
char name2[20] = "李四";   int age2 = 19;   double score2 = 85.0;
char name3[20] = "王五";   int age3 = 20;   double score3 = 78.5;

能用,但一眼就能看出三个毛病:

毛病 说明
名字爆炸 3 个学生 9 个变量名;30 个学生 90 个
关系是"名字里隐含"的 name1 和 age1 是一伙的,这件事只存在于你脑子里,编译器不知道
没法整体搬运 想把"张三这条记录"传给函数?得一个一个传

根子上的问题:张三 18 92.5 明明是一条记录,代码里却是三个互不相干的变量。

二、结构体:一条记录 = 一个整体

结构体就是给"一条记录"画一张表格模板:

struct Student          // 模板名:Student(学生)
{
    char   name[20];    // 成员①:姓名
    int    age;         // 成员②:年龄
    double score;       // 成员③:成绩
};                      // ← 这个分号千万别漏!

读成大白话:"凡是叫 Student 的东西,都由一个姓名、一个年龄、一个成绩组成。"

之后再要记录,就只用起一个名字:

Student s1;             // 一个学生的全部信息
Student s2;
Student s3;

现在 s1 是"一条完整记录",能整体传递、整体赋值、塞进数组(8.4)、传给函数(8.3)。

三、和新老朋友对一对

数组(7.1) 结构体(本章)
装什么 一堆同类型数据 一组可以是不同类型的数据
怎么数 用下标:a[0] a[1] 用成员名:s.name s.age
内存 连续一排,每格一样大 连续一块(但有对齐填充,见 8.1.5)
能否整体赋值 不能(a = b; 报错) 能(s2 = s1; 合法)
像什么 一列同规格的柜子 一个多格抽屉柜,每格装不同的东西

关键直觉:数组是"横向铺开"(同类数据排成一排),结构体是"纵向打包"(一件东西的各个属性捆成一包)。现实中两者常常套着用——Student cls[30] 就是"30 个学生",每一格又是一个结构体(8.4 的内容)。

四、结构体在真实程序里长什么样

它几乎是所有"记录型数据"的标准做法:

一句话:只要你说得出"一条××由哪几项组成",就该用结构体。

五、本节的知识路线

本节是第八章的总入口,后面五篇按这个顺序展开:

小节 解决什么
8.1.1 结构体定义 模板怎么写(struct 语法、末尾分号)
8.1.2 结构体变量 用模板造出变量(Student s1;)
8.1.3 结构体成员及初始化 怎么给成员填值、怎么点开某一格
8.1.4 结构体的嵌套 成员本身又是结构体
8.1.5 结构体的大小 sizeof 为什么比"成员大小相加"大

六、动手 Demo:先感受一下"打包"的好处

先把上面那张模板敲进去,造两个学生,对比"输出一条记录"有多干净:

#include <iostream>
#include <cstring>              // strcpy 用(7.3)
using namespace std;

struct Student {
    char   name[20];
    int    age;
    double score;
};

int main() {
    Student s1;
    strcpy(s1.name, "张三");     // 字符数组不能直接 = "张三"(7.3)
    s1.age   = 18;
    s1.score = 92.5;

    cout << "姓名:" << s1.name  << endl;
    cout << "年龄:" << s1.age   << endl;
    cout << "成绩:" << s1.score << endl;

    cout << "这条记录占 " << sizeof(Student) << " 字节" << endl;
    return 0;
}

看到什么算成功:打印出张三的三项信息,最后一行显示 这条记录占 32 字节。

顺手一算:name 20 + age 4 + score 8 = 32,这次正好对上。但别以为总能这么直加——char c 加 int i 明明只有 5 字节,实际却占 8。这叫内存对齐,8.1.5 专门讲。

30 秒实验:把 struct Student 里的 double score; 改成 int score;,重新运行。总大小从 32 瘦到 28——double 一格 8 字节、int 一格 4 字节,换个成员类型,整个结构体就少 4 字节。成员的类型和顺序,真的会决定结构体占多少内存(8.1.5)。

七、常见误区

  1. "结构体就是数组的升级版"——不是。数组是同类型排一排,结构体是不同类型捆一包。它们解决的是两种不同的需求,而且常常套在一起用。
  2. "结构体定义完就占内存了"——没有。struct Student { ... }; 只是画图纸,一张图纸不占房间;只有 Student s1;(8.1.2)才真正分配空间。这个区分在 8.1.1 展开。
  3. "结构体只是把变量换个写法"——它的真正价值是让"一条记录"变成一个可以整体搬运的东西:整体赋值、进函数、进数组、进指针,全靠这一层封装。这是从"面向过程"迈向"面向对象"的第一步(第九章的类就长在它身上)。
  4. "student 和 Student 差不多"——C++ 区分大小写(1.3.2 的铁律)。结构体名习惯首字母大写,变量名小写,这样一眼能分清"模板"和"实例"。
自测三问(点开对答案) 1. 结构体和数组最本质的区别是什么?——数组只能装**同类型**的一排数据;结构体能装**不同类型**的一组数据,用来表示"一条记录"。 2. 为什么要用结构体,而不是给每个属性起一个独立变量?——① 不用起一堆名字;② 让"这几个数据属于同一条记录"这件事被**编译器也知道**;③ 可以整体赋值、传参、进数组。 3. 定义结构体这件事本身会分配内存吗?——不会。定义只是画图纸,**定义变量时才分配**。

相关笔记