← 目录 / C++ 编程语法

十、结构体

结构体允许把不同类型的相关数据组合成一个整体,比如把学生的姓名、年龄、分数打包在一起,是竞赛中组织复杂数据的核心工具。

定义结构体

结构体用 struct 关键字定义,把多个不同类型的数据字段打包成一种新的数据类型。定义好后可以像 intdouble 一样使用它来创建变量。

struct Student
{
  string name; // 姓名
  int age; // 年龄
  float score; // 分数
}; // ⚠️ 分号不能少!
↑ 这只是类型定义,不占内存
创建变量时才分配空间
Student s1
string name "小明"
int age 15
float score 92.5
⚠️
结构体定义末尾的分号 ; 必须写!这是初学者最容易遗漏的地方,缺少分号会导致编译错误,且错误提示通常很难看懂。
📌
三点说明:
① 结构体通常定义在 main() 之前,以便全局使用。
② 结构体只定义了一种新的数据类型,本身不占内存,只有用它创建变量时才分配空间。
③ 竞赛中常见的结构体:坐标点 Point、图的边 Edge、学生信息 Student 等。

使用结构体

定义好结构体后,可以像普通类型一样创建结构体变量,通过 .(点运算符)访问其成员。

点运算符:结构体变量 · 成员名
s1
.
name
"小明"
s1
.
age
15
s1
.
score
92.5
C++ · 结构体变量创建与访问
1// 方式1:先定义,再逐成员赋值
2Student s1;
3s1.name = "小明"; // 用 . 访问成员
4s1.age = 15;
5s1.score = 92.5;
6
7cout << "姓名:" << s1.name << endl;
8cout << "成绩:" << s1.score << endl;
9
10// 方式2:初始化时直接赋值(按成员定义顺序)
11Student s2 = {"小红", 14, 95.0}; // 顺序要与定义一致

结构体数组

可以用结构体数组存储多个同类型数据,每个元素都是一个完整的结构体变量,用 [i].成员名 访问。

students[0]
name"小明"
age15
score92.5
students[1]
name"小红"
age14
score95.0
students[2]
name"小华"
age16
score88.0
C++ · 结构体数组 & vector
1// 静态数组
2Student students[3] =
3{
4 {"小明", 15, 92.5},
5 {"小红", 14, 95.0},
6 {"小华", 16, 88.0}
7};
8
9// 遍历结构体数组
10for (int i = 0; i < 3; i++)
11 cout << students[i].name << ":" << students[i].score << endl;
12
13// 结合 vector(动态大小,竞赛中更常用)
14vector<Student> stuList;
15stuList.push_back({"小明", 15, 92.5});
16stuList.push_back({"小红", 14, 95.0});
17
18for (auto &s : stuList) // 范围 for 循环 + 引用
19 cout << s.name << " " << s.score << endl;

结构体的成员函数

前面的例子里,"数据"(姓名、年龄、分数)和"操作数据的函数"(比如打印学生信息)是分开写的:数据放在 struct 里,函数则是一个独立的普通函数,调用时要把结构体变量当参数传进去,像 PrintStudent(s1) 这样。

其实 C++ 的 struct 不只能装数据,也可以把"操作这些数据的函数"直接写在结构体内部,这样的函数叫成员函数。成员函数天生就能访问同一个结构体里的其他成员,不需要再通过参数传入、也不需要加前缀。

C++ · 把函数写进结构体内部
1struct Student
2{
3 string name;
4 int age;
5 float score;
6
7 // 成员函数:写在结构体内部,直接用 name、score,不用加前缀
8 void Print()
9 {
10 cout << name << " " << score << endl;
11 }
12
13 // 成员函数也可以有返回值和参数,和普通函数一样
14 bool IsPass()
15 {
16 return score >= 60.0; // 60 分及格
17 }
18};
19
20int main()
21{
22 Student s1 = {"小明", 15, 92.5};
23 s1.Print(); // 小明 92.5,用 . 调用成员函数,和访问成员变量的写法一样
24 if (s1.IsPass()) cout << "及格了!";
25}
💡
对比一下:之前写法是 PrintStudent(s1)——把 s1 当"原料"喂给一个外部函数;现在写法是 s1.Print()——直接叫 s1 自己去执行"打印"这个动作。后者读起来更像一句话:"让 s1 打印自己",这正是"面向对象"思路的雏形:把数据和操作数据的行为绑定在一起。第十二章会在此基础上系统展开。

构造函数:创建对象时自动执行的初始化代码

前面创建 Student 变量,要么逐个成员赋值(s1.name = "小明";),要么用花括号按顺序初始化(Student s1 = {"小明", 15, 92.5};)。C++ 还提供了第三种、更灵活的方式——构造函数:一个和结构体同名没有返回类型的特殊成员函数,在每次创建对象时会被自动调用,专门用来完成初始化工作。

C++ · 构造函数
1struct Student
2{
3 string name;
4 int age;
5 float score;
6
7 // 构造函数:函数名和结构体名一样叫 Student,没有返回类型(连 void 都不写)
8 Student(string n, int a, float sc)
9 {
10 name = n;
11 age = a;
12 score = sc;
13 cout << name << " 被创建了!" << endl;
14 }
15};
16
17int main()
18{
19 Student s1("小明", 15, 92.5); // 创建的瞬间自动调用构造函数,输出"小明 被创建了!"
20 s1.Print();
21}
📌
构造函数的两个关键特征:① 函数名必须和结构体名完全一致;② 没有返回类型,连 void 都不写——如果写了返回类型,编译器就不认为它是构造函数了。创建对象时写 Student s1("小明", 15, 92.5)(圆括号传参),而不是花括号,两者会分别匹配到构造函数和之前学过的"按顺序初始化",本质上是两种不同的创建方式。

析构函数:对象销毁时自动执行的清理代码

和构造函数相对的是析构函数:对象销毁时(比如函数运行结束、变量离开作用域)自动调用的特殊函数,用来做"收尾工作"(比如释放构造函数里申请的资源)。写法是在结构体名前加一个 ~,没有返回类型,也不能有参数——因为它是自动触发的,不需要(也无法)手动传参。

C++ · 析构函数
1struct Student
2{
3 string name;
4
5 Student(string n) { name = n; cout << name << " 出生" << endl; } // 构造函数
6 ~Student() { cout << name << " 销毁" << endl; } // 析构函数:~ + 结构体名,无参数无返回类型
7};
8
9int main()
10{
11 Student s1("小明"); // 输出:小明 出生
12 cout << "---函数继续运行---" << endl;
13} // s1 在这里离开作用域,自动输出:小明 销毁
① 构造函数自动执行
对象诞生时(如 Student s1(...) 这一行)
② 正常使用对象
调用成员函数、访问/修改成员变量
③ 析构函数自动执行
对象销毁时(如离开 { } 作用域)
⚠️
不用手动调用构造/析构函数!它们都是自动触发的——创建对象时自动调用构造函数,对象销毁时自动调用析构函数,代码里永远不会看到 s1.Student(...)s1.~Student() 这样主动调用它们的写法。目前我们还没学到动态内存分配(new/delete),析构函数暂时只用来做简单的收尾提示;等第十二章学到动态内存管理后,析构函数会承担起"自动释放资源、防止内存泄漏"的重要职责。
📖
本节只介绍成员函数、构造函数、析构函数最基础的用法。更完整的内容——构造函数的三种写法、初始化列表、拷贝构造函数、运算符重载等——将在 第十二章「类与面向对象」 中系统讲解。竞赛中的结构体大多数时候只用来"打包数据",不必强行加上构造/析构函数;但理解这两个概念,能帮你更顺利地过渡到面向对象编程。

结构体作为函数参数

结构体可以作为函数参数传递,同样分为值传递和引用传递两种方式。由于结构体往往包含多个字段,引用传递更为推荐,可避免昂贵的拷贝开销。

📋值传递(拷贝)
函数收到结构体的完整副本,修改不影响原对象。对于多字段结构体,拷贝开销较大。
void Print(Student s)
🔗引用传递(推荐)
函数直接操作原结构体,无拷贝开销。需要只读时加 const
void Update(Student &s)
C++ · 结构体作为函数参数
1// 值传递:函数内修改不影响原结构体
2void PrintStudent(Student s)
3{
4 cout << s.name << " " << s.score << endl;
5}
6
7// 引用传递:直接修改原结构体(推荐)
8void UpdateScore(Student &s, float newScore)
9{
10 s.score = newScore; // 直接修改原结构体
11}
12
13int main()
14{
15 Student s = {"小明", 15, 90.0};
16 PrintStudent(s); // 输出:小明 90
17 UpdateScore(s, 95.0); // 修改成绩
18 PrintStudent(s); // 输出:小明 95
19}
使用场景推荐方式
只需读取结构体内容const 引用const Student &s),避免拷贝开销
需要修改结构体内容普通引用Student &s
结构体很小(只有两三个基本类型字段)值传递也可以,开销可忽略

结构体与排序

竞赛中,结构体最常用的场景之一就是配合 sort() 进行自定义排序。需要提供一个比较函数 Cmp,告诉 sort 按什么规则排序。

按成绩从高到低排序的过程
排序前
[0]小明92.5
[1]小红95.0
[2]小华88.0
sort() + Cmp
排序后(成绩↓)
[0]小红95.0 🥇
[1]小明92.5 🥈
[2]小华88.0 🥉
// 比较函数:返回 true 表示 a 排在 b 前面
bool Cmp(const Student &a, const Student &b)
{
  return a.score > b.score; // 大于 → 成绩高的排前面(降序)
}
C++ · 结构体排序完整示例
1#include <algorithm>
2#include <vector>
3
4struct Student { string name; int age; float score; };
5
6// 自定义比较函数:成绩高的排前面
7bool Cmp(const Student &a, const Student &b)
8{ return a.score > b.score; }
9
10int main()
11{
12 vector<Student> v =
13 { {"小明",15,92.5}, {"小红",14,95.0}, {"小华",16,88.0} };
14
15 sort(v.begin(), v.end(), Cmp); // 传入比较函数
16
17 for (auto &s : v)
18 cout << s.name << ":" << s.score << endl;
19 // 输出:小红:95 小明:92.5 小华:88
20}
🏆
竞赛中 Cmp 函数的规则记忆:
return a.score > b.score → 成绩大的在前 → 降序
return a.score < b.score → 成绩小的在前 → 升序
多关键字排序:return a.score != b.score ? a.score > b.score : a.name < b.name(成绩不同按成绩降序,相同按姓名升序)