在 C++ 中,如果我们想检查容器里是否存在某个元素,或者找出它排在什么位置,可以使用 <algorithm> 头文件提供的通用查找函数。常用的主要有 2 个:一个是按固定值查找的 std::find(),另一个是按自定义条件查找的 std::find_if()。
std::find() 按值查找
std::find() 用来在指定的 [first, last) 区间内,从左往右依次比对,查找第 1 个等于目标值的元素。
语法:
auto it = std::find(first, last, value);说明:
std::find() 接收以下 3 个参数:
- first(必选):查找范围的起始位置,比如
prices.begin(),包含在查找范围内。 - last(必选):查找范围的结束边界,比如
prices.end(),实际查找的是[first, last)左闭右开区间。 - value(必选):需要查找的目标值。
查找成功时,它会返回指向第 1 个匹配元素的迭代器;直到末尾都没找到时,则返回结束边界 last。
为什么 std::find() 不直接返回 true 或 false,而是返回一个迭代器呢?因为拿到迭代器之后,我们不仅能通过 it != end() 判断元素存不存在,找到目标后还能直接算出它的下标,或者通过 *it 修改该位置的值。
示例 1:查找元素并计算下标
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> prices = {18, 35, 50, 68};
// 1. 查找存在的元素 50
auto it1 = std::find(prices.begin(), prices.end(), 50);
if (it1 != prices.end()) {
auto idx = it1 - prices.begin(); // 计算元素下标
std::cout << "找到 50,下标为:" << idx << '\n';
}
// 2. 查找不存在的元素 99
auto it2 = std::find(prices.begin(), prices.end(), 99);
if (it2 == prices.end()) {
std::cout << "未找到 99\n";
}
return 0;
}运行结果如下。
找到 50,下标为:2
未找到 99分析:
在这个例子中,我们第 1 次查找整数 50 时,函数从头依次比对,在第 3 个位置找到 50 并返回指向它的迭代器 it1。
确认找到目标后,由于 prices.begin() 指向下标 0 处的 18,我们将两个迭代器相减 it1 - prices.begin()。算出两者中间隔了 2 个位置,正好得到下标 2。
第 2 次查找 99 时,直到末尾都没有匹配项,函数返回 prices.end(),程序进入 “未找到” 分支。需要注意,两个迭代器直接相减算下标只适用于 vector、array、deque,list 迭代器不支持直接相减。
注意 当 std::find() 没有找到目标元素时,返回的迭代器等于结束边界 end(),并不指向任何有效元素。因此在通过 *it 读取或修改元素之前,小伙伴们一定要先检查 it != 容器.end() 是否成立,千万不要直接解引用。
如果容器中存在重复元素,或者我们只想跳过前几个元素、在局部子区间内查找并修改元素,只需要调整传入的起止迭代器即可。下面我们来看一个例子。
示例 2:子区间查找与修改元素
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> nums = {10, 20, 30, 20, 40};
// 从下标 2 开始往后查找 20
auto it = std::find(nums.begin() + 2, nums.end(), 20);
if (it != nums.end()) {
*it = 200; // 直接修改找到的元素
}
for (int n : nums) {
std::cout << n << ' ';
}
std::cout << '\n';
return 0;
}运行结果如下。
10 20 30 200 40分析:
nums.begin() + 2 表示将查找起点直接设在下标为 2 的元素 30 处。这样一来,排在下标 1 处的第 1 个 20 就被跳过了,最终找到的是位于下标 3 处的第 2 个 20,并通过 *it = 200; 直接把该位置的值改成了 200。
对于上面这个例子,小伙伴们可以试着把查找起点改回 nums.begin(),看看最终被修改成 200 的是哪一个位置。
std::find_if() 按条件查找
std::find() 只能查找 “等于某个固定值” 的元素。但如果我们想查找 “第 1 个大于 85 分的成绩”,或者在对象容器中查找 “编号为 1002 的图书”,也就是按自定义条件查找,就需要使用 std::find_if() 函数。
语法:
auto it = std::find_if(first, last, pred);说明:
前两个参数 first 和 last 依然表示查找区间。第 3 个参数 pred 是一个自定义的判断函数或 Lambda 表达式,它接收当前正在检查的元素,并返回一个 bool 值。
std::find_if() 会从 first 开始,依次把区间内的每个元素传给 pred 检查。一旦遇到第 1 个让 pred 返回 true 的元素,它就会立即停止查找并返回指向该元素的迭代器。如果所有元素都不满足条件,则返回结束边界 last。
1. 按数值条件查找
我们先来看看如何使用 std::find_if() 配合 Lambda 表达式,找出容器中第 1 个满足大小条件的数值。
示例 3:查找首个大于 85 的分数
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> scores = {72, 80, 92, 65, 95};
// 查找第 1 个大于 85 的分数
auto it = std::find_if(scores.begin(), scores.end(),
[](int s) {
return s > 85;
});
if (it != scores.end()) {
std::cout << "首个大于 85 的分数:" << *it << '\n';
}
return 0;
}运行结果如下。
首个大于 85 的分数:92分析:
std::find_if() 从第 1 个元素 72 开始,依次把每个分数传给 Lambda 表达式检查。前面的 72 和 80 都不满足 s > 85,当检查到第 3 个元素 92 时,Lambda 返回 true,查找立即停止并返回指向 92 的迭代器。
虽然排在后面的 95 也大于 85,但因为 std::find_if() 只找第 1 个满足条件的元素,所以找到 92 后就不会再继续往后检查了。
2. 按结构体成员查找
在实际开发中,当 vector 里存放的是自定义的结构体或类对象时,结构体默认不支持直接使用 == 比对相等,因此无法直接调用 std::find()。此时使用 std::find_if() 按某个成员变量来匹配是最常用的做法。
示例 4:按编号查找结构体元素
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
struct Book {
int id;
std::string title;
int price;
};
int main() {
std::vector<Book> books = {
{1001, "C++ 入门", 59},
{1002, "数据结构", 68},
{1003, "操作系统", 75}
};
// 查找编号 id 为 1002 的图书
auto it = std::find_if(books.begin(), books.end(),
[](const Book& b) {
return b.id == 1002;
});
if (it != books.end()) {
std::cout << it->title << ":" << it->price << " 元\n";
}
return 0;
}运行结果如下。
数据结构:68 元分析:
在这个例子中,Lambda 参数写成了常量引用 const Book& b,避免每次检查时复制整个结构体对象。匹配到第 2 本书后,函数返回指向该元素的迭代器 it,我们通过 it->title 和 it->price 就能直接读取书名与价格。
map 和 set 自带的 find() 方法
虽然 std::find() 可以用于大多数容器,但它的工作方式是 “从头到尾挨个比对”。对于 vector、array、list 这类顺序容器,使用 std::find() 是很自然的选择。
不过对于 map、set 这类关联容器,它们内部已经按照特定结构组织好了数据,并且自身就提供了专属的 find() 成员函数。尤其是 map 容器,调用它自带的 find() 方法可以直接按照 “键” 快速定位数据,比从头遍历高效得多。
示例 5:使用 map.find() 按键查找
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> scores = {
{"Jack", 85},
{"Lucy", 95},
{"Tony", 90}
};
auto it = scores.find("Lucy");
if (it != scores.end()) {
std::cout << it->first << ":" << it->second << '\n';
}
return 0;
}运行结果如下。
Lucy:95分析:
scores.find("Lucy") 表示直接在 map 中按键查找 "Lucy":找到时返回指向该键值对的迭代器;找不到时返回结束边界 scores.end()。通过迭代器访问时,it->first 表示键 "Lucy",it->second 表示对应的值 95。
三种常见查找方式对比
在 C++ 开发中,面对不同的数据类型,对应的查找写法与返回值规则有所不同。平时写代码时,小伙伴们可以按下面 3 种情况来区分:
顺序容器:像 vector、array、list 本身没有 find() 成员函数,查找元素时使用 <algorithm> 中的 std::find() 或 std::find_if(),找不到时返回 end() 迭代器。
关联容器:像 map、set、unordered_map 优先调用容器自带的成员函数 find(),查找速度比从头遍历快得多,找不到时同样返回 end() 迭代器。
字符串:在字符串查找时,std::string 提供了专属的 find() 成员函数,它返回子串所在的下标数字,找不到时返回 std::string::npos 而不是迭代器。
