4. Lambda与STL算法:std::for_each、std::find_if、std::sort中的Lambda应用
说实话,刚学C++那会儿,我对STL算法是又爱又恨。爱的是它功能强大,恨的是每次都要写个函数对象,代码又长又绕。直到Lambda表达式来了,我才觉得STL算法真正「活」了过来。
今天我们就聊聊三个最常用的算法——std::for_each、std::find_if和std::sort。它们和Lambda搭配起来,写代码就像在写业务逻辑本身,而不是在跟编译器较劲。
4.1 std::for_each:遍历的优雅写法
std::for_each说白了就是「对容器里的每个元素做点什么事」。以前你得写个循环,现在一行Lambda搞定。
我个人的习惯是:如果只是简单打印或修改,用for_each;如果还要中途跳出,那就用for循环。你想想看,for_each没有break,这是它的局限。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// 传统方式:写个函数对象
struct Print {
void operator()(int n) const {
std::cout << n << " ";
}
};
std::for_each(nums.begin(), nums.end(), Print());
// Lambda方式:清爽多了
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << " "; });
// 带状态的Lambda:统计总和
int sum = 0;
std::for_each(nums.begin(), nums.end(),
[&sum](int n) { sum += n; });
std::cout << "\n总和: " << sum;
return 0;
}
小技巧:如果你要修改容器元素,记得用引用捕获。比如 [&](int& n) { n *= 2; }。我刚开始就犯过这个错——传了值进去,改了半天原容器纹丝不动。
4.2 std::find_if:按条件查找
std::find_if是我项目里用得最多的算法之一。它返回第一个满足条件的元素的迭代器。找不到就返回end()。
我记得有一次写一个用户管理系统,要从一堆用户里找出第一个VIP用户。用Lambda写起来就像在描述需求:「找那个等级大于5的」。
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
struct User {
std::string name;
int level;
};
int main() {
std::vector<User> users = {
{"Alice", 3},
{"Bob", 7},
{"Charlie", 5},
{"David", 2}
};
// 查找第一个等级大于5的用户
auto it = std::find_if(users.begin(), users.end(),
[](const User& u) { return u.level > 5; });
if (it != users.end()) {
std::cout << "找到: " << it->name << "\n";
} else {
std::cout << "没找到\n";
}
// 带外部条件的查找
int minLevel = 4;
auto it2 = std::find_if(users.begin(), users.end(),
[minLevel](const User& u) { return u.level >= minLevel; });
return 0;
}
注意:std::find_if返回的是迭代器,使用前一定要检查是否等于end()。我曾经在项目里忘了检查,直接解引用,程序就崩了。嗯,那是个周五下午,debug到晚上八点才找到原因。
4.3 std::sort:自定义排序
std::sort默认是升序排列。但实际项目中,排序规则千奇百怪——按分数降序、按名字长度、甚至按某个字段的哈希值。Lambda在这里简直是救星。
我个人觉得,std::sort的Lambda参数是最直观的:你只要告诉它「a应该在b前面吗?」返回true就表示a在前。
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
struct Student {
std::string name;
int score;
};
int main() {
std::vector<Student> students = {
{"Alice", 85},
{"Bob", 92},
{"Charlie", 78},
{"David", 92}
};
// 按分数降序排列
std::sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.score > b.score;
});
// 按分数降序,分数相同按名字升序
std::sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
if (a.score != b.score)
return a.score > b.score;
return a.name < b.name;
});
// 输出结果
for (const auto& s : students) {
std::cout << s.name << ": " << s.score << "\n";
}
return 0;
}
核心要点:std::sort的Lambda必须满足「严格弱序」——即比较操作必须是可传递的,且不能出现a<b和b<a同时为true的情况。否则程序会崩溃。我见过一个同事写的排序Lambda里用了<=,结果排序时直接段错误。
4.4 三者对比与选择
| 算法 | 作用 | Lambda返回值 | 典型场景 |
|---|---|---|---|
| std::for_each | 遍历每个元素 | void | 打印、累加、修改 |
| std::find_if | 查找第一个匹配 | bool | 按条件搜索 |
| std::sort | 排序 | bool(a是否在b前) | 自定义排序规则 |
你想想看,这三个算法覆盖了日常开发中80%的数据处理需求。遍历、查找、排序——哪个项目能少了它们?
4.5 知识结构图
4.6 避坑指南
我这些年用Lambda配合STL算法,踩过不少坑。挑几个典型的说说:
- 捕获方式搞错:按值捕获时,Lambda内部拿到的是副本。如果你期望修改外部变量,记得用引用捕获
[&]。 - 生命周期问题:如果Lambda要在函数返回后使用(比如存到回调列表里),千万别捕获局部变量的引用——那个变量早就销毁了。
- 排序比较器不严格:
std::sort要求比较操作是「严格弱序」。用<=或>=都会出问题。记住:只用<或>。 - 忘记检查find_if结果:
std::find_if返回的迭代器一定要和end()比较后再使用。这是新手最容易犯的错误。
我的习惯:写Lambda时,先想清楚「我需要捕获哪些变量?按值还是按引用?」然后再动笔。这样能避免一半以上的bug。
好了,关于Lambda与STL算法的结合,我们就聊到这里。这三个算法——for_each、find_if、sort——是日常开发中的主力军。掌握了它们,你的C++代码会变得更简洁、更易读、更不容易出错。