文章目录
- 引言
- [一、入门:`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::sort与std::stable_sort—— 排序,不再手写冒泡) -
- [2.1 基础用法](#2.1 基础用法)
- [2.2 `std::stable_sort` vs `std::sort`](#2.2
std::stable_sortvsstd::sort) - [2.3 `std::partial_sort` ------ 只排序前 N 个](#2.3
std::partial_sort—— 只排序前 N 个)
- [三、`std::copy`、`std::copy_if`、`std::transform` ------ 搬数据,不再手写 for](#三、
std::copy、std::copy_if、std::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—— 把每个元素变成另一个值)
- [3.1 `std::copy` ------ 复制到另一个位置](#3.1
- [四、`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::count、std::all_of、std::any_of、std::none_of—— 统计与判断) - [六、`std::for_each` ------ 对每个元素做"有副作用"的事](#六、
std::for_each—— 对每个元素做"有副作用"的事) - [七、`std::accumulate`、`std::reduce`、`std::inner_product` ------ 折叠与归约](#七、
std::accumulate、std::reduce、std::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::unique与std::unique_copy—— 去重) - [十一、`std::merge`、`std::set_union`、`std::set_intersection` ------ 集合操作](#十一、
std::merge、std::set_union、std::set_intersection—— 集合操作) - [十二、`std::fill`、`std::generate`、`std::iota` ------ 填充序列](#十二、
std::fill、std::generate、std::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+ 个命名明确的算法------find、copy_if、transform------每一个都直接表达了你的意图。用算法代替手写循环,不是少写几行代码的事,而是让你的代码从"怎么做的"变成"做了什么"。
本文不讲全部 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 的优势:
- 意图写在函数名里 ------
find就是查找,不用读循环体 - 不会出低级错误 ------不会忘了
break,不会写错边界条件 - 对所有容器通用 ------换成
list或deque,同一行代码仍然有效
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::sort 与 std::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) 的复杂度 + 要求随机访问迭代器 (所以只适用于 vector、deque、array------不能用于 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::copy、std::copy_if、std::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::count、std::all_of、std::any_of、std::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。如果循环是纯变换(计算新值、过滤、统计),用无副作用的算法(transform、copy_if、count)。
七、std::accumulate、std::reduce、std::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_element、std::max_element、std::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_search、std::lower_bound、std::upper_bound、std::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_bound 是 std::map 中同名成员函数的自由函数版本------对有序序列通用。
十、std::unique 与 std::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::merge、std::set_union、std::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::fill、std::generate、std::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 设计哲学的另一半。容器负责存储,算法负责操作,迭代器将二者连接:
- 用算法代替手写循环,代码从"怎么做的"变成"做了什么" ------
find/transform/copy_if比for (int i = 0; i < n; ++i)传达了更多意图 - STL 算法消除了手写循环中最常见的四类错误:数组越界、迭代器失效、忘记 break、循环变量混淆------这些在算法中物理上不可能
- lambda 表达式 + 算法是绝配------算法提供框架("做什么"),lambda 提供细节("具体条件是什么")
- 算法有迭代器类别要求 ------使用前检查文档:
sort需要随机访问,reverse需要双向,find只需要输入 - C++20 ranges 将算法推到了新的表达高度 :
source | filter(f) | transform(g)是数据流管道,从左到右读就是执行顺序
下一篇------容器的陷阱:迭代器失效与内存策略------我们将聚焦 STL 容器中那些最容易踩到的坑。容器本身很简单,但它们在边界条件下的行为,会让你的程序从"正常运行"变成"间歇性崩溃"。
📝 动手练习:
- 用
std::find_if+ lambda 在一个vector<Person>中查找年龄大于 30 且名字以 "a" 开头的第一个人- 用
std::copy_if过滤一个vector<int>中所有质数到新的vector中,然后用std::for_each打印- 用
std::transform将两个vector<double>对应元素相加,结果存入第三个vector(两输入序列版本)- 用
std::accumulate配合自定义 lambda,实现一个vector<int>中所有奇数的平方和- 找一个自己写过的"超过 5 行的 for 循环",用 STL 算法重写------比较代码行数和清晰度