泛型算法
掌握C++标准算法库——find/sort/count/accumulate/copy/transform等核心算法,理解迭代器如何将算法与容器解耦
学习目标
- 能实现用
find/count/accumulate/equal操作不同顺序容器——通过迭代器范围[begin, end)传递,并核对算法要求的迭代器能力 - 能比较并使用
sort/unique/stable_sort配合自定义谓词完成排序与去重——并独立写出 lambda 作为sort的第三个参数实现降序排列 - 能改出:把一段
for循环里逐个检查并删除特定元素的代码,改写为remove_if+ lambda +erase的惯用写法(erase-remove idiom)
机制总览
泛型算法:机制路径
- 1
直觉:为什么 100 种容器不能有 100 种算法?
你已经学了六种容器——每种都有自己的一套操作接口。如果要在 vector 里找一个值,你会写一个循环;要在 list 里找同样的值,再写一个循环——除了容器名字变了,代码几乎一模一样。难道每学一种新容器,就要把排序、查找、计数这些"工序"重写一遍?
- 2
算法不认识容器——只认迭代器
C++ 标准库提供大量 泛型算法(generic algorithm) ——它们被设计成与容器类型解耦。算法不是容器的成员函数,也不直接拥有容器——它们依靠 迭代器范围(iterator range) 与迭代器能力工作。并非每个算法适用于每个容器,例如 sort 需要随机访问, list 必须使用成员 sort 。
- 3
官方 Chapter 10 的完整算法契约
泛型并不意味着“任何算法都能接任何迭代器”。每个算法声明最低能力,调用者提供满足能力的范围、操作和输出位置;算法通常只重排或改写元素,不会替容器插入或删除元素。
章级决策实验
泛型算法:机制与证据
切换《泛型算法》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。
选择推理阶段
当前阶段 · 直觉:为什么 100 种容器不能有 100 种算法?
你已经学了六种容器——每种都有自己的一套操作接口。如果要在 vector 里找一个值,你会写一个循环;要在 list 里找同样的值,再写一个循环——除了容器名字变了,代码几乎一模一样。难道每学一种新容器,就要把排序、查找、计数这些"工序"重写一遍?
可核验证据
保留编译诊断,运行本节最小示例,并用边界断言、对象计数或 sanitizer 复核「直觉:为什么 100 种容器不能有 100 种算法?」的契约。
学完《泛型算法》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。
失效—证据矩阵
泛型算法:失效与核验
直觉:为什么 100 种容器不能有 100 种算法?
典型失效
若把「直觉:为什么 100 种容器不能有 100 种算法?」当作孤立语法点,忽略类型约束、对象生命周期或库契约,代码即使通过编译也可能破坏不变量。
核验证据
保留编译诊断,运行本节最小示例,并用边界断言、对象计数或 sanitizer 复核「直觉:为什么 100 种容器不能有 100 种算法?」的契约。
算法不认识容器——只认迭代器
典型失效
若把「算法不认识容器——只认迭代器」当作孤立语法点,忽略类型约束、对象生命周期或库契约,代码即使通过编译也可能破坏不变量。
核验证据
保留编译诊断,运行本节最小示例,并用边界断言、对象计数或 sanitizer 复核「算法不认识容器——只认迭代器」的契约。
官方 Chapter 10 的完整算法契约
典型失效
若把「官方 Chapter 10 的完整算法契约」当作孤立语法点,忽略类型约束、对象生命周期或库契约,代码即使通过编译也可能破坏不变量。
核验证据
保留编译诊断,运行本节最小示例,并用边界断言、对象计数或 sanitizer 复核「官方 Chapter 10 的完整算法契约」的契约。
直觉:为什么 100 种容器不能有 100 种算法?
你已经学了六种容器——每种都有自己的一套操作接口。如果要在 vector 里找一个值,你会写一个循环;要在 list 里找同样的值,再写一个循环——除了容器名字变了,代码几乎一模一样。难道每学一种新容器,就要把排序、查找、计数这些"工序"重写一遍?
想象工厂流水线有一套标准加工工序:分拣(find)、计数(count)、排序(sort)、填充(fill)——这些工序不关心物料是装在长条槽(vector)里、挂在链条上(list)、还是放在分段托盘(deque)里。它们只需要一根统一的输送带(迭代器)把物料一个接一个送到加工位。输送带只需回答三个问题:这是哪件物料?下一件在哪?送到哪一站结束?
这套"标准工序"就是 C++ 的 100 多个标准算法——你只要学会它们一次,就能作用在所有提供输送带的容器上。没有这套算法库会怎样? 你会一遍遍写循环——查找、排序、去重——或者更糟,自己手写一个又慢又容易出错的排序,而不用标准库里早已调优好的版本。这一章教你用标准算法代替手写循环,从此「这个操作该选哪个算法」你心里有数。
算法不认识容器——只认迭代器
C++ 标准库提供大量 ↡作用于序列元素的标准函数模板。通过左闭右开的迭代器范围接收数据,不依赖具体容器类型;不同算法会要求不同的迭代器能力。主要定义在 algorithm 与 numeric 头文件。——它们被设计成与容器类型解耦。算法不是容器的成员函数,也不直接拥有容器——它们依靠 ↡一对迭代器组成的左闭右开区间。first 指向首元素,last 指向尾元素之后的位置;空范围满足 first 等于 last。 与迭代器能力工作。并非每个算法适用于每个容器,例如 sort 需要随机访问,list 必须使用成员 sort。
上图展示了泛型算法的核心设计:三层解耦。算法层只认 begin() 和 end() 两个迭代器——至于这两个迭代器背后是 vector、list 还是 deque——算法毫不知情。这就像工厂的加工工序只认输送带上的物料——物料是从长条槽滑过来的、从链条挂架递来的、还是从分段托盘转接的——工序不关心。
算法在头文件中,不在容器里
#include <algorithm> // find, sort, count, copy, fill, replace, ...
#include <numeric> // accumulate, inner_product, partial_sum, ...大部分算法定义在 <algorithm> 中,数值相关的算法(如 accumulate)在 <numeric> 中。注意——算法是独立函数,不是容器方法——你不需要 v.sort(),而是 std::sort(v.begin(), v.end())。
第一个例子:用 find 代替手写循环
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> v = {3, 7, 1, 9, 2, 7, 4, 8};
// 手写循环——逻辑可泛化,但这里仍要自行维护边界与步进
auto it_manual = v.begin();
while (it_manual != v.end() && *it_manual != 7)
++it_manual;
// 泛型算法——1 行,对任何容器都有效
auto it_algo = std::find(v.begin(), v.end(), 7);
// 两者结果相同
std::cout << (it_manual == it_algo) << '\n'; // 1(true)
}find 接受三个参数:前两个定义搜索范围 [begin, end),第三个是要找的值。它从 begin 开始逐个比对,找到就返回指向该元素的迭代器,找不到就返回 end。
官方 Chapter 10 的完整算法契约
泛型并不意味着“任何算法都能接任何迭代器”。每个算法声明最低能力,调用者提供满足能力的范围、操作和输出位置;算法通常只重排或改写元素,不会替容器插入或删除元素。
迭代器类别与算法参数模式
↡按可读写、单遍或多遍、前进或后退、随机跳转等能力划分的迭代器层级。算法只要求完成任务所需的最低类别。 从能力上分为五类:输入迭代器可读且通常只保证单遍;输出迭代器可写且通常只保证单遍;前向迭代器可多遍向前;双向迭代器还能递减;随机访问迭代器支持常数时间跳转、距离与下标。forward_list 只有前向能力,list 是双向能力,vector、deque、array 与 string 提供随机访问。
常见算法参数模式可以据此阅读:
alg(first, last, otherArgs):一个输入范围,结果通过返回值或原范围表达。alg(first, last, dest, otherArgs):一个输入范围加一个输出起点;dest必须有效或是插入迭代器。alg(first1, last1, first2, otherArgs):第二个范围只给起点,调用者必须保证它足够长。alg(first1, last1, first2, last2, otherArgs):两个范围边界都明确;某些重载在较新标准才提供,使用时要核对项目语言版本。
算法把违反能力要求变成编译错误:std::sort(list.begin(), list.end()) 失败,因为双向迭代器没有随机跳转;这不是容器名字被特殊禁止,而是能力契约不满足。
插入、流与反向迭代器
插入迭代器有三种:back_inserter 调用 push_back,front_inserter 调用 push_front,inserter(c, pos) 在指定位置反复调用 insert 并维护插入位置。输出顺序很重要:front_inserter 连续写入会反转元素顺序。
std::list<int> values;
std::copy(src.begin(), src.end(), std::back_inserter(values));
std::vector<int> middle = {1, 4};
std::copy(src.begin(), src.end(), std::inserter(middle, middle.begin() + 1));istream_iterator<T> 把格式化输入适配为输入迭代器,ostream_iterator<T> 把赋值适配为输出;反向迭代器让 rbegin() 到 rend() 以相反方向遍历,但 reverse_iterator::base() 指向的是反向迭代器所指元素的后一个正向位置:
std::istream_iterator<int> in(std::cin), eof;
std::vector<int> numbers(in, eof);
std::copy(numbers.rbegin(), numbers.rend(),
std::ostream_iterator<int>(std::cout, " "));lambda、bind 与参数重排
lambda 最适合短小的局部操作;已有函数的参数顺序不符合算法谓词时,std::bind 可以绑定固定实参并重排占位参数:
#include <functional>
using std::placeholders::_1;
bool at_least(const std::string &word, std::size_t size) {
return word.size() >= size;
}
auto firstLong = std::find_if(words.begin(), words.end(),
std::bind(at_least, _1, 6));bind 默认按值保存绑定实参;要绑定不可拷贝对象或要求被调函数接收普通引用时,使用 std::ref/std::cref 明确包装。现代代码中 lambda 往往更易读,但理解 bind 有助于读懂 C++11 代码和参数重排。
容器特定算法
↡利用容器内部结构提供的成员算法。list 与 forward_list 的 sort、merge、remove、reverse 和 unique 能直接重连节点,而非覆盖元素值。 对链表尤其重要。泛型 remove 只把保留元素移到前面并返回新末尾,不能缩短容器;list::remove 会真正删除节点。list::sort、merge、splice 通过重连节点工作,不要求随机访问,也不复制节点中的值。
std::list<int> a = {3, 1, 2, 2};
std::list<int> b = {6, 4, 5};
a.sort();
a.unique();
b.sort();
a.merge(b); // a: 1 2 3 4 5 6,b 为空forward_list 提供相应的单链表成员操作以及 splice_after。使用成员版本还是泛型版本,不只是语法选择:前者能改变节点结构和容器大小,后者只能通过迭代器读写现有元素。
Stepper:算法怎么一步步执行?
猜一猜:把下面 Stepper 从第一步逐一切换到最后一步,你看到 find 的执行过程——它每一轮检查哪个元素?找到目标值后立即停止还是继续扫描?
初始数组
vector<int> v = {3, 7, 1, 9, 2, 7, 4, 8};——目标值 7。算法准备从 begin() 开始逐个扫描。
只读算法:只看不改
↡遍历序列的每个元素,读取它们但不修改原有数据的算法。典型如 find(查找)、count(计数)、accumulate(累加)、equal(比较)。接受一对迭代器定义范围,必要时额外参数指定操作。 不会修改容器中的元素——它们只是"浏览"一遍。
find——找到第一个匹配元素
#include <vector>
#include <algorithm>
#include <string>
int main() {
std::vector<std::string> words = {"apple", "banana", "cherry", "date"};
auto it = std::find(words.begin(), words.end(), "cherry");
if (it != words.end())
std::cout << "找到: " << *it << '\n'; // "找到: cherry"
else
std::cout << "未找到\n";
}find 返回一个迭代器——指向第一个匹配元素。未找到则返回 end() 迭代器(尾后位置,不可解引用)。常见错误:不检查 != end() 就直接解引用——如果是 end(),那是未定义行为。
find_if 是其变体——接受一个谓词(返回 bool 的函数)而不是具体值:
// 找到第一个长度 > 5 的单词
auto it = std::find_if(words.begin(), words.end(),
[](const std::string &s) { return s.size() > 5; });
// it 指向 "banana"count——统计出现次数
std::vector<int> v = {1, 2, 3, 2, 1, 2, 4};
int n = std::count(v.begin(), v.end(), 2); // n = 3count 遍历整个范围,统计等于目标值的元素个数。count_if 统计满足谓词的元素数。accumulate 是累加版本——它定义在 <numeric> 而非 <algorithm>:
#include <numeric>
std::vector<int> v = {1, 2, 3, 4, 5};
int sum = std::accumulate(v.begin(), v.end(), 0); // sum = 15
int product = std::accumulate(v.begin(), v.end(), 1,
[](int a, int b) { return a * b; }); // product = 120accumulate 的第三个参数是初始值——它是累加的"起点",同时也决定了返回值的类型。
equal——比较两个序列是否相等
std::vector<int> a = {1, 2, 3, 4, 5};
std::vector<int> b = {1, 2, 3, 4, 5};
bool same = std::equal(a.begin(), a.end(), b.begin());
// same = trueequal 假设第二个序列至少和第一个一样长——它只接受第二个序列的起始迭代器,不会检查长度是否足够。如果第二个序列比第一个短——未定义行为。
写算法:会改容器内容
↡遍历序列的过程中会修改元素值的算法。包括 fill(填充)、copy(复制)、replace(替换)、transform(变换)等。其中 copy/transform 涉及目标位置——需要输出迭代器。与只读算法不同,写算法要求目标范围必须有足够的空间。 会改变元素的值——因此你需要确保目标有足够的"位置"来接收结果。
fill——从头到尾写入同一值
fill(begin, end, val) 把区间内的每个元素都设成 val。fill_n(dest, n, val) 从 dest 开始写入 n 个 val:
std::vector<int> v = {1, 2, 3, 4, 5};
std::fill(v.begin(), v.begin() + 3, 0); // v: {0, 0, 0, 4, 5}
std::vector<int> v2;
std::fill_n(std::back_inserter(v2), 5, 42); // v2: {42, 42, 42, 42, 42}第二段使用了 ↡把写入操作转换为容器插入操作的迭代器适配器。back_inserter 调用 push_back,front_inserter 调用 push_front,inserter 在指定位置调用 insert。定义在 iterator 头文件。——它把“往迭代器写入”转换为容器插入,解决目标范围尚未创建元素的问题。
copy——整段搬运
copy(src_begin, src_end, dst) 把源区间的元素一个个复制到目标:
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(5); // 必须预分配足够空间!
std::copy(src.begin(), src.end(), dst.begin()); // dst: {1, 2, 3, 4, 5}最常见的错误:dst 没有预分配空间——copy 只是写入到已有位置,不会自动扩容。如果你不确定目标有多大,用 back_inserter:
std::vector<int> dst; // 空!
std::copy(src.begin(), src.end(), std::back_inserter(dst)); // 正确:自动 push_backreplace——条件替换
std::vector<int> v = {1, 2, 3, 2, 4, 2, 5};
std::replace(v.begin(), v.end(), 2, 99); // 所有 2 → 99
// v: {1, 99, 3, 99, 4, 99, 5}replace_copy 是"复制并替换"的版本——源不变,改版写入目标:
std::vector<int> src = {1, 2, 3, 2, 4};
std::vector<int> dst;
std::replace_copy(src.begin(), src.end(),
std::back_inserter(dst), 2, 99);
// src: {1, 2, 3, 2, 4} 不变
// dst: {1, 99, 3, 99, 4}transform——逐元素变形
transform 是对每个元素调用函数对象、把结果写入目标的通用武器:
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(src.size()); // 预分配
// 每个元素 ×2
std::transform(src.begin(), src.end(), dst.begin(),
[](int x) { return x * 2; });
// dst: {2, 4, 6, 8, 10}
// 原地平方——src 和 dst 可以是同一个容器
std::transform(src.begin(), src.end(), src.begin(),
[](int x) { return x * x; });
// src: {1, 4, 9, 16, 25}transform 接受三个必需参数:源范围 [begin, end) + 目标起始位置 + 一个可调用对象(函数、函数指针、函数对象、lambda)。transform 的强大在于——你自己决定"怎么变",它负责把"变"施加到每个元素。
排序定制:sort、unique、stable_sort
sort——随机访问范围排序
sort(begin, end) 对区间进行升序排序——默认用 < 比较。要求迭代器是 ↡支持常数时间跳转、距离、比较和下标访问的迭代器。vector、deque、array 与 string 提供随机访问;list 只有双向能力,forward_list 只有前向能力。——这意味着 vector/deque/array/string 可用,list 不可用(要用 list::sort)。标准规定复杂度和排序结果,不规定必须使用快排或 introsort;具体实现可自由选择满足要求的算法。
std::vector<int> v = {5, 2, 8, 1, 9, 3, 6, 4, 7};
std::sort(v.begin(), v.end());
// v: {1, 2, 3, 4, 5, 6, 7, 8, 9}下面 Stepper 用快速排序的一次可能执行路径解释“比较、分区、递归”如何产生有序结果。它是教学模型,不是对某个标准库 std::sort 内部步骤或 pivot 选择的承诺:
原始数组
开始排序前——{5, 2, 8, 1, 9, 3, 6, 4, 7},完全是随机顺序。
自定义排序规则
sort 默认按升序排。要降序怎么办?传入第三个参数——一个比较函数:
std::vector<int> v = {5, 2, 8, 1, 9};
// 降序排序
std::sort(v.begin(), v.end(),
[](int a, int b) { return a > b; });
// v: {9, 8, 5, 2, 1}这个比较函数被称为 ↡返回 bool 值的可调用对象——用于判断谁应该排前面。sort 的第三个参数就是谓词:返回 true 表示第一个参数应该排在第二个参数前面。lambda 是最常用的谓词形式。——sort 在比较两个元素时会调用它。返回 true 表示 a 应该排在 b 前面。
更实际的例子——对结构体按某个字段排序:
struct Student {
std::string name;
int score;
};
std::vector<Student> students = {
{"Alice", 92}, {"Bob", 85}, {"Charlie", 97}
};
// 按成绩降序排
std::sort(students.begin(), students.end(),
[](const Student &a, const Student &b) {
return a.score > b.score;
});
// Charlie(97) > Alice(92) > Bob(85)unique——去重
unique 把重复的相邻元素"挤"到末尾——注意它只处理相邻重复,不会真正删除元素:
std::vector<int> v = {1, 2, 2, 3, 3, 3, 4, 5, 5};
auto new_end = std::unique(v.begin(), v.end());
// [v.begin(), new_end) 是 {1, 2, 3, 4, 5}
// [new_end, v.end()) 中的元素仍有效,但值未指定
v.erase(new_end, v.end()); // 删除新逻辑末尾之后的元素
// v: {1, 2, 3, 4, 5}↡对比较结果等价的元素保持原始相对顺序的排序。stable_sort 提供稳定性,sort 不保证;两者的复杂度与额外存储条件也可能不同。 的关键语义是等价元素保持原序,不能把它和 sort 的全部差异简化为函数名不同:
// 假设有多个 Student score=90——stable_sort 保持他们的原始顺序
std::stable_sort(students.begin(), students.end(),
[](const Student &a, const Student &b) {
return a.score > b.score;
});lambda 表达式:就地生成一个函数
你已经见过几次了——[](int x) { return x > 0; } 这种写在代码中间的"函数"。这是 ↡一种就地定义匿名函数对象的语法,由捕获列表、参数列表、可选返回类型和函数体组成。捕获决定闭包如何保存外部变量,C++11 引入。——C++11 引入的语法糖,让你在需要函数对象的地方当场写一个"匿名函数"。
lambda 的四个组成部分:
- ① 捕获列表
[capture]:告诉 lambda 它如何访问外部变量。[=]按值捕获(复制一份)、[&]按引用捕获(共享原变量)、[x, &y]混合——x 按值、y 按引用 - ② 参数列表
(params):和普通函数一样的参数——调用者传进来的东西 - ③ 返回类型
-> ret:大多数情况可省略——编译器能从函数体推导 - ④ 函数体
{ body }:lambda 的执行逻辑
int threshold = 10;
std::vector<int> v = {5, 15, 8, 20, 3};
// [=] 按值捕获 threshold——lambda 内部可以读取它
auto it = std::find_if(v.begin(), v.end(),
[=](int x) { return x > threshold; }); // *it = 15
// [&] 按引用捕获——lambda 内部可以修改外部变量
int count = 0;
std::for_each(v.begin(), v.end(),
[&](int x) { if (x > threshold) ++count; });
// count = 2容易踩的坑
小结
- 泛型算法与容器解耦——通过迭代器范围
[begin, end)接收数据;只要迭代器能力满足算法要求,同一算法就能复用。find/count等只读算法不改数据,fill/copy/transform等写算法需要有效目标 sort对 RandomAccess 迭代器区间 O(n log n) 排序——接受可选的第三个参数(谓词)自定义排序规则;unique只压缩重复不删除——配合erase完成真正的去重操作- lambda 是就地定义的匿名函数——
[capture](params) -> ret { body };[=]按值捕获外部变量(副本)、[&]按引用捕获(共享);lambda 作为谓词传入算法——大幅减少独立函数定义 - 写算法不自动扩容——
copy/transform写入时目标容器空间必须足够,否则用back_inserter自动 push_back;fill_n同理——n 个位置必须有效 - 手写循环能被标准算法替换的场景——查找用
find/find_if、计数用count/count_if、删除元素用remove_if+erase
练习
问题 1(改代码型) 下面代码试图从一个 vector 中删除所有负数——但不只一个 bug。找出所有问题并写出修正后的代码。
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {3, -1, 2, -3, 5, -2};
for (auto it = v.begin(); it != v.end(); ++it) {
if (*it < 0) {
v.erase(it); // Bug 1
} // Bug 2
}
for (int x : v) std::cout << x << ' '; // 应该输出 3 2 5
}问题 2(独立实现题) 写一个函数 top_n,输入一个 vector<Student> 和整数 n,返回成绩(score)最高的 n 个学生——按成绩从高到低排列。用 sort + lambda 完成排序。
struct Student {
std::string name;
int score;
};
std::vector<Student> top_n(std::vector<Student> students, int n);问题 3(选型题) 对于以下三个场景,应该用哪个标准算法?各用一句话说明原因。
- A:有一个
vector<string>,需要把所有长度超过 10 的单词替换为"[LONG]"。 - B:检查一个
vector<int>是否所有元素都为正数——返回bool。 - C:把一个
vector<int>的每个元素加 10——结果存到另一个list<int>中。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 泛型算法(generic algorithm)
作用于序列元素上的标准函数模板。通过迭代器范围 [begin, end) 接收数据,不依赖具体容器类型——这是"泛型"的含义:同一份算法代码适配所有容器。包括查找(find)、排序(sort)、计数(count)、累加(accumulate)、复制(copy)、变换(transform)等 100 多个。定义在 algorithm 和 numeric 头文件中。详见本章「算法不认识容器」一节。
- 迭代器范围(iterator range)
一对迭代器组成的左闭右开区间 [begin, end)。begin 指向第一个元素,end 指向最后一个元素之后的位置(尾后迭代器,不可解引用)。所有标准算法都通过迭代器范围来接收数据——这就是算法与容器解耦的关键。详见本章 AlgorithmArchitectureDiagram。
- 只读算法(read-only algorithm)
遍历序列的每个元素,读取它们但不修改原有数据的算法。典型包括 find(查找特定值)、count(统计出现次数)、accumulate(累加求和)、equal(比较两序列是否相等)。只接受输入迭代器,不要求目标空间。详见本章「只读算法」一节的代码示例。
- 写算法(write algorithm)
遍历序列的过程中会修改元素值的算法。包括 fill(填充同一值)、copy(复制整段)、replace(条件替换)、transform(逐元素变换)。其中 copy/transform 涉及目标位置——需要目标区间有足够空间,否则用 back_inserter 自动扩容。详见本章「写算法」一节和 AlgorithmExecutionDiagram。
- 插入迭代器(insert iterator)
把写入转换为容器插入的输出迭代器适配器。back_inserter 调用 push_back,front_inserter 调用 push_front,inserter 在指定位置调用 insert。定义在 iterator 头文件。详见「插入、流与反向迭代器」。
- lambda 表达式(lambda expression)
一种就地定义匿名函数的语法——写在需要函数对象的地方,无需单独命名。形式为
[capture](params) -> ret { body }。capture 指定如何访问外部变量([=] 按值复制一份、[&] 按引用共享原变量);params 是调用者传入的参数;-> ret 是返回值类型(通常可省略、编译器自动推导);{ body }是函数体。编译器将 lambda 展开为一个匿名函数对象类(闭包)。C++11 引入。详见本章 LambdaSyntaxDiagram。- 谓词(predicate)
返回 bool 值的可调用对象(函数、函数指针、函数对象或 lambda)。一元谓词接受一个参数,二元谓词接受两个参数(如 sort 的比较函数)。sort 通过二元谓词决定两个元素的相对顺序,find_if/count_if 通过一元谓词筛选元素。详见本章「自定义排序规则」和「lambda 表达式」节。
- 稳定排序(stable_sort)
排序时对值相等的元素保持它们原始的前后顺序。std::sort 不保证稳定性——相等的元素顺序可能被交换。std::stable_sort 保证相等元素不改变相对位置——当排序涉及多个字段时,稳定性很重要。详见本章「unique——去重」小节。
- 随机访问迭代器(RandomAccess Iterator)
支持常数时间跳转、距离、比较和下标访问的迭代器。vector/deque/array/string 提供这种能力;list 只有双向能力,forward_list 只有向前能力。std::sort 要求随机访问,因此 list 使用成员 sort。详见「sort」小节。
- 迭代器类别(iterator categories)
按可读写、单遍或多遍、前进或后退、随机跳转等能力划分的层级。输入与输出迭代器支持单遍读写,前向可多遍,双向可递减,随机访问可常数时间跳转。算法只要求最低必要类别。
- 容器特定算法(container-specific algorithms)
利用节点结构实现的成员算法。list 与 forward_list 的 sort、merge、remove、reverse、unique 和 splice 系列会重连或删除节点,与只改写现有元素的泛型算法语义不同。