结构体 = 把几个"不同类型"的变量打包成一个整体,起一个名字。 第七章的数组解决的是"一排同样类型的格子"(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 的内容)。
四、结构体在真实程序里长什么样
它几乎是所有"记录型数据"的标准做法:
- 通讯录:姓名 + 电话 + 地址
- 成绩表:学号 + 姓名 + 各科分数 + 总分
- 商品:名称 + 单价 + 库存
- 图形:圆心 x/y + 半径(7.2 那节我们用两个数组硬凑过,用结构体就自然了)
- 日期:年 + 月 + 日(8.1.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)。
七、常见误区
- "结构体就是数组的升级版"——不是。数组是同类型排一排,结构体是不同类型捆一包。它们解决的是两种不同的需求,而且常常套在一起用。
- "结构体定义完就占内存了"——没有。
struct Student { ... };只是画图纸,一张图纸不占房间;只有Student s1;(8.1.2)才真正分配空间。这个区分在 8.1.1 展开。 - "结构体只是把变量换个写法"——它的真正价值是让"一条记录"变成一个可以整体搬运的东西:整体赋值、进函数、进数组、进指针,全靠这一层封装。这是从"面向过程"迈向"面向对象"的第一步(第九章的类就长在它身上)。
- "
student和Student差不多"——C++ 区分大小写(1.3.2 的铁律)。结构体名习惯首字母大写,变量名小写,这样一眼能分清"模板"和"实例"。
自测三问(点开对答案)
1. 结构体和数组最本质的区别是什么?——数组只能装**同类型**的一排数据;结构体能装**不同类型**的一组数据,用来表示"一条记录"。 2. 为什么要用结构体,而不是给每个属性起一个独立变量?——① 不用起一堆名字;② 让"这几个数据属于同一条记录"这件事被**编译器也知道**;③ 可以整体赋值、传参、进数组。 3. 定义结构体这件事本身会分配内存吗?——不会。定义只是画图纸,**定义变量时才分配**。- ☐ 8.1.1 结构体定义(下一节)
相关笔记
- 一维数组 —— 同类型排一排:结构体的对照物
- 字符数组 —— 姓名这种成员为什么不能直接赋值
- 结构体定义 —— 下一节:模板怎么写
- 🔷 C++基础 · 索引 —— 全课路线图