23. 算法库:用算法代替手写循环

文章目录

  • 引言
  • [一、入门:`std::find` ------ 查找,不再写 `break`](#一、入门:std::find —— 查找,不再写 break)
    • [1.1 基础用法](#1.1 基础用法)
    • [1.2 `find_if`:按条件查找](#1.2 find_if:按条件查找)
    • [1.3 `find_if_not` / `find_first_of` / `adjacent_find` 等变体](#1.3 find_if_not / find_first_of / adjacent_find 等变体)
  • [二、`std::sort` 与 `std::stable_sort` ------ 排序,不再手写冒泡](#二、std::sortstd::stable_sort —— 排序,不再手写冒泡)
    • [2.1 基础用法](#2.1 基础用法)
    • [2.2 `std::stable_sort` vs `std::sort`](#2.2 std::stable_sort vs std::sort)
    • [2.3 `std::partial_sort` ------ 只排序前 N 个](#2.3 std::partial_sort —— 只排序前 N 个)
  • [三、`std::copy`、`std::copy_if`、`std::transform` ------ 搬数据,不再手写 for](#三、std::copystd::copy_ifstd::transform —— 搬数据,不再手写 for)
    • [3.1 `std::copy` ------ 复制到另一个位置](#3.1 std::copy —— 复制到另一个位置)
    • [3.2 `std::copy_if` ------ 只复制满足条件的](#3.2 std::copy_if —— 只复制满足条件的)
    • [3.3 `std::transform` ------ 把每个元素变成另一个值](#3.3 std::transform —— 把每个元素变成另一个值)
  • [四、`std::erase`(C++20)与 `std::remove` ------ 删除,不再写复杂移位](#四、std::erase(C++20)与 std::remove —— 删除,不再写复杂移位)
    • [4.1 经典的 "删除奇数的 for 循环" 问题](#4.1 经典的 "删除奇数的 for 循环" 问题)
    • [4.2 erase-remove 惯用法(C++11-C++17)](#4.2 erase-remove 惯用法(C++11-C++17))
    • [4.3 C++20:`std::erase_if` 一行搞定](#4.3 C++20:std::erase_if 一行搞定)
  • [五、`std::count`、`std::all_of`、`std::any_of`、`std::none_of` ------ 统计与判断](#五、std::countstd::all_ofstd::any_ofstd::none_of —— 统计与判断)
  • [六、`std::for_each` ------ 对每个元素做"有副作用"的事](#六、std::for_each —— 对每个元素做"有副作用"的事)
  • [七、`std::accumulate`、`std::reduce`、`std::inner_product` ------ 折叠与归约](#七、std::accumulatestd::reducestd::inner_product —— 折叠与归约)
  • 八、`std::min_element`、`std::max_element`、`std::minmax_element`
  • 九、`std::binary_search`、`std::lower_bound`、`std::upper_bound`、`std::equal_range`
  • [十、`std::unique` 与 `std::unique_copy` ------ 去重](#十、std::uniquestd::unique_copy —— 去重)
  • [十一、`std::merge`、`std::set_union`、`std::set_intersection` ------ 集合操作](#十一、std::mergestd::set_unionstd::set_intersection —— 集合操作)
  • [十二、`std::fill`、`std::generate`、`std::iota` ------ 填充序列](#十二、std::fillstd::generatestd::iota —— 填充序列)
  • [十三、算法 vs 手写循环:什么时候该用哪个](#十三、算法 vs 手写循环:什么时候该用哪个)
  • 十四、两个完整的重构示例
    • [14.1 从 C 循环到 STL 算法](#14.1 从 C 循环到 STL 算法)
    • [14.2 从手写循环到算法组合](#14.2 从手写循环到算法组合)
  • [十五、算法库速查:覆盖 90% 日常场景的列表](#十五、算法库速查:覆盖 90% 日常场景的列表)
  • 总结

本系列为《C++深度修炼:基础、STL源码与多线程实战》第23篇

前置条件:理解迭代器(第22篇),熟悉常用 STL 容器(第18-21篇),了解 lambda 表达式(第14篇)

引言

C 语言里,"处理一组数据"几乎只有一种方式------手写循环:

c 复制代码
// 在数组中找特定值
int *found = NULL;
for (int *p = arr; p != arr + n; ++p) {
    if (*p == target) { found = p; break; }
}

// 复制满足条件的元素
int dst[100], dst_count = 0;
for (int i = 0; i < n; ++i) {
    if (src[i] > 0) dst[dst_count++] = src[i];
}

// 把所有元素翻倍
for (int i = 0; i < n; ++i) {
    arr[i] *= 2;
}

这些循环的共同问题:意图被淹没在实现细节里。 for (int i = 0; i < n; ++i) 这行代码什么都没说------读者必须读完整个循环体,才能推断出这是在"查找"、"过滤"还是"变换"。

C++ 的 STL 算法库提供了 100+ 个命名明确的算法------findcopy_iftransform------每一个都直接表达了你的意图。用算法代替手写循环,不是少写几行代码的事,而是让你的代码从"怎么做的"变成"做了什么"。

本文不讲全部 100+ 个算法------只讲覆盖 90% 日常场景的核心十几组,每组配上 C 循环的对照写法。


一、入门:std::find ------ 查找,不再写 break

1.1 基础用法

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {5, 2, 8, 1, 9, 3};

    // 查找等于 8 的元素
    auto it = std::find(v.begin(), v.end(), 8);
    if (it != v.end()) {
        std::cout << "找到: " << *it << ",位置: "
                  << std::distance(v.begin(), it) << '\n';
    } else {
        std::cout << "未找到\n";
    }
}

对比 C 手写循环:

c 复制代码
// C 版本:你需要声明循环变量、写条件、写递增、写 break
int target = 8;
int *found = NULL;
for (int *p = arr; p != arr + n; ++p) {
    if (*p == target) {
        found = p;
        break;  // 容易漏写 continue/break
    }
}

std::find 的优势:

  1. 意图写在函数名里 ------find 就是查找,不用读循环体
  2. 不会出低级错误 ------不会忘了 break,不会写错边界条件
  3. 对所有容器通用 ------换成 listdeque,同一行代码仍然有效

1.2 find_if:按条件查找

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {5, 2, 8, 1, 9, 3};

    // 查找第一个大于 5 的元素
    auto it = std::find_if(v.begin(), v.end(),
                           [](int x) { return x > 5; });
    if (it != v.end()) {
        std::cout << "第一个 >5 的元素: " << *it << '\n';  // 8
    }

    // 查找第一个偶数
    auto it2 = std::find_if(v.begin(), v.end(),
                            [](int x) { return x % 2 == 0; });
    std::cout << "第一个偶数: " << *it2 << '\n';  // 2
}

find_if 的名字已经说了全部的事:"查找,如果(满足条件)"。------lambda 表达式填上"什么条件"就行。

1.3 find_if_not / find_first_of / adjacent_find 等变体

cpp 复制代码
// find_if_not:查找第一个不满足条件的
auto it1 = std::find_if_not(v.begin(), v.end(),
                            [](int x) { return x > 0; });  // 第一个非正数

// find_first_of:查找集合中任意元素首次出现的位置
std::vector<int> targets = {10, 20, 30};
auto it2 = std::find_first_of(v.begin(), v.end(),
                              targets.begin(), targets.end());

// adjacent_find:查找相邻重复的第一个位置
auto it3 = std::adjacent_find(v.begin(), v.end());

二、std::sortstd::stable_sort ------ 排序,不再手写冒泡

2.1 基础用法

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {5, 2, 8, 1, 9, 3};

    std::sort(v.begin(), v.end());  // 升序
    // v: {1, 2, 3, 5, 8, 9}

    // 降序
    std::sort(v.begin(), v.end(), std::greater<int>{});
    // v: {9, 8, 5, 3, 2, 1}

    // 自定义比较
    std::sort(v.begin(), v.end(),
              [](int a, int b) { return std::abs(a) < std::abs(b); });
}

O(n log n) 的复杂度 + 要求随机访问迭代器 (所以只适用于 vectordequearray------不能用于 list)。

list 有自己的 lst.sort() 成员函数。

2.2 std::stable_sort vs std::sort

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iostream>

struct Employee {
    std::string name;
    int salary;
};

int main() {
    std::vector<Employee> staff = {
        {"alice", 5000}, {"bob", 5000}, {"charlie", 8000}
    };

    // stable_sort:等值元素的相对顺序保持不变
    std::stable_sort(staff.begin(), staff.end(),
                     [](const auto &a, const auto &b) {
                         return a.salary < b.salary;
                     });
    // alice 仍在 bob 之前(她们工资相同,但 alice 原来在前)

    // sort:不保证等值元素的相对顺序
    std::sort(staff.begin(), staff.end(),
              [](const auto &a, const auto &b) {
                  return a.salary < b.salary;
              });
    // alice 和 bob 的先后顺序不确定------取决于实现
}

2.3 std::partial_sort ------ 只排序前 N 个

cpp 复制代码
std::vector<int> v = {5, 2, 8, 1, 9, 3, 7, 4};

// 只把最小的 3 个排到前面------其余的不管顺序
std::partial_sort(v.begin(), v.begin() + 3, v.end());
// v: {1, 2, 3, ?, ?, ?, ?, ?}  ← 前3个是最小的3个,后面元素顺序不保证

// 另一个实用工具:nth_element------把第 n 小的放到位置 n
std::nth_element(v.begin(), v.begin() + 4, v.end());
// v[4] 是第 5 小的元素(中位数),左边的都小于它,右边的都大于它

三、std::copystd::copy_ifstd::transform ------ 搬数据,不再手写 for

3.1 std::copy ------ 复制到另一个位置

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iterator>
#include <iostream>

int main() {
    std::vector<int> src = {1, 2, 3, 4, 5};
    std::vector<int> dst(src.size());

    std::copy(src.begin(), src.end(), dst.begin());
    // dst: {1, 2, 3, 4, 5}

    // 搭配 back_inserter------不用预先分配大小
    std::vector<int> dst2;
    std::copy(src.begin(), src.end(), std::back_inserter(dst2));

    // 也可以输出到标准输出
    std::copy(src.begin(), src.end(),
              std::ostream_iterator<int>(std::cout, " "));
    std::cout << '\n';
}

3.2 std::copy_if ------ 只复制满足条件的

cpp 复制代码
std::vector<int> src = {1, 2, 3, 4, 5, 6, 7, 8};
std::vector<int> evens;

std::copy_if(src.begin(), src.end(),
             std::back_inserter(evens),
             [](int x) { return x % 2 == 0; });
// evens: {2, 4, 6, 8}

对比 C 手写循环:

c 复制代码
// 必须自己管理 dst_count,自己写 if 判断,自己递增索引
int dst[100], dst_count = 0;
for (int i = 0; i < n; ++i) {
    if (src[i] % 2 == 0) {
        dst[dst_count++] = src[i];
    }
}

3.3 std::transform ------ 把每个元素变成另一个值

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iterator>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};

    // 每个元素翻倍
    std::vector<int> doubled;
    std::transform(v.begin(), v.end(),
                   std::back_inserter(doubled),
                   [](int x) { return x * 2; });
    // doubled: {2, 4, 6, 8, 10}

    // 两个输入序列------逐元素相加
    std::vector<int> a = {1, 2, 3, 4, 5};
    std::vector<int> b = {10, 20, 30, 40, 50};
    std::vector<int> sum;
    std::transform(a.begin(), a.end(), b.begin(),
                   std::back_inserter(sum),
                   [](int x, int y) { return x + y; });
    // sum: {11, 22, 33, 44, 55}
}

transform 命名的精准性:当你看到 transform(v.begin(), v.end(), dst, [](auto x) { return x * x; }),你立刻知道这是一个"映射/变换"操作------把每个元素 x 变成 x²。用 C 的 for 循环,你需要多读两秒才能理解这是在做什么。


四、std::erase(C++20)与 std::remove ------ 删除,不再写复杂移位

4.1 经典的 "删除奇数的 for 循环" 问题

C 语言中,从数组中删除特定元素是一个经典的容易出错的循环:

c 复制代码
// 错误的写法------删除奇数后,i 还是直接 ++ 了
for (int i = 0; i < n; ++i) {
    if (arr[i] % 2 != 0) {
        // 把后面的元素往前搬------然后 i++------跳过了被搬过来的元素!
        for (int j = i; j < n - 1; ++j) arr[j] = arr[j + 1];
        n--;
    }
}

4.2 erase-remove 惯用法(C++11-C++17)

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9};

    // std::remove 不真正删除元素------它把不删除的元素移到前面,返回一个新的"逻辑末尾"
    auto new_end = std::remove_if(v.begin(), v.end(),
                                  [](int x) { return x % 2 != 0; });

    // 然后用 erase 真正删除从 new_end 到 end 的元素
    v.erase(new_end, v.end());

    // v: {2, 4, 6, 8}
    for (auto x : v) std::cout << x << ' ';  // 2 4 6 8
}

std::remove 的工作原理:

text 复制代码
删除奇数前: [1, 2, 3, 4, 5, 6, 7, 8, 9]
            ↑保留?  ↑保留? ...

remove 后:  [2, 4, 6, 8, ?, ?, ?, ?, ?]
                              ↑ new_end(从这里到 end 是需要 erase 的"垃圾")

erase 后:   [2, 4, 6, 8]

4.3 C++20:std::erase_if 一行搞定

cpp 复制代码
// C++20起------直接擦除,不需要 remove + erase 两步
std::erase_if(v, [](int x) { return x % 2 != 0; });
// v: {2, 4, 6, 8}

五、std::countstd::all_ofstd::any_ofstd::none_of ------ 统计与判断

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9};

    // 统计
    auto n = std::count(v.begin(), v.end(), 3);     // 等于 3 的有几个?→ 1
    auto m = std::count_if(v.begin(), v.end(),       // 是偶数的有几个?→ 4
                           [](int x) { return x % 2 == 0; });

    // 全称/存在/不存在判断
    bool all = std::all_of(v.begin(), v.end(),       // 全 > 0?→ true
                           [](int x) { return x > 0; });
    bool any = std::any_of(v.begin(), v.end(),       // 有 > 10 的吗?→ false
                           [](int x) { return x > 10; });
    bool none = std::none_of(v.begin(), v.end(),     // 没有负数?→ true
                             [](int x) { return x < 0; });

    std::cout << "count(3)=" << n << '\n'
              << "count_if(even)=" << m << '\n'
              << std::boolalpha
              << "all_of(>0)=" << all << '\n'
              << "any_of(>10)=" << any << '\n'
              << "none_of(<0)=" << none << '\n';
}

对比 C 手写:

c 复制代码
// all_of 的手写版本:
int all_positive = 1;
for (int i = 0; i < n; ++i) {
    if (arr[i] <= 0) { all_positive = 0; break; }
}

all_of / any_of / none_of 是函数名即文档的最佳例子------不需要注释,不需要读循环体。


六、std::for_each ------ 对每个元素做"有副作用"的事

std::for_each 和范围 for 的区别:for_each 可以和其他算法链式组合(配合 C++20 ranges),而且明确表达了"这段代码有副作用"。

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};

    // 打印每个元素------有副作用
    std::for_each(v.begin(), v.end(),
                  [](int x) { std::cout << "值: " << x << '\n'; });

    // 修改每个元素------有副作用
    std::for_each(v.begin(), v.end(),
                  [](int &x) { x *= 2; });
    // v: {2, 4, 6, 8, 10}
}

指导原则:如果循环有副作用(打印、修改、发网络请求等),用 for_each 或范围 for。如果循环是纯变换(计算新值、过滤、统计),用无副作用的算法(transformcopy_ifcount)。


七、std::accumulatestd::reducestd::inner_product ------ 折叠与归约

cpp 复制代码
#include <numeric>      // accumulate, inner_product
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};

    // 求和(初始值 0)
    int sum = std::accumulate(v.begin(), v.end(), 0);
    // sum = 1 + 2 + 3 + 4 + 5 = 15

    // 求积(初始值 1)
    int product = std::accumulate(v.begin(), v.end(), 1,
                                  [](int acc, int x) { return acc * x; });
    // product = 1 * 1 * 2 * 3 * 4 * 5 = 120

    // 拼接字符串
    std::vector<std::string> words = {"hello", " ", "world"};
    std::string sentence = std::accumulate(words.begin(), words.end(),
                                           std::string{});
    // sentence = "hello world"

    // 内积:点乘
    std::vector<int> a = {1, 2, 3};
    std::vector<int> b = {4, 5, 6};
    int dot = std::inner_product(a.begin(), a.end(), b.begin(), 0);
    // dot = 1*4 + 2*5 + 3*6 = 32
}

accumulate 的名字不太好记(它来自函数式编程的 "accumulate"),但它做的事非常明确:把一群值通过一个二值操作"压缩"成一个值。


八、std::min_elementstd::max_elementstd::minmax_element

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {5, 2, 8, 1, 9, 3};

    // 最大值
    auto max_it = std::max_element(v.begin(), v.end());
    std::cout << "最大值: " << *max_it << " (位置 "
              << std::distance(v.begin(), max_it) << ")\n";

    // 最小值
    auto min_it = std::min_element(v.begin(), v.end());
    std::cout << "最小值: " << *min_it << " (位置 "
              << std::distance(v.begin(), min_it) << ")\n";

    // 同时取最大最小(一次遍历)
    auto [min_it2, max_it2] = std::minmax_element(v.begin(), v.end());
    std::cout << "最小值: " << *min_it2 << ", 最大值: " << *max_it2 << '\n';

    // 按自定义比较器
    struct Person { std::string name; int age; };
    std::vector<Person> people = {{"alice", 30}, {"bob", 25}, {"charlie", 35}};
    auto oldest = std::max_element(people.begin(), people.end(),
                                   [](const auto &a, const auto &b) {
                                       return a.age < b.age;
                                   });
    std::cout << "最年长: " << oldest->name << '\n';  // charlie
}

九、std::binary_searchstd::lower_boundstd::upper_boundstd::equal_range

这些算法要求序列已排序------它们利用二分查找实现 O(log n) 的查找速度:

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 3, 5, 5, 5, 7, 9};  // 已排序

    // binary_search:是否存在?
    bool exists = std::binary_search(v.begin(), v.end(), 5);
    std::cout << "5 存在? " << exists << '\n';  // true

    // lower_bound:第一个 >= 5 的位置
    auto lb = std::lower_bound(v.begin(), v.end(), 5);
    std::cout << "第一个 >=5 的位置: " << std::distance(v.begin(), lb) << '\n';  // 2

    // upper_bound:第一个 > 5 的位置
    auto ub = std::upper_bound(v.begin(), v.end(), 5);
    std::cout << "第一个 >5 的位置: " << std::distance(v.begin(), ub) << '\n';  // 5

    // equal_range:等于 5 的元素范围 [lb, ub)
    auto [lo, hi] = std::equal_range(v.begin(), v.end(), 5);
    std::cout << "等于 5 的元素个数: " << std::distance(lo, hi) << '\n';  // 3
}

lower_bound / upper_boundstd::map 中同名成员函数的自由函数版本------对有序序列通用。


十、std::uniquestd::unique_copy ------ 去重

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 1, 2, 3, 3, 3, 4, 5, 5};

    // unique:把连续重复的元素"压到"后面,返回新的逻辑末尾
    // 注意:需要先排序(unique 只去除**连续**重复)
    auto last = std::unique(v.begin(), v.end());
    v.erase(last, v.end());
    // v: {1, 2, 3, 4, 5}
}

如果不想修改原序列:

cpp 复制代码
std::vector<int> v = {1, 1, 2, 3, 3, 3, 4, 5, 5};
std::vector<int> deduped;
std::unique_copy(v.begin(), v.end(), std::back_inserter(deduped));

十一、std::mergestd::set_unionstd::set_intersection ------ 集合操作

cpp 复制代码
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> a = {1, 3, 5, 7};
    std::vector<int> b = {3, 5, 8, 10};    // 两个都必须已排序

    // 并集
    std::vector<int> union_result;
    std::set_union(a.begin(), a.end(),
                   b.begin(), b.end(),
                   std::back_inserter(union_result));
    // union_result: {1, 3, 5, 7, 8, 10}

    // 交集
    std::vector<int> intersect_result;
    std::set_intersection(a.begin(), a.end(),
                          b.begin(), b.end(),
                          std::back_inserter(intersect_result));
    // intersect_result: {3, 5}

    // 差集(在 a 中但不在 b 中)
    std::vector<int> diff_result;
    std::set_difference(a.begin(), a.end(),
                        b.begin(), b.end(),
                        std::back_inserter(diff_result));
    // diff_result: {1, 7}

    // 合并两个已排序序列
    std::vector<int> merged;
    std::merge(a.begin(), a.end(),
               b.begin(), b.end(),
               std::back_inserter(merged));
    // merged: {1, 3, 3, 5, 5, 7, 8, 10} ------ 保留所有重复
}

十二、std::fillstd::generatestd::iota ------ 填充序列

cpp 复制代码
#include <algorithm>
#include <numeric>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v(10);

    // fill:填充固定值
    std::fill(v.begin(), v.end(), 42);
    // v: {42, 42, 42, 42, 42, 42, 42, 42, 42, 42}

    // iota:填充递增序列
    std::iota(v.begin(), v.end(), 1);
    // v: {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // generate:填充由函数生成的值
    int counter = 0;
    std::generate(v.begin(), v.end(),
                  [&counter] { return counter++ * 10; });
    // v: {0, 10, 20, 30, 40, 50, 60, 70, 80, 90}
}

十三、算法 vs 手写循环:什么时候该用哪个

场景 推荐 理由
查找第一个满足条件的元素 find_if 函数名即意图
对每个元素做相同变换 transform 明确表达"映射"语义
过滤后复制 copy_if 不用手写 vector::push_back + if
排序 sort 比你手写的快且正确
统计/判断 count_if/all_of/any_of 一行搞定
有副作用的遍历 范围 for 或 for_each 无副作用的算法更好,但有副作用时范围 for 更简洁
需要索引的复杂计算 手写 for 算法中索引访问不自然
同时修改多个不相关容器 手写 for 算法不适合多输出
逻辑包含多个阶段的跳出条件 手写 for 算法中没有 break

一条简单的判断法则:如果你的循环体用一句话就能说清楚在做什么,STL 里大概率有一个和这句话同名的算法。


十四、两个完整的重构示例

14.1 从 C 循环到 STL 算法

原始 C 循环(求数组中所有正数的平方,加入新数组)

c 复制代码
int src[] = {-3, 5, -1, 8, -2, 4};
int dst[100], count = 0;
for (int i = 0; i < 6; ++i) {
    if (src[i] > 0) {
        dst[count++] = src[i] * src[i];
    }
}

C++ STL 版本

cpp 复制代码
std::vector<int> src = {-3, 5, -1, 8, -2, 4};
std::vector<int> dst;

std::transform(
    src.begin(), src.end(),
    std::back_inserter(dst),
    [](int x) { return x * x; }
);
// dst: {9, 25, 1, 64, 4, 16}   ------等一下,这个版本没过滤!

// 正确的做法------两步:
std::vector<int> positives;
std::copy_if(src.begin(), src.end(),
             std::back_inserter(positives),
             [](int x) { return x > 0; });
std::transform(positives.begin(), positives.end(),
               std::back_inserter(dst),
               [](int x) { return x * x; });
// dst: {25, 64, 16}

C++20 ranges 可以一步到位(链式调用):

cpp 复制代码
// C++20 ranges
auto result = src
    | std::views::filter([](int x) { return x > 0; })
    | std::views::transform([](int x) { return x * x; });

14.2 从手写循环到算法组合

cpp 复制代码
// 任务:从一个 vector<string> 中找出所有长度 > 3 的字符串,
//       转为大写,按字典序排序,取前 5 个

// ❌ 手写 for 版本------50+ 行,执行步骤分散在 if/for 之间
void process_manual(std::vector<std::string> &words) {
    std::vector<std::string> filtered;
    for (const auto &w : words) {
        if (w.size() > 3) {
            std::string upper = w;
            for (auto &c : upper) c = std::toupper(c);
            filtered.push_back(upper);
        }
    }
    std::sort(filtered.begin(), filtered.end());
    if (filtered.size() > 5) filtered.resize(5);
    // ...
}

// ✅ 算法版本------清晰的流水线
void process_algorithms(std::vector<std::string> &words) {
    std::vector<std::string> result;

    std::copy_if(words.begin(), words.end(),
                 std::back_inserter(result),
                 [](const auto &w) { return w.size() > 3; });

    std::for_each(result.begin(), result.end(),
                  [](auto &w) {
                      std::transform(w.begin(), w.end(), w.begin(),
                                     [](unsigned char c) { return std::toupper(c); });
                  });

    std::sort(result.begin(), result.end());
    if (result.size() > 5) result.resize(5);
    // ...
}

十五、算法库速查:覆盖 90% 日常场景的列表

类别 算法 用途
查找 find, find_if, find_if_not 找元素/找满足条件的
find_first_of 找集合中任意元素的首次出现
adjacent_find 找相邻重复
binary_search 二分查找(要求有序)
lower_bound, upper_bound, equal_range 二分查找位置(要求有序)
排序 sort, stable_sort 排序(要求随机访问)
partial_sort 部分排序
nth_element 找第 n 小
is_sorted 检查是否已排序
复制 copy, copy_if 复制/条件复制
copy_n, copy_backward 复制 N 个/反向复制
变换 transform 元素映射
replace, replace_if 替换特定值/满足条件的值
删除 remove, remove_if + erase 删除特定值(见第24篇警惕)
unique + erase 去重连续重复
统计 count, count_if 计数/条件计数
判断 all_of, any_of, none_of 全称/存在/不存在
归约 accumulate 求和/求积/字符串拼接/自定义归约
inner_product 内积
最值 min_element, max_element, minmax_element 最小值/最大值/两者
填充 fill, fill_n 填充固定值
generate, generate_n 生成值
iota 递增序列
集合 set_union, set_intersection, set_difference 集合并/交/差(要求有序)
merge 合并两个有序序列
其他 for_each 有副作用遍历
reverse, rotate, shuffle 反转/旋转/随机打乱
lexicographical_compare 字典序比较
next_permutation, prev_permutation 全排列

总结

STL 算法库不是 C++ 的"附加功能"------它是 STL 设计哲学的另一半。容器负责存储,算法负责操作,迭代器将二者连接:

  1. 用算法代替手写循环,代码从"怎么做的"变成"做了什么" ------find / transform / copy_iffor (int i = 0; i < n; ++i) 传达了更多意图
  2. STL 算法消除了手写循环中最常见的四类错误:数组越界、迭代器失效、忘记 break、循环变量混淆------这些在算法中物理上不可能
  3. lambda 表达式 + 算法是绝配------算法提供框架("做什么"),lambda 提供细节("具体条件是什么")
  4. 算法有迭代器类别要求 ------使用前检查文档:sort 需要随机访问,reverse 需要双向,find 只需要输入
  5. C++20 ranges 将算法推到了新的表达高度source | filter(f) | transform(g) 是数据流管道,从左到右读就是执行顺序

下一篇------容器的陷阱:迭代器失效与内存策略------我们将聚焦 STL 容器中那些最容易踩到的坑。容器本身很简单,但它们在边界条件下的行为,会让你的程序从"正常运行"变成"间歇性崩溃"。


📝 动手练习

  1. std::find_if + lambda 在一个 vector<Person> 中查找年龄大于 30 且名字以 "a" 开头的第一个人
  2. std::copy_if 过滤一个 vector<int> 中所有质数到新的 vector 中,然后用 std::for_each 打印
  3. std::transform 将两个 vector<double> 对应元素相加,结果存入第三个 vector(两输入序列版本)
  4. std::accumulate 配合自定义 lambda,实现一个 vector<int> 中所有奇数的平方和
  5. 找一个自己写过的"超过 5 行的 for 循环",用 STL 算法重写------比较代码行数和清晰度
相关推荐
Nile1 小时前
Claude Code-Dynamic Workflows:1.为什么用工作流?
人工智能·ai·ai编程·ai-native
狂炫冰美式1 小时前
AI 生成 Draw.io,导入飞书/Lark 画板后可编辑
前端·人工智能·后端
战族狼魂1 小时前
从零构建企业级Hermes-Agent:复杂任务拆解、工具协同与安全落地实践
开发语言·人工智能·python
o561-6o623o7鹿1 小时前
陈,生理实验系统虚实结合型 生理学实验系统 生理学实验系统软件
人工智能
继续商行1 小时前
Go 并发原语深度剖析:Channel 与 Mutex 的性能博弈
人工智能
yaoxiaoganggang1 小时前
克隆 Superpowers 的规则库到你的本地(或者直接作为 Git Submodule)
人工智能·经验分享·git·ai编程
小雨青年1 小时前
GitHub Spark:自然语言能把全栈 AI 应用做到什么程度
人工智能·github
AI袋鼠帝2 小时前
比Codex快4倍!终于有开源模型卷本地Agent执行效率了~
人工智能
j_xxx404_2 小时前
MySQL库操作硬核解析:字符集、校验规则、大小写比较、备份恢复与连接排查
运维·服务器·数据库·人工智能·mysql·ai·oracle