19、只读算法:find、count、accumulate、equal、mismatch、for_each
说实话,很多初学者一上来就急着写排序、插入、删除这些“有动作”的算法。但在我多年的项目经验里,真正用得最多的,反而是那些“只看不动”的只读算法。你想想看,大部分时候我们只是想查查数据在不在、数一数有多少、或者比对一下两个容器是否一致——这些活儿,STL 早就帮我们封装好了。
这一节,我就带你逐个过一遍这六个最常用的只读算法。它们都在 <algorithm> 和 <numeric> 头文件里,用起来非常顺手。
19.1 查找与计数:find 和 count
find 和 count 可以说是最基础的“搜索二人组”。find 帮你找到第一个匹配的元素,返回一个迭代器;count 则告诉你匹配的元素有多少个。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
// 查找第一个值为 5 的元素
auto it = std::find(vec.begin(), vec.end(), 5);
if (it != vec.end()) {
std::cout << "找到了第一个 5,位置在: "
<< std::distance(vec.begin(), it) << std::endl;
} else {
std::cout << "没找到 5" << std::endl;
}
// 统计值为 5 的元素有多少个
int cnt = std::count(vec.begin(), vec.end(), 5);
std::cout << "5 出现了 " << cnt << " 次" << std::endl;
return 0;
}
std::distance 把迭代器转成下标索引,调试时特别方便。不过要注意,std::distance 对随机访问迭代器是 O(1),对双向迭代器是 O(n)——但大多数情况下我们用的 vector 和 deque 都没问题。
19.2 累加求和:accumulate
accumulate 在 <numeric> 头文件里,它做的事情很简单:把一段范围内的元素累加起来。但它的妙处在于,你可以自定义“累加”的方式——不只是加法,乘法、字符串拼接,甚至更复杂的操作都行。
#include <iostream>
#include <vector>
#include <numeric> // 别忘了这个头文件
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 默认加法:1+2+3+4+5 = 15
int sum = std::accumulate(vec.begin(), vec.end(), 0);
std::cout << "总和: " << sum << std::endl;
// 自定义乘法:1*2*3*4*5 = 120
int product = std::accumulate(vec.begin(), vec.end(), 1,
std::multiplies<int>());
std::cout << "乘积: " << product << std::endl;
// 字符串拼接
std::vector<std::string> words = {"Hello", " ", "World", "!"};
std::string sentence = std::accumulate(words.begin(), words.end(),
std::string());
std::cout << "拼接结果: " << sentence << std::endl;
return 0;
}
std::accumulate(vec.begin(), vec.end(), 0) 返回 int,如果容器里存的是 double,小数部分会被截断。我建议写成 std::accumulate(vec.begin(), vec.end(), 0.0) 来避免精度丢失。
19.3 比较两个序列:equal 和 mismatch
这两个算法专门用来比较两个容器是否“一样”。equal 返回 bool,告诉你两个范围是否完全相同;mismatch 则更细致,它会返回第一对不相同的元素的位置。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> a = {1, 2, 3, 4, 5};
std::vector<int> b = {1, 2, 3, 4, 6};
std::vector<int> c = {1, 2, 3, 4, 5};
// equal 判断是否完全相等
bool eq1 = std::equal(a.begin(), a.end(), b.begin());
bool eq2 = std::equal(a.begin(), a.end(), c.begin());
std::cout << "a == b ? " << std::boolalpha << eq1 << std::endl;
std::cout << "a == c ? " << std::boolalpha << eq2 << std::endl;
// mismatch 找出第一个不同的位置
auto mis = std::mismatch(a.begin(), a.end(), b.begin());
if (mis.first != a.end()) {
std::cout << "第一个不同位置: "
<< std::distance(a.begin(), mis.first) << std::endl;
std::cout << "a 中是 " << *mis.first
<< ", b 中是 " << *mis.second << std::endl;
}
return 0;
}
equal 比较两个 vector<double>,结果明明数值一样却返回 false。后来才发现是浮点数精度问题。如果你要比较浮点数,最好自己写一个带容差的比较函数传进去。
19.4 遍历与操作:for_each
for_each 可以说是最灵活的只读算法——它允许你对每个元素执行任意操作。虽然 C++11 之后有了范围 for 循环,但 for_each 在某些场景下依然有它的优势,比如配合函数对象或 lambda 表达式时,代码更紧凑。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 用 for_each 打印每个元素
std::cout << "原始数据: ";
std::for_each(vec.begin(), vec.end(), [](int x) {
std::cout << x << " ";
});
std::cout << std::endl;
// 用 for_each 统计偶数的个数
int even_count = 0;
std::for_each(vec.begin(), vec.end(), [&even_count](int x) {
if (x % 2 == 0) ++even_count;
});
std::cout << "偶数个数: " << even_count << std::endl;
return 0;
}
transform 再 for_each),或者需要传递函数对象时,for_each 会更优雅。
19.5 知识体系总览
下面这张图帮你理清这六个算法的核心关系。说白了,它们就干三件事:查(find、count)、算(accumulate)、比(equal、mismatch)、遍历(for_each)。
19.6 综合示例:用只读算法解决实际问题
最后,我拿一个真实场景来串一下这些算法。假设你有一个学生成绩列表,需要完成几个任务:查找某个学生、统计及格人数、计算平均分、比对两次考试的成绩差异。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
struct Student {
std::string name;
int score;
};
int main() {
std::vector<Student> students = {
{"Alice", 85}, {"Bob", 72}, {"Charlie", 59},
{"David", 91}, {"Eve", 68}, {"Frank", 45}
};
// 1. 查找名为 "Charlie" 的学生
auto it = std::find_if(students.begin(), students.end(),
[](const Student& s) { return s.name == "Charlie"; });
if (it != students.end()) {
std::cout << "找到 " << it->name << ",成绩: " << it->score << std::endl;
}
// 2. 统计及格人数(score >= 60)
int pass_count = std::count_if(students.begin(), students.end(),
[](const Student& s) { return s.score >= 60; });
std::cout << "及格人数: " << pass_count << std::endl;
// 3. 计算平均分
int total = std::accumulate(students.begin(), students.end(), 0,
[](int sum, const Student& s) { return sum + s.score; });
double avg = static_cast<double>(total) / students.size();
std::cout << "平均分: " << avg << std::endl;
// 4. 用 for_each 打印所有不及格的学生
std::cout << "不及格学生: ";
std::for_each(students.begin(), students.end(), [](const Student& s) {
if (s.score < 60) std::cout << s.name << " ";
});
std::cout << std::endl;
return 0;
}
find/find_if:查找第一个匹配元素,返回迭代器count/count_if:统计匹配元素个数,返回整数accumulate:累加或自定义归约操作,在 <numeric> 中equal/mismatch:比较两个序列是否一致for_each:对每个元素执行操作,灵活但注意不要修改容器大小
好了,这六个只读算法就讲完了。说实话,它们看起来简单,但组合起来能解决大部分日常的查询和统计需求。我个人建议你多写几个小例子练练手,尤其是 accumulate 和 for_each 配合 lambda 的写法,用熟了之后代码会简洁很多。