vector 传给函数,理解传值会整份拷贝这个坑,
再学一个新工具——引用 &,让函数能真正改动外面的容器。
vector 传给函数,理解传值会整份拷贝这个坑,
再学一个新工具——引用 &,让函数能真正改动外面的容器。
上节课把学生信息打包进了 struct,数据是整齐了,
但录入、打印、统计的代码还是一股脑堆在 main 里:
int main() {
std::vector<Student> students;
// 录入……十几行
// 打印……十几行
// 求平均分……十几行
// 找最高分……十几行
return 0; // main 已经长到要滚屏才能看完了
} main 什么都干,就很难读、很难改、更难复用。
第 11 讲的解决办法今天照样管用:把每件事拆成一个函数。
只是这次要传给函数的,不再是一个 int,而是一整个
vector——这里有个新坑要踩。
参数类型写成 std::vector<Student>,调用时把容器名传进去,
函数里就能像用普通 vector 一样遍历它:
void printAll(std::vector<Student> students) {
for (int i = 0; i < students.size(); i++) {
std::cout << students[i].name << ":" << students[i].score << std::endl;
}
}
int main() {
std::vector<Student> students = {{"小明", 90}, {"小红", 85}};
printAll(students); // 一行调用,代替一整个循环
return 0;
} printAll 只是读数据打印出来,不修改任何东西,
所以这样写完全正确,能跑出正确结果。但如果函数需要改动容器,
问题就来了——下面就是这个坑。
第 11 讲学过:函数拿到的是实参的一份拷贝,
在函数里改形参,不影响外面。当时的例子是 int,
现在换成 vector,规矩一模一样:
void addOne(std::vector<int> v) {
v.push_back(999); // 改的是函数内部那份拷贝
}
int main() {
std::vector<int> nums = {1, 2, 3};
addOne(nums);
std::cout << nums.size() << std::endl; // 还是 3,不是 4!
return 0;
} addOne 拿到的 v 是 nums 的复制品——
push_back 加进去的 999,加在了这份复制品上。
函数一结束,复制品就被丢掉了,main 里的 nums 从头到尾没被碰过。
int 拷贝一下无所谓,但如果 vector 里装着几千个学生,
每次调用函数都要把这几千个学生完整复制一遍,既慢又占内存。
所以哪怕只是只读的 printAll,也有改进的余地。
在参数类型后面加上 &,这个参数就变成了引用——
不再复制一份,而是直接给原来那个变量起了个别名。
函数里对它做的任何改动,都实实在在落在 main 的那个容器上:
void addOne(std::vector<int>& v) { // 注意类型后面多了一个 &
v.push_back(999); // 现在改的就是 main 里的那个 nums
}
int main() {
std::vector<int> nums = {1, 2, 3};
addOne(nums);
std::cout << nums.size() << std::endl; // 4,改动生效了
return 0;
} std::vector<int> v:拷贝,函数里改了不算数。std::vector<int>& v:引用,函数里改了就是真改。&,但这是本节课最需要盯住的细节。
把 copy_problem.cpp 补成完整程序(记得 #include <vector>)
并运行,确认打印出的是 3。
&,别的一个字都不改,再运行一次。4——一个字符改变了整个程序的行为。
像 printAll 这种只读函数,我们希望:用引用省掉拷贝,
但又明确表示不会修改。这时在类型前面加 const:
// 只读:加 const,表示"我保证不改它"
void printAll(const std::vector<Student>& students) {
for (int i = 0; i < students.size(); i++) {
std::cout << students[i].name << std::endl;
}
}
// 要改:只加 &,不加 const
void addStudent(std::vector<Student>& students, Student s) {
students.push_back(s);
} const 之后,万一你在函数里手滑写了 students.push_back(…),
编译器会直接报错,而不是等程序跑起来才发现数据被改坏了。
这是让编译器帮你挡错误的好习惯。
| 写法 | 会拷贝吗 | 什么时候用 |
|---|---|---|
| vector<T> v | 会,慢 | 基本不用(除非真想要一份独立副本) |
| vector<T>& v | 不会 | 函数需要修改容器时 |
| const vector<T>& v | 不会 | 函数只读容器时 |
记一句话:传容器,默认就加 &;不改它,就再加 const。
把录入、打印、算平均分各封装成一个函数,main
就只剩下一条清晰的主线,一眼能看完:
#include <iostream>
#include <string>
#include <vector>
struct Student {
std::string name;
int score;
};
// 录入 n 个学生,装进 students
void inputStudents(std::vector<Student>& students, int n) {
for (int i = 0; i < n; i++) {
Student s;
std::cout << "第 " << (i + 1) << " 个学生姓名:";
std::cin >> s.name;
std::cout << "分数:";
std::cin >> s.score;
students.push_back(s);
}
}
// 打印全部学生
void printAll(const std::vector<Student>& students) {
for (int i = 0; i < students.size(); i++) {
std::cout << students[i].name << ":" << students[i].score << std::endl;
}
}
// 算平均分并返回
double averageScore(const std::vector<Student>& students) {
int sum = 0;
for (int i = 0; i < students.size(); i++) {
sum += students[i].score;
}
return sum / (double)students.size();
}
int main() {
std::vector<Student> students;
int n;
std::cout << "有多少个学生?";
if (!(std::cin >> n) || n <= 0) {
std::cout << "人数必须是正整数。" << std::endl;
return 1;
}
inputStudents(students, n);
printAll(students);
std::cout << "平均分:" << averageScore(students) << std::endl;
return 0;
} inputStudents 要往容器里加东西,所以用
&(不加就白忙一场);printAll 和
averageScore 只读,所以用
const &——三个函数的参数写法差别,正好对应它们各自要干的事。
现在的 main 只有六七行,读一遍就知道整个程序在干什么。
想加新功能(比如找最高分),只要再写一个函数、在 main 里加一行调用。
写一个 int totalScore(const std::vector<Student>& students),
返回所有学生分数的总和,在 main 里调用并打印。
写一个 int countPassed(const std::vector<Student>& students),
返回分数大于等于 60 的学生人数。
提示:遍历 + 计数器,只是这次把它包进了函数里。
写一个 void addBonus(std::vector<Student>& students, int bonus),
给每个学生的分数都加上 bonus 分。
调用前后各打印一次全班成绩,确认真的改动了。
const?如果加了会怎样?
写一个 Student findTop(const std::vector<Student>& students),
用打擂台算法找出分数最高的学生,把整个 Student 返回,
main 里拿到后打印他的姓名和分数。
提示:函数的返回类型可以是 struct——
和返回 int、double 没有本质区别。
| 能力 | 用到的工具 |
|---|---|
| 把容器交给函数处理 | 参数写 std::vector<T> |
| 让函数真能改动容器 | 引用 std::vector<T>& |
| 只读时省掉拷贝又防手滑 | const std::vector<T>& |
| 让 main 保持简短清晰 | 一个函数只干一件事 |
vector、struct、引用。下节课做一次系统复习,
把这些串成一张完整的知识地图,为后面两节的综合项目做准备。