19、只读算法:find、count、accumulate、equal、mismatch、for_each

说实话,很多初学者一上来就急着写排序、插入、删除这些“有动作”的算法。但在我多年的项目经验里,真正用得最多的,反而是那些“只看不动”的只读算法。你想想看,大部分时候我们只是想查查数据在不在、数一数有多少、或者比对一下两个容器是否一致——这些活儿,STL 早就帮我们封装好了。

这一节,我就带你逐个过一遍这六个最常用的只读算法。它们都在 <algorithm><numeric> 头文件里,用起来非常顺手。

19.1 查找与计数:find 和 count

findcount 可以说是最基础的“搜索二人组”。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;
}
我个人的习惯: 如果只是简单遍历,我会用范围 for 循环,可读性更好。但如果需要配合算法链(比如先 transformfor_each),或者需要传递函数对象时,for_each 会更优雅。

19.5 知识体系总览

下面这张图帮你理清这六个算法的核心关系。说白了,它们就干三件事:(find、count)、(accumulate)、(equal、mismatch)、遍历(for_each)。

只读算法知识体系 只读算法 查找 统计 比较 遍历 find find_if count count_if equal mismatch for_each 还有 accumulate(求和)属于数值算法,在 <numeric> 头文件中 所有算法都接受迭代器范围 [first, last),左闭右开

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:对每个元素执行操作,灵活但注意不要修改容器大小

好了,这六个只读算法就讲完了。说实话,它们看起来简单,但组合起来能解决大部分日常的查询和统计需求。我个人建议你多写几个小例子练练手,尤其是 accumulatefor_each 配合 lambda 的写法,用熟了之后代码会简洁很多。

公众号:蓝海资料掘金营,微信deep3321