操作数据,而非容器
一、概述
泛型算法(Generic Algorithm) 是 C++ Standard Template Library (STL) 的核心组成部分,其本质是与容器类型无关的通用操作逻辑 。通过模板和迭代器机制,泛型算法能够对任意满足迭代器接口的容器(如 vector
、list
、map
)进行操作,实现代码复用和高度灵活性。
核心特征:
- 容器无关性:不依赖具体容器类型,只要求容器提供迭代器。
- 类型安全:通过模板参数推导类型,编译期检查错误。
- 高性能 :针对不同容器底层数据结构优化(如
vector
的连续内存访问、list
的链表节点操作)。
STL 泛型算法可分为以下几类
类别 | 典型算法 | 功能描述 |
---|---|---|
非修改序列算法 | for_each , count |
遍历或统计元素,不修改容器 |
修改序列算法 | copy , transform |
生成新序列或就地修改元素 |
排序与划分算法 | sort , partition |
重排元素顺序 |
二分搜索算法 | lower_bound , binary_search |
在有序序列中高效查找 |
集合算法 | set_union , includes |
对有序集合进行交、并、差操作 |
堆操作算法 | make_heap , push_heap |
实现堆数据结构操作 |
数值算法 | accumulate , inner_product |
数学运算(求和、内积等) |
二、泛型算法的原理
1、泛型算法的核心原则
-
算法与容器解耦
泛型算法不直接操作容器 ,而是通过迭代器(Iterator) 间接访问容器元素。迭代器作为中间层,屏蔽了不同容器的底层实现差异(如
vector
的连续内存、list
的链表节点),使算法能统一处理所有支持迭代器的容器。 -
不改变容器结构
算法永远不会直接添加或删除容器元素 ,因此不会改变容器的大小 (size)或容量(capacity)。这一设计保证了算法的通用性和安全性。
std::vector<int> v = {3, 1, 4, 1, 5}; auto it = std::find(v.begin(), v.end(), 1); // 查找元素,但不修改容器
2**、算法对元素的操作限制**
允许的操作 | 禁止的操作 |
---|---|
修改元素的值(如赋值) | 添加或删除元素 |
移动元素的位置 | 改变容器的容量或大小 |
-
允许:修改元素值
算法可以通过迭代器修改元素的值,例如
std::replace
替换指定值:std::replace(v.begin(), v.end(), 1, 9); // 将所有1替换为9
-
允许:移动元素位置
算法可以重新排列元素顺序,例如
std::sort
排序:std::sort(v.begin(), v.end()); // 元素按升序排列
-
禁止:改变容器大小
算法无法直接调用容器的
insert()
或erase()
方法。之前有举过std::remove
仅标记要删除的元素,需结合erase()
完成实际删除的例子:auto new_end = std::remove(v.begin(), v.end(), 3); // 移动元素,不改变大小 v.erase(new_end, v.end()); // 真正删除元素
3**、插入器(Inserter)的桥梁作用(算法不会直接不改变容器结构,只会借助其他工具)**
-
插入器的功能
插入器是一种特殊迭代器 ,允许算法间接向容器添加元素。当对插入器赋值时,它会在底层容器上执行插入操作(如
push_back
、insert
)。 -
插入器类型
插入器类型 底层操作 示例 std::back_inserter
调用 push_back()
适用于 vector
、deque
、list
std::front_inserter
调用 push_front()
适用于 deque
、list
std::inserter
调用 insert()
指定位置通用型插入器 -
示例:使用插入器实现元素添加
cpp#include <vector> #include <iterator> #include <algorithm> int main() { std::vector<int> src = {1, 2, 3}; std::vector<int> dest; // 通过back_inserter间接添加元素 std::copy(src.begin(), src.end(), std::back_inserter(dest)); // dest变为{1, 2, 3} return 0; }
4**、泛型算法的工作流程**
-
输入阶段
算法接受一对迭代器(
begin
,end
)定义操作范围,以及可能的辅助参数(如比较函数、插入器等)。 -
执行阶段
通过迭代器遍历元素,按需修改值或移动位置,但不改变容器结构。
-
输出阶段
返回结果可能是一个迭代器(如
find
返回找到的位置)或通过插入器间接操作容器。
流程图 :
5**、为什么禁止算法直接操作容器?**
- 通用性 :避免算法依赖具体容器的接口(如
vector::push_back
和list::push_front
不同)。 - 安全性:防止误操作导致容器状态不一致(如迭代器失效)。
- 性能:允许编译器优化迭代器操作(如连续内存的指针算术)。
5**、泛型算法的核心特性**
特性 | 说明 |
---|---|
容器无关性 | 通过迭代器抽象,适配所有支持迭代器的容器 |
元素操作可控 | 可修改值或移动元素,但不改变容器大小 |
插入器的间接扩展 | 通过特殊迭代器间接添加元素,保持算法逻辑简洁 |
编译时类型安全 | 模板机制确保操作的类型匹配,避免运行时错误 |
三、基本泛型算法
理解算法的最基本的方法就是了解它们是否读取元素、改变元素或是重排元素顺序。
(1)总纲
1、泛型算法基本架构
-
输入范围机制
-
双迭代器界定 :所有算法通过
[begin, end)
半开区间操作元素cppsort(vec.begin(), vec.end()); // 对整个vector排序 find(lst.begin(), lst.end(), 42); // 在list中查找元素
-
容器无关性:算法不直接操作容器,仅通过迭代器间接访问
-
尾后迭代器 :
end()
指向最后一个元素的后一位,确保空范围安全
-
-
参数通用模式
cppalgorithm(first_iter, last_iter, [predicate/op]); // 示例:统计大于5的元素 count_if(v.begin(), v.end(), [](int x){return x > 5;});
2、算法行为分类
类型 | 特点 | 典型算法 |
---|---|---|
观察型 | 不修改元素,仅读取 | find , count , accumulate |
修改型 | 直接修改元素值 | replace , fill , copy |
重排型 | 改变元素顺序但不修改值 | sort , reverse , shuffle |
cpp
// 观察型:查找元素位置
auto pos = find(v.begin(), v.end(), target);
// 修改型:将所有负数替换为0
replace_if(v.begin(), v.end(), [](int x){return x < 0;}, 0);
// 重排型:按自定义规则排序
sort(v.begin(), v.end(), [](int a, int b){return a%10 < b%10;});
-
可组合性
// 组合排序与查找 sort(v.begin(), v.end()); auto it = lower_bound(v.begin(), v.end(), 42);
4、一些建议
-
掌握共性规律
- 所有算法都遵循
操作范围->处理元素->返回结果
的流程 - 70%算法可归入"读/改/排"三大类
- 所有算法都遵循
-
关注关键参数
- 前两个参数必为输入范围
- 可选参数包括:谓词(判断条件)、输出位置、自定义操作函数
-
实践建议
// 从简单算法入手理解模式 vector<int> data {5,3,7,2}; // 1. 使用find_if查找首个偶数 auto it = find_if(data.begin(), data.end(), [](int x){return x%2==0;}); // 2. 用transform转换元素 transform(data.begin(), data.end(), data.begin(), [](int x){return x*2;}); // 3. 结合sort和unique去重 sort(data.begin(), data.end()); auto last = unique(data.begin(), data.end()); data.erase(last, data.end());
(2)基本泛型算法
2.1观察型算法(只读)
核心定义
本质 :仅读取输入范围内的元素,不修改容器内容或元素顺序 的算法类型
设计目标:通过统一接口实现数据的安全观察与计算
核心特征
-
无副作用性
- 算法执行后保证原始数据完全不变
- 适用于需要保留原始数据场景(如统计分析、条件检测)
-
输入范围依赖
-
通过
[begin, end)
迭代器对操作数据范围 -
典型参数结构:
result_type algorithm(first_iter, last_iter, [predicate]);
-
-
谓词扩展性
-
支持通过谓词(Predicate)自定义读取逻辑
// 统计字符串长度大于5的元素 count_if(vec.begin(), vec.end(), [](const string& s){return s.size() >5;});
-
常见算法分类
算法类型 | 典型代表 | 返回值类型 |
---|---|---|
查找型 | find , search , adjacent_find |
迭代器(指向目标位置) |
计数型 | count , count_if |
整型数值(统计结果) |
比较型 | equal , mismatch |
bool或差异位置对 |
累积型 | accumulate , inner_product |
计算值(如求和/内积) |
遍历型 | for_each |
可选的函数对象返回值 |
关键使用模式
cpp
// 示例1:查找首个满足条件的元素
vector<int> data {7, 4, 9, 2};
auto it = find_if(data.begin(), data.end(),
[](int x){return x%3 ==0;}); // 返回指向9的迭代器
// 示例2:跨容器元素比较
list<string> names {"Alice", "Bob"};
vector<string> tmp {"Alice", "Charlie"};
bool match_front = equal(names.begin(), names.end(), tmp.begin()); // 返回false
// 示例3:数值计算
int sum = accumulate(data.begin(), data.end(), 0); // 7+4+9+2=22
设计原则体现
-
统一性
所有只读算法遵循相同的迭代器接口规范,例如:
cpp// 所有查找类算法返回迭代器 auto pos1 = find(...); // 值查找 auto pos2 = search(...); // 子序列查找
-
泛型扩展
通过模板支持任意元素类型:
cpp// 对自定义类型有效 struct Person { string name; int age; }; vector<Person> people; auto elder = find_if(people.begin(), people.end(), [](const Person& p){return p.age >60;});
-
安全保证
即使操作无效范围也不会导致数据破坏:
cppvector<int> empty_vec; // 安全操作:返回empty_vec.end() auto result = find(empty_vec.begin(), empty_vec.end(), 42);
应用场景建议
-
数据质量检查
cpp// 验证容器中是否存在非法值 bool has_negative = any_of(data.begin(), data.end(), [](int x){return x <0;});
-
元数据分析
cpp// 计算文本行平均长度 vector<string> lines = read_file(); int total = accumulate(lines.begin(), lines.end(), 0, [](int sum, const string& s){return sum + s.size();}); double avg = static_cast<double>(total)/lines.size();
-
预处理验证
cpp// 排序前检查是否已有序 if(!is_sorted(data.begin(), data.end())) { sort(data.begin(), data.end()); }
注意事项
-
迭代器有效性
确保输入的迭代器范围
[begin, end)
构成有效区间 -
谓词纯度
谓词函数应当是无副作用的纯函数(不修改外部状态)
-
性能选择
对已排序数据优先使用
binary_search
等优化算法:// 比线性查找更高效 bool exists = binary_search(sorted_data.begin(), sorted_data.end(), target);
2.2修改型算法 (算法不检查写操作)
核心概念
-
定义
通过迭代器修改容器元素值或结构的算法,属于修改型算法
- 直接修改 :在原容器上改变元素值(如
fill
) - 输出写入 :将结果写入另一个容器(如
transform
)
- 直接修改 :在原容器上改变元素值(如
-
统一接口特征
cpp// 典型参数结构 algorithm(input_begin, input_end, [output_iter], [operation]);
常见算法分类
算法类型 | 典型代表 | 修改方式 | 关键特性 |
---|---|---|---|
原位修改型 | fill , replace , generate |
直接修改输入范围内的元素 | 无需额外存储空间 |
拷贝输出型 | copy , transform |
将结果写入另一个容器 | 需要预分配输出空间 |
条件修改型 | replace_if , remove_if |
根据谓词选择性修改元素 | 常配合lambda使用 |
结构变更型 | remove , unique |
逻辑删除后需配合erase |
实际修改容器物理结构 |
详细解释
1. 填充与赋值
算法 | 功能 | 示例 |
---|---|---|
std::fill |
将区间填充为指定值 | fill(v.begin(), v.end(), 0) |
std::generate |
通过函数生成值填充区间 | generate(v.begin(), v.end(), rand) |
std::iota |
填充递增序列(C++11 起) | iota(v.begin(), v.end(), 1) |
2. 元素转换
算法 | 功能 | 示例 |
---|---|---|
std::transform |
对每个元素应用函数并写入目标位置 | transform(src.begin(), src.end(), dest.begin(), toupper) |
std::replace |
替换区间内的特定值 | replace(v.begin(), v.end(), 3, 10) |
std::replace_if |
按条件替换元素 | replace_if(v.begin(), v.end(), is_odd, 0) |
3. 删除与去重
算法 | 功能 | 示例 |
---|---|---|
std::remove |
移除指定值(需配合 erase ) |
v.erase(remove(v.begin(), v.end(), 42), v.end()) |
std::remove_if |
按条件移除元素 | v.erase(remove_if(v.begin(), v.end(), is_negative), v.end()) |
std::unique |
移除相邻重复元素(需先排序) | v.erase(unique(v.begin(), v.end()), v.end()) |
4. 重新排列
算法 | 功能 | 示例 |
---|---|---|
std::reverse |
反转区间元素顺序 | reverse(v.begin(), v.end()) |
std::rotate |
循环移动区间元素 | rotate(v.begin(), v.begin()+2, v.end()) |
std::shuffle |
随机打乱元素顺序(需随机引擎) | shuffle(v.begin(), v.end(), rand_gen) |
5. 复制与移动
算法 | 功能 | 示例 |
---|---|---|
std::copy |
复制源区间到目标位置 | copy(src.begin(), src.end(), dest.begin()) |
std::move |
移动元素到目标位置(C++11 起) | move(src.begin(), src.end(), dest.begin()) |
使用场景
-
初始化容器
使用
fill
、generate
或iota
填充初始值。 -
数据清洗
通过
remove
、remove_if
或unique
删除无效或冗余数据。 -
数据转换
使用
transform
或replace
修改元素格式或内容。 -
算法优化
通过
reverse
、rotate
或shuffle
调整数据顺序以满足特定需求。 -
数据迁移
使用
copy
或move
将数据转移到其他容器。
核心写入方式
1. 原位直接修改
cpp
vector<int> vec(5);
fill(vec.begin(), vec.end(), 10); // 所有元素变为10
replace(vec.begin(), vec.end(), 10, 20); // 10替换为20
2. 输出到其他容器
cpp
vector<int> src {1,2,3}, dest;
// 需要确保dest有足够空间(或使用插入迭代器)
copy(src.begin(), src.end(), back_inserter(dest));
// 转换+写入模式
transform(src.begin(), src.end(), back_inserter(dest),
[](int x){return x*2;}); // dest得到[2,4,6]
3. 条件性修改
cpp
vector<int> data {5,-3,7,-2};
replace_if(data.begin(), data.end(),
[](int x){return x <0;}, 0); // 负数替换为0
auto new_end = remove_if(data.begin(), data.end(),
[](int x){return x%2 ==0;}); // 逻辑删除偶数
data.erase(new_end, data.end()); // 物理删除
关键注意事项
-
空间预分配
cpp// 错误:未预分配空间 vector<int> dest; copy(src.begin(), src.end(), dest.begin()); // 崩溃! // 正确:使用插入迭代器 copy(src.begin(), src.end(), back_inserter(dest));
-
迭代器失效
cppvector<int> data {1,2,3,4}; auto it = data.begin(); fill(data.begin(), data.end(), 0); // 安全操作 data.push_back(5); // 使it可能失效
-
谓词副作用
cpp// 错误示例:带副作用的谓词 int counter = 0; replace_if(data.begin(), data.end(), [&](int x){return ++counter >2;}, 0); // 结果不可预测
典型应用模式
cpp
// 数据清洗管道
vector<int> process_data(vector<int>& raw) {
vector<int> result;
// 1. 去除非正数
remove_copy_if(raw.begin(), raw.end(), back_inserter(result),
[](int x){return x <=0;});
// 2. 数值规范化
transform(result.begin(), result.end(), result.begin(),
[](int x){return x*100;});
// 3. 去重处理
sort(result.begin(), result.end());
auto last = unique(result.begin(), result.end());
result.erase(last, result.end());
return result;
}
(3)重排型容器算法
1、重排型算法概述
重排型算法通过重新排列容器内元素的顺序实现特定目标,不改变元素值。这些算法定义在头文件 <algorithm>
中,部分需要 <random>
支持。核心特点如下:
- 不改变容器大小 (除非配合
erase
) - 依赖迭代器范围,支持不同容器
- 时间复杂度各异,需根据场景选择
类别 | 算法列表 | 用法说明 |
---|---|---|
排序(Sorting) | sort , stable_sort , partial_sort |
- sort(first, last) :对迭代器范围 [first, last) 内的元素进行排序。 - stable_sort(first, last) :稳定排序,相同元素相对顺序不变。 - partial_sort(first, middle, last) :部分排序,使 [first, middle) 有序,剩余元素无序。 |
反转(Reversing) | reverse |
reverse(first, last) :反转迭代器范围 [first, last) 内元素的顺序。 |
旋转(Rotating) | rotate , rotate_copy |
- rotate(first, middle, last) :将 [middle, last) 区间的元素移到 [first, last) 最前方。 - rotate_copy(first, middle, last, dest) :旋转复制,结果存入 dest 开始的位置。 |
随机重排(Shuffling) | shuffle |
shuffle(first, last, rng) :利用随机数生成器 rng (如 default_random_engine )对 [first, last) 内元素随机重排。 |
排列生成(Permutation) | next_permutation , prev_permutation |
- next_permutation(first, last) :求下一个字典序排列,成功返回 true ,否则 false 。 - prev_permutation(first, last) :求上一个字典序排列,成功返回 true ,否则 false 。 |
分区(Partitioning) | partition , stable_partition |
- partition(first, last, pred) :按谓词 pred 分区,不保证元素相对顺序。 - stable_partition(first, last, pred) :稳定分区,保留元素相对顺序。 |
2.关键算法详解
1. 排序算法
-
std::sort
-
功能:对范围内的元素进行升序排序(默认)或自定义排序。
-
实现原理:通常为IntroSort(快速排序 + 堆排序的混合)。
-
时间复杂度 :平均
O(n log n)
,最坏O(n²)
。 -
示例:
cpp#include <vector> #include <algorithm> std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6}; std::sort(vec.begin(), vec.end()); // 默认升序 std::sort(vec.begin(), vec.end(), std::greater<int>()); // 降序
-
-
std::stable_sort
-
示例 :
cppstruct Item { int id; int value; }; std::vector<Item> items = {{1,3}, {2,3}, {3,1}}; std::stable_sort(items.begin(), items.end(), [](const Item& a, const Item& b) { return a.value < b.value; // 按 value 排序,相同值保持插入顺序 }); // 结果顺序:{3,1}, {1,3}, {2,3}
-
注意:时间复杂度 O(n log n),内存占用较高。
-
特点:保持相等元素的原始相对顺序。
-
适用场景:需要稳定排序时(如按多字段排序)。
std::partial_sort
-
用途:部分排序(仅排序前 N 个元素)
-
示例 :
cppstd::vector<int> v = {9, 5, 2, 7, 4}; // 仅排序前3个元素,其余不保证顺序 std::partial_sort(v.begin(), v.begin() + 3, v.end()); // 结果:2,4,5,9,7(前3位有序,后两位无序)
-
注意:时间复杂度 O(n log k)(k 为排序部分大小)。
2. 反转算法
-
std::reverse
-
功能:反转容器中元素的顺序。
-
实现原理:交换首尾对称位置的元素。
-
时间复杂度 :
O(n)
。 -
示例:
cppstd::vector<int> vec = {1, 2, 3, 4, 5}; std::reverse(vec.begin(), vec.end()); // 结果为 {5, 4, 3, 2, 1}
-
3. 旋转算法
-
std::rotate
-
功能 :将
[first, middle, last)
范围内的元素左旋,使middle
成为新的第一个元素。 -
时间复杂度 :
O(n)
。 -
示例:
cppstd::vector<int> vec = {1, 2, 3, 4, 5}; auto mid = vec.begin() + 2; // 指向元素3 std::rotate(vec.begin(), mid, vec.end()); // 结果 {3, 4, 5, 1, 2}
-
std::rotate_copy
-
用途:旋转并拷贝到新容器,不修改原数据
-
示例 :
cppstd::vector<int> src = {1,2,3,4,5}; std::vector<int> dest(src.size()); std::rotate_copy(src.begin(), src.begin()+2, src.end(), dest.begin()); // dest: 3,4,5,1,2(原容器不变)
4. 随机重排算法
-
std::shuffle
-
功能:随机打乱元素顺序。
-
依赖 :需提供随机数生成器(如
std::mt19937
)。 -
示例:
cpp#include <random> std::vector<int> vec = {1, 2, 3, 4, 5}; std::random_device rd; std::mt19937 rng(rd()); std::shuffle(vec.begin(), vec.end(), rng);
-
- 注意 :使用高质量随机数生成器(如
mt19937
)。
5. 排列生成算法
-
std::next_permutation
-
功能:按字典序生成下一个更大的排列。
-
返回值 :
bool
(若存在更小排列则返回false
)。 -
示例:
cpp#include <algorithm> #include <vector> #include <iostream> int main() { std::vector<int> v = {1,2,3}; do { // 依次输出:123, 132, 213, 231, 312, 321 for (auto i : v) { std::cout << i; } std:: cout<<std::endl; } while (std::next_permutation(v.begin(), v.end())); }
-
std::prev_permutation
-
用途:生成下一个字典序更小的排列
-
示例 :
cpp#include <algorithm> #include <vector> #include <iostream> int main() { std::vector<int> v = {1,2,3}; do { // 依次输出:123, 132, 213, 231, 312, 321 for (auto i : v) { std::cout << i; } std:: cout<<std::endl; } while (std::next_permutation(v.begin(), v.end())); std::vector<int> v1 = {3,2,1}; do { // 依次输出:321, 312, 231, 213, 132, 123 for(auto i : v1) { std::cout << i; } std::cout << std::endl; } while (std::prev_permutation(v1.begin(), v1.end())); return 0; }
6. 分区算法
-
std::partition
-
功能:根据谓词条件将元素分为满足条件和不满足条件的两组,并将满足条件的元素移到前端。
-
不保证稳定性(相等元素可能交换顺序)。
-
示例:
cppstd::vector<int> vec = {1, 2, 3, 4, 5}; auto it = std::partition(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; }); // 偶数在前,奇数在后,如 {4, 2, 3, 1, 5}
std::stable_partition
-
用途:分区并保持元素相对顺序
-
示例 :
cpp#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> v = {1,2,3,4,1,5}; auto it = std::stable_partition(v.begin(), v.end(), [](int x) { return x < 3; }); // 结果:1,2,1,3,4,5 → 1,2 保持顺序,剩余元素保持顺序 for (auto i : v) { std::cout << i << " "; } return 0; }
-
注意:时间复杂度 O(n),但可能使用额外内存。
-
3.总结与选择建议
算法类型 | 典型场景 | 性能与特点 |
---|---|---|
sort |
需要快速排序,不关心相等元素顺序 | O(n log n),非稳定 |
stable_sort |
需保持相等元素原始顺序(如多字段排序) | O(n log n),内存占用较高 |
partial_sort |
仅需前 N 个元素有序(如排行榜前10名) | O(n log k),k 为部分排序大小 |
rotate |
循环移动元素(如缓冲区处理) | O(n) |
shuffle |
随机化数据(如游戏洗牌) | O(n),依赖随机数生成器质量 |
next_permutation |
遍历所有排列(如密码破解、组合优化) | O(n) 单次调用 |
stable_partition |
分区且需保持元素顺序(如日志按级别分类) | O(n),可能使用额外内存 |
四、如何去"定制"自己的算法呢?
1. 默认比较与自定义操作的必要性
C++标准库算法如sort
默认使用<
运算符比较元素。但在以下情况需自定义比较逻辑:
- 默认排序规则不适用:如按字符串长度而非字典序排序。
- 元素类型未定义
<
运算符:如自定义结构体需按特定成员排序。
2. 向算法传递函数:以sort
为例
sort
的重载版本允许传入谓词(Predicate),自定义排序规则。
示例1:按字符串长度排序
cpp
#include <vector>
#include <algorithm>
#include <string>
#include <iostream>
#include <string_view>
/**
* @brief 比较两个字符串视图的长度,判断第一个字符串是否比第二个字符串短。
*
* @param first 第一个要比较的字符串视图。
* @param second 第二个要比较的字符串视图。
* @return true 如果 first 的长度小于 second 的长度。
* @return false 如果 first 的长度大于或等于 second 的长度。
*/
// 自定义比较函数(二元谓词)
bool isShorter(const std::string_view first, const std::string_view second) {
return first.size() < second.size();
}
int main() {
std::vector<std::string> vec = {"apple", "banana", "cherry", "date"};
std::sort(vec.begin(), vec.end(), isShorter); // 按长度升序排列
for (const auto &str : vec) {
std::cout << str << " (" << str.size() << "), ";
}
// 结果:date (4), apple (5), banana (6), cherry (6)
return 0;
}
3. 谓词的定义与分类
- 谓词 :返回
bool
的可调用对象(函数、函数对象、Lambda)。- 一元谓词 :接受一个参数,如
find_if
的条件判断。 - 二元谓词 :接受两个参数,如
sort
的比较操作。
- 一元谓词 :接受一个参数,如
示例2:自定义结构体排序
cpp
struct Person {
std::string name;
int age;
};
// 按年龄升序的二元谓词
/**
* @brief 比较两个 Person 对象的年龄,判断第一个对象的年龄是否小于第二个对象的年龄。
*
* @param a 第一个要比较的 Person 对象的常量引用。
* @param b 第二个要比较的 Person 对象的常量引用。
* @return true 如果 a 的年龄小于 b 的年龄。
* @return false 如果 a 的年龄大于或等于 b 的年龄。
*/
#include <cassert>
bool compareByAge(const Person &a, const Person &b, bool ascending = true) {
if (ascending) {
return a.age < b.age;
}
return a.age > b.age;
}
int main() {
std::vector<Person> people = {{"Alice", 30}, {"Bob", 25}, {"Charlie", 20}};
// 升序排序
std::sort(people.begin(), people.end(), [](const Person &a, const Person &b) {
return compareByAge(a, b, true);
});
// 降序排序
std::sort(people.begin(), people.end(), [](const Person &a, const Person &b) {
return compareByAge(a, b, false);
});
return 0;
}
4. 严格弱序条件
自定义二元谓词必须满足严格弱序,确保排序逻辑合理:
- 非自反性 :
comp(a, a) == false
。 - 不对称性 :若
comp(a, b) == true
,则comp(b, a) == false
。 - 传递性 :若
comp(a, b) && comp(b, c)
,则comp(a, c)
。 - 等价传递性 :若
a
与b
等价,b
与c
等价,则a
与c
等价。
错误示例 :使用<=
破坏严格弱序
cpp
// 错误:违反非自反性和不对称性
bool badCompare(int a, int b) {
return a <= b; // 若a == b,返回true,导致未定义行为
}
5. 使用Lambda简化谓词
对于简单逻辑,Lambda表达式更便捷。
示例3:Lambda按结构体成员排序
cpp
struct Point { int x, y; };
int main() {
std::vector<Point> points = {{3, 5}, {1, 2}, {2, 4}};
// 按 x 坐标升序排序
std::sort(points.begin(), points.end(),
[](const Point &a, const Point &b) {
// 比较两个点的 x 坐标
return a.x < b.x;
});
// 结果:{1,2}, {2,4}, {3,5}
return 0;
}
6.小结
- 自定义排序 :通过传递二元谓词,覆盖
sort
的默认<
比较。 - 严格弱序:确保谓词满足条件,避免未定义行为。
- 灵活选择:函数、Lambda、函数对象均可作为谓词,按需使用。
再见咯