← 目录 / C++ 编程语法
结构体允许把不同类型的相关数据组合成一个整体,比如把学生的姓名、年龄、分数打包在一起,是竞赛中组织复杂数据的核心工具。
10.1
结构体用 struct 关键字定义,把多个不同类型的数据字段打包成一种新的数据类型。定义好后可以像 int、double 一样使用它来创建变量。
; 必须写!这是初学者最容易遗漏的地方,缺少分号会导致编译错误,且错误提示通常很难看懂。main() 之前,以便全局使用。Point、图的边 Edge、学生信息 Student 等。10.2
定义好结构体后,可以像普通类型一样创建结构体变量,通过 .(点运算符)访问其成员。
| 1 | // 方式1:先定义,再逐成员赋值 |
| 2 | Student s1; |
| 3 | s1.name = "小明"; // 用 . 访问成员 |
| 4 | s1.age = 15; |
| 5 | s1.score = 92.5; |
| 6 | |
| 7 | cout << "姓名:" << s1.name << endl; |
| 8 | cout << "成绩:" << s1.score << endl; |
| 9 | |
| 10 | // 方式2:初始化时直接赋值(按成员定义顺序) |
| 11 | Student s2 = {"小红", 14, 95.0}; // 顺序要与定义一致 |
10.3
可以用结构体数组存储多个同类型数据,每个元素都是一个完整的结构体变量,用 [i].成员名 访问。
| 1 | // 静态数组 |
| 2 | Student students[3] = |
| 3 | { |
| 4 | {"小明", 15, 92.5}, |
| 5 | {"小红", 14, 95.0}, |
| 6 | {"小华", 16, 88.0} |
| 7 | }; |
| 8 | |
| 9 | // 遍历结构体数组 |
| 10 | for (int i = 0; i < 3; i++) |
| 11 | cout << students[i].name << ":" << students[i].score << endl; |
| 12 | |
| 13 | // 结合 vector(动态大小,竞赛中更常用) |
| 14 | vector<Student> stuList; |
| 15 | stuList.push_back({"小明", 15, 92.5}); |
| 16 | stuList.push_back({"小红", 14, 95.0}); |
| 17 | |
| 18 | for (auto &s : stuList) // 范围 for 循环 + 引用 |
| 19 | cout << s.name << " " << s.score << endl; |
10.4
前面的例子里,"数据"(姓名、年龄、分数)和"操作数据的函数"(比如打印学生信息)是分开写的:数据放在 struct 里,函数则是一个独立的普通函数,调用时要把结构体变量当参数传进去,像 PrintStudent(s1) 这样。
其实 C++ 的 struct 不只能装数据,也可以把"操作这些数据的函数"直接写在结构体内部,这样的函数叫成员函数。成员函数天生就能访问同一个结构体里的其他成员,不需要再通过参数传入、也不需要加前缀。
| 1 | struct 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 | |
| 20 | int 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++ 还提供了第三种、更灵活的方式——构造函数:一个和结构体同名、没有返回类型的特殊成员函数,在每次创建对象时会被自动调用,专门用来完成初始化工作。
| 1 | struct 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 | |
| 17 | int main() |
| 18 | { |
| 19 | Student s1("小明", 15, 92.5); // 创建的瞬间自动调用构造函数,输出"小明 被创建了!" |
| 20 | s1.Print(); |
| 21 | } |
void 都不写——如果写了返回类型,编译器就不认为它是构造函数了。创建对象时写 Student s1("小明", 15, 92.5)(圆括号传参),而不是花括号,两者会分别匹配到构造函数和之前学过的"按顺序初始化",本质上是两种不同的创建方式。和构造函数相对的是析构函数:对象销毁时(比如函数运行结束、变量离开作用域)自动调用的特殊函数,用来做"收尾工作"(比如释放构造函数里申请的资源)。写法是在结构体名前加一个 ~,没有返回类型,也不能有参数——因为它是自动触发的,不需要(也无法)手动传参。
| 1 | struct Student |
| 2 | { |
| 3 | string name; |
| 4 | |
| 5 | Student(string n) { name = n; cout << name << " 出生" << endl; } // 构造函数 |
| 6 | ~Student() { cout << name << " 销毁" << endl; } // 析构函数:~ + 结构体名,无参数无返回类型 |
| 7 | }; |
| 8 | |
| 9 | int main() |
| 10 | { |
| 11 | Student s1("小明"); // 输出:小明 出生 |
| 12 | cout << "---函数继续运行---" << endl; |
| 13 | } // s1 在这里离开作用域,自动输出:小明 销毁 |
Student s1(...) 这一行)
{ } 作用域)
s1.Student(...) 或 s1.~Student() 这样主动调用它们的写法。目前我们还没学到动态内存分配(new/delete),析构函数暂时只用来做简单的收尾提示;等第十二章学到动态内存管理后,析构函数会承担起"自动释放资源、防止内存泄漏"的重要职责。10.5
结构体可以作为函数参数传递,同样分为值传递和引用传递两种方式。由于结构体往往包含多个字段,引用传递更为推荐,可避免昂贵的拷贝开销。
const。| 1 | // 值传递:函数内修改不影响原结构体 |
| 2 | void PrintStudent(Student s) |
| 3 | { |
| 4 | cout << s.name << " " << s.score << endl; |
| 5 | } |
| 6 | |
| 7 | // 引用传递:直接修改原结构体(推荐) |
| 8 | void UpdateScore(Student &s, float newScore) |
| 9 | { |
| 10 | s.score = newScore; // 直接修改原结构体 |
| 11 | } |
| 12 | |
| 13 | int 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) |
| 结构体很小(只有两三个基本类型字段) | 值传递也可以,开销可忽略 |
10.6
竞赛中,结构体最常用的场景之一就是配合 sort() 进行自定义排序。需要提供一个比较函数 Cmp,告诉 sort 按什么规则排序。
| 1 | #include <algorithm> |
| 2 | #include <vector> |
| 3 | |
| 4 | struct Student { string name; int age; float score; }; |
| 5 | |
| 6 | // 自定义比较函数:成绩高的排前面 |
| 7 | bool Cmp(const Student &a, const Student &b) |
| 8 | { return a.score > b.score; } |
| 9 | |
| 10 | int 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 | } |
return a.score > b.score → 成绩大的在前 → 降序return a.score < b.score → 成绩小的在前 → 升序return a.score != b.score ? a.score > b.score : a.name < b.name(成绩不同按成绩降序,相同按姓名升序)