struct(结构体)——把姓名、分数这些
属于同一个人的数据打包成一个整体,用点号访问其中的成员,
再把它和 vector 组合起来,管理一整个班的学生。
struct(结构体)——把姓名、分数这些
属于同一个人的数据打包成一个整体,用点号访问其中的成员,
再把它和 vector 组合起来,管理一整个班的学生。
第 18 讲的成绩统计程序,只存了分数。如果还想记住每个分数是谁的,
按现在的办法,只能开两个 vector:
std::vector<std::string> names; // 姓名放这里
std::vector<int> scores; // 分数放那里
// names[2] 和 scores[2] 是同一个人——全靠下标对齐,全靠自觉
// 万一某次只往其中一个 push_back,两个 vector 就错位了 vector 在语法上毫无关系,
“第 2 个名字和第 2 个分数是同一个人”这件事,编译器根本不知道,全靠你自己记牢。
一旦某处只更新了一个、漏了另一个,数据就错位了,
而且这种 bug 极难发现——程序照样能跑,只是每个人的分数都张冠李戴。
更合理的想法是:既然姓名和分数本来就属于同一个学生,
那就应该把它们绑成一个东西,而不是拆成两半分开放。
struct 做的就是这件事——把几个相关的变量,打包成一个新的类型。
struct Student {
std::string name;
int score;
}; // 注意:这里的分号不能少 | 部分 | 例子 | 含义 |
|---|---|---|
| struct | struct | 固定关键字,表示要定义结构体 |
| 类型名 | Student | 你给这个新类型起的名字 |
| 成员 | name、score | 打包进来的那些变量 |
}; ——结构体定义的末尾必须有分号。
这和函数、if、for 的大括号都不一样(那些后面不加分号),
是初学 struct 时最高频的编译错误。
struct Student { … }; 只是设计了一张图纸,
告诉编译器“以后有一种东西叫 Student,它由姓名和分数组成”——
这时还没有任何学生存在。真正造出一个学生,要等到下面
Student s; 这一步。
struct 定义好之后,Student 就成了一个和
int、double 地位相同的类型,可以直接拿来声明变量。
访问里面的成员,用点号 .:
#include <iostream>
#include <string>
struct Student {
std::string name;
int score;
};
int main() {
Student s; // 声明一个 Student 变量
s.name = "小明"; // 用点号访问成员
s.score = 90;
std::cout << s.name << " 的分数是 " << s.score << std::endl;
return 0;
} s.name 是 std::string,s.score 是 int——
它们能做的事,和普通的 string、int 变量完全一样:
能赋值、能 cin 读入、能参与运算、能拿去 if 判断。
唯一的区别只是名字前面多了 s. 这个前缀。
和数组的初始化列表很像,用花括号按成员声明的顺序依次给值:
Student s = {"小明", 90}; // 按声明顺序,一次性给全部成员赋值
// 等价于:
// Student s;
// s.name = "小明";
// s.score = 90;
仿照 struct_use.cpp,定义一个 struct Book,
包含三个成员:书名(std::string title)、作者
(std::string author)、页数(int pages)。
main 里创建一本书,给三个成员各赋一个值。
第 16 讲说过,std::vector<类型> 尖括号里写要装的元素类型。
既然 Student 现在也是一个正经类型,那当然可以写
std::vector<Student>——一个装学生的 vector:
#include <iostream>
#include <string>
#include <vector>
struct Student {
std::string name;
int score;
};
int main() {
std::vector<Student> students; // 一个装 Student 的 vector
students.push_back({"小明", 90});
students.push_back({"小红", 85});
students.push_back({"小刚", 78});
for (int i = 0; i < students.size(); i++) {
std::cout << students[i].name << ":" << students[i].score << std::endl;
}
return 0;
} students[i] 先取出第 i 个学生
(这是一个完整的 Student),再 .name 取出这个学生的姓名。
先选人,再选属性。
vector:姓名和分数现在长在同一个
Student 里面,push_back 一次加入一个
Student,姓名和分数会作为同一条记录一起移动,
不再出现两个容器长度不同造成的错位——用类型固定数据之间的归属关系,
比“靠自己记得对齐”可靠得多。
实际程序里,学生信息往往是用户输入的。先填好一个 Student 变量,
再整个 push_back 进去:
Student s;
std::cout << "姓名:";
std::cin >> s.name;
std::cout << "分数:";
std::cin >> s.score;
students.push_back(s); // 填好一个,整个塞进 vector
定义 struct Book(书名、作者、页数),在 main
里创建两本书并分别打印它们的完整信息。
先输入班级人数,再用循环逐个录入每个学生的姓名和分数,存进
std::vector<Student>,最后遍历打印所有学生。
提示:把 struct_input.cpp 的录入逻辑放进
第 18 讲那样的 for 循环里。
在练习 19-2 的基础上,用第 15 讲的打擂台算法, 找出分数最高的学生,并打印他的姓名和分数。
int maxScore 记住最高分,
最后还能知道这个分数是谁的吗?试试直接用一个
Student 当擂主,比较时比 .score。
| 能力 | 用到的工具 |
|---|---|
| 把相关数据打包成新类型 | struct 类型名 { 成员; }; |
| 创建结构体变量 | Student s; 或 Student s = {…}; |
| 访问某个成员 | 点号 s.name、s.score |
| 批量管理一组对象 | std::vector<Student> |
最该记住:数据之间有关系,就让类型体现这种关系——
别用几个平行的 vector 加“自觉对齐”来凑合。
main 里了——
这正是第 11 讲开头抱怨过的问题。下节课把这些操作封装进函数,
学会怎么把 vector 传给函数,让代码结构真正清爽起来。