08 C++结构体详解
8.1 为什么要学结构体
在前面的学习中,我们已经接触了 int、double、char、数组等数据类型。它们都很重要,但有一个共同限制:
- 基本类型一次只能表达一种“单一信息”;
- 数组虽然能存多个值,但要求元素类型一致;
- 真实项目中的对象往往由多种属性共同组成。
例如,一个学生通常至少有姓名、年龄、学号、成绩;一本书会有书名、作者、价格、库存;一个游戏角色可能有名称、等级、血量、攻击力、坐标等信息。这些属性的类型并不一样。如果仅靠基本类型和普通数组来组织,代码会很快变得混乱,变量数量爆炸,维护困难。
结构体(struct)的价值就在于:
- 把“同一对象”的多个属性打包成一个整体;
- 让代码语义更清晰,变量名更少但信息更完整;
- 能与数组、指针、函数组合,形成工程化代码风格。
你可以把结构体理解为“程序员自定义的数据模板”。定义模板后,就能批量创建同类型对象。
8.2 结构体的基本定义与使用
8.2.1 语法结构
struct 结构体名 {
成员类型 成员名;
成员类型 成员名;
...
};
注意最后的分号不能漏。
8.2.2 示例:定义学生结构体
#include <iostream>
#include <string>
using namespace std;
struct Student {
string name;
int age;
int score;
};
int main() {
Student s1;
s1.name = "张三";
s1.age = 18;
s1.score = 92;
cout << "姓名: " << s1.name << endl;
cout << "年龄: " << s1.age << endl;
cout << "成绩: " << s1.score << endl;
return 0;
}
8.2.3 初始化方式
结构体变量常见初始化方式:
- 先定义再逐个赋值;
- 定义时使用花括号一次性初始化。
Student a;
a.name = "李四";
a.age = 19;
a.score = 85;
Student b = {"王五", 20, 88};
8.3 结构体成员访问与内存理解
结构体变量使用点号 . 访问成员:
cout << s1.name << endl;
cout << s1.score << endl;
从内存角度看,结构体是“一段连续内存中的复合数据”。每个成员占据一部分空间,编译器可能会因为对齐规则在成员之间插入空隙。这意味着:
sizeof(结构体类型)往往不等于所有成员sizeof之和;- 对齐是为了提高访问效率;
- 不要主观假设结构体大小,使用
sizeof才准确。
示例:
#include <iostream>
using namespace std;
struct A {
char c;
int x;
};
int main() {
cout << "sizeof(A) = " << sizeof(A) << endl;
return 0;
}
在不同编译器或平台上,结果可能不同。
8.4 结构体数组:管理一组同类对象
当我们有很多学生对象时,用结构体数组比散乱变量更高效。
#include <iostream>
#include <string>
using namespace std;
struct Student {
string name;
int age;
int score;
};
int main() {
Student cls[3] = {
{"张三", 18, 90},
{"李四", 19, 85},
{"王五", 18, 95}
};
for (int i = 0; i < 3; i++) {
cout << "第" << i + 1 << "位: "
<< cls[i].name << " "
<< cls[i].age << "岁 "
<< "成绩=" << cls[i].score << endl;
}
return 0;
}
8.4.1 应用:找最高分
int bestIndex = 0;
for (int i = 1; i < 3; i++) {
if (cls[i].score > cls[bestIndex].score) {
bestIndex = i;
}
}
cout << "最高分: " << cls[bestIndex].name
<< " " << cls[bestIndex].score << endl;
这个场景非常常见:查找最大值、排序、统计平均分、筛选及格学生等。
8.5 结构体指针:用地址操作对象
8.5.1 基本写法
Student stu = {"小明", 18, 91};
Student* p = &stu;
访问成员使用 ->:
cout << p->name << endl;
p->score = 100;
完整示例:
#include <iostream>
#include <string>
using namespace std;
struct Student {
string name;
int age;
int score;
};
int main() {
Student stu = {"小明", 18, 91};
Student* p = &stu;
cout << "修改前: " << p->name << " " << p->score << endl;
p->score = 96;
cout << "修改后: " << p->name << " " << p->score << endl;
return 0;
}
8.5.2 什么时候优先使用指针
- 需要在函数中修改原对象;
- 避免值传递拷贝带来的开销;
- 需要动态分配对象(后续面向对象和堆内存会用到)。
8.6 结构体作为函数参数
结构体传参有两大方式:
- 值传递:会复制一份,函数里修改不影响实参;
- 地址传递:传入地址,函数里可直接改实参。
8.6.1 值传递示例
#include <iostream>
#include <string>
using namespace std;
struct Student {
string name;
int age;
int score;
};
void increaseByValue(Student s) {
s.score += 10;
cout << "函数内(值传递)成绩: " << s.score << endl;
}
int main() {
Student stu = {"赵六", 18, 70};
increaseByValue(stu);
cout << "主函数成绩: " << stu.score << endl;
return 0;
}
运行后你会看到:函数内分数变了,主函数原值没变。
8.6.2 地址传递示例
#include <iostream>
#include <string>
using namespace std;
struct Student {
string name;
int age;
int score;
};
void increaseByAddress(Student* s) {
s->score += 10;
}
int main() {
Student stu = {"赵六", 18, 70};
increaseByAddress(&stu);
cout << "主函数成绩: " << stu.score << endl;
return 0;
}
8.6.3 更推荐的只读传参
如果函数只读取数据,不需要修改对象,建议使用:
void printStudent(const Student& s)
这是“常量引用传参”,可避免拷贝,且防止误改数据,性能和安全性更平衡。
8.7 结构体嵌套结构体
现实对象常有层级关系,比如“老师带学生”,“订单里有收货地址”,“公司里有部门”。结构体嵌套能很好表达这种关系。
示例:老师结构体里包含学生结构体。
#include <iostream>
#include <string>
using namespace std;
struct Student {
string name;
int age;
int score;
};
struct Teacher {
int id;
string name;
Student apprentice;
};
int main() {
Teacher t;
t.id = 1001;
t.name = "陈老师";
t.apprentice.name = "小王";
t.apprentice.age = 18;
t.apprentice.score = 89;
cout << "老师: " << t.name << "(" << t.id << ")" << endl;
cout << "学生: " << t.apprentice.name
<< " 年龄=" << t.apprentice.age
<< " 分数=" << t.apprentice.score << endl;
return 0;
}
8.8 const 与结构体:防误改的关键
const 在结构体章节非常实用,主要用于函数参数和指针约束。
8.8.1 const 指针参数
void show(const Student* s) {
cout << s->name << " " << s->score << endl;
// s->score = 100; // 编译错误,避免误改
}
8.8.2 const 引用参数
void show(const Student& s) {
cout << s.name << " " << s.score << endl;
}
在工程中,只读函数优先 const 引用 是很常见的规范。
8.9 综合案例一:老师与学生分配系统
8.9.1 需求描述
- 有 3 位老师;
- 每位老师带 5 名学生;
- 需要自动生成姓名并分配随机成绩;
- 最后打印完整信息。
8.9.2 代码实现
#include <iostream>
#include <string>
#include <ctime>
using namespace std;
struct Student {
string name;
int score;
};
struct Teacher {
string name;
Student students[5];
};
void allocateData(Teacher teachers[], int len) {
string teacherPrefix = "教师";
string studentPrefix = "学生";
string seed = "ABCDE";
for (int i = 0; i < len; i++) {
teachers[i].name = teacherPrefix + seed[i];
for (int j = 0; j < 5; j++) {
teachers[i].students[j].name = studentPrefix + seed[j];
teachers[i].students[j].score = rand() % 61 + 40; // 40~100
}
}
}
void printData(const Teacher teachers[], int len) {
for (int i = 0; i < len; i++) {
cout << "====================" << endl;
cout << teachers[i].name << endl;
for (int j = 0; j < 5; j++) {
cout << " " << teachers[i].students[j].name
<< " 成绩=" << teachers[i].students[j].score << endl;
}
}
}
int main() {
srand((unsigned int)time(nullptr));
Teacher teachers[3];
int len = sizeof(teachers) / sizeof(teachers[0]);
allocateData(teachers, len);
printData(teachers, len);
return 0;
}
8.9.3 案例讲解
这个案例很适合训练以下能力:
- 结构体嵌套结构体;
- 结构体数组的批量处理;
- 函数分层:数据生成和数据展示分离;
const只读参数习惯。
在真实项目中,分层思想比“把所有代码塞到 main”重要得多。
8.10 综合案例二:英雄年龄排序(结构体 + 冒泡)
8.10.1 需求描述
创建 5 个英雄信息:姓名、年龄、性别。按年龄升序排序并输出。
8.10.2 代码实现
#include <iostream>
#include <string>
using namespace std;
struct Hero {
string name;
int age;
string gender;
};
void bubbleSort(Hero arr[], int len) {
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (arr[j].age > arr[j + 1].age) {
Hero temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
void printHeros(const Hero arr[], int len) {
for (int i = 0; i < len; i++) {
cout << arr[i].name << " "
<< arr[i].gender << " "
<< arr[i].age << endl;
}
}
int main() {
Hero arr[5] = {
{"刘备", 23, "男"},
{"关羽", 22, "男"},
{"张飞", 20, "男"},
{"赵云", 21, "男"},
{"貂蝉", 19, "女"}
};
int len = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, len);
printHeros(arr, len);
return 0;
}
8.10.3 思路重点
- 比较条件是
arr[j].age > arr[j + 1].age; - 交换的单位是整个
Hero对象; - 打印函数使用
const参数,表达“只读”。
8.11 结构体常见错误与规避
8.11.1 忘记末尾分号
struct Student {
string name;
int age;
} // 少了分号
8.11.2 误用 . 与 ->
- 普通对象用
.; - 指针对象用
->。
8.11.3 值传递导致“改不动原对象”
函数参数是结构体值时,改的是副本。若要修改原对象请传地址或引用。
8.11.4 忽略初始化
结构体成员未初始化可能产生不确定值。建议定义时直接初始化,或在创建后立即赋值。
8.11.5 在头文件中滥用 using namespace std;
小项目问题不大,但工程化开发建议避免在头文件使用,以防命名污染。
8.12 提升建议:从“会用”到“用好”
- 先建模再写代码:先想清楚“这个对象有哪些属性”。
- 函数职责单一:创建、修改、打印尽量拆分。
- 优先 const 引用参数:读操作更安全,也更高效。
- 养成命名规范:结构体名用
PascalCase,成员名风格保持一致。 - 合理组织文件:结构体声明写到头文件,函数实现放源文件。
8.13 练习题(含思路提示)
练习 1:图书管理(基础)
定义 Book 结构体,成员包含 title、author、price、stock。创建 3 本书,打印价格最高的书。
提示:
- 用结构体数组存储图书;
- 遍历比较
price; - 保存最大值下标。
练习 2:通讯录(进阶)
定义 Contact 结构体,成员含 name、phone、email。实现函数:
addContact添加联系人;findByName按姓名查找;printAll打印全部。
提示:
- 先用定长数组实现;
- 再尝试扩展成
vector<Contact>(学完 STL 后再做)。
练习 3:班级成绩分析(综合)
定义 Student 结构体并建立数组,完成:
- 计算班级平均分;
- 找出最高分和最低分;
- 按成绩降序排序并输出。
提示:
- 平均分注意类型转换,避免整除误差;
- 排序时交换整个结构体对象。
8.14 小结
这一章的核心不是“记语法”,而是建立数据建模思维。结构体让你能够把零散变量组织成有意义的对象,并且与数组、指针、函数协同工作,形成清晰可维护的程序结构。你应重点掌握以下能力:
- 能定义并初始化结构体;
- 能使用结构体数组管理批量对象;
- 能通过指针与函数参数灵活读写对象;
- 能借助
const写出更安全的接口; - 能通过综合案例把“结构体 + 流程控制 + 排序算法”串起来。
如果你把本章示例全部独立敲一遍,并在此基础上做小改造(比如新增字段、改变排序条件、增加过滤功能),你会明显感受到自己对 C++ 程序组织能力的提升。这种提升不仅服务于基础语法,更会直接影响后续类、对象、STL、项目实践的学习效率。
继续前进时,请记住一句话:
好程序不是只会跑,而是数据结构清晰、接口边界明确、代码容易维护。结构体正是这条路上的第一块基石。