【Cpp基础】lambda表达式的结构
在Linux条件变量的使用时,经常用到C++的 lambda 表达式
1. Lambda 表达式的基本结构
// 最基本的 lambda 表达式
[]() {
// 函数体
};
这就是一个完整的 lambda 表达式,相当于一个匿名函数。
2. 逐部分讲解
① 捕获列表 []
定义 lambda 可以访问哪些外部变量
int a = 10;
int b = 20;
// 1. 不捕获任何变量
[]() { /* 不能访问 a 或 b */ };
// 2. 按值捕获(复制)
[a, b]() {
// 可以访问 a 和 b 的副本
std::cout << a + b; // 输出 30
};
// 3. 按引用捕获
[&a, &b]() {
a = 100; // 修改原始变量
b = 200;
};
// 4. 捕获所有变量(按值)
[=]() {
// 可以访问所有外部变量的副本
};
// 5. 捕获所有变量(按引用)
[&]() {
// 可以访问并修改所有外部变量
};
// 6. 混合捕获
[=, &a]() { // a 按引用,其他按值
a = 100; // 可以修改 a
// b 是副本,不能修改原始 b
};
[&, a]() { // a 按值,其他按引用
// a 是副本,其他变量可修改
};
② 参数列表 ()
和普通函数一样
// 无参数
[]() { return 42; };
// 带参数
[](int x, int y) { return x + y; };
// 自动推导参数类型 (C++14+)
[](auto x, auto y) { return x + y; };
// 模板参数 (C++20+)
[]<typename T>(T x, T y) { return x + y; };
③ 函数体 {}
和普通函数体一样
[]() {
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
return sum; // 返回 55
};
④ 返回类型(通常自动推导)
// 自动推导返回类型
[](int x, int y) { return x + y; }; // 返回 int
// 显式指定返回类型
[](int x, int y) -> double {
return (x + y) / 2.0;
}; // 返回 double
// 多个 return 语句时,需要显式指定
[](bool flag) -> int {
if (flag) {
return 1; // int
} else {
return 0.5; // double,编译器会困惑
// 需要指定返回类型为 double 或 auto
}
};
⑤ mutable(可选)
允许修改按值捕获的变量
int counter = 0;
// ❌ 错误:不能修改按值捕获的变量
[=]() {
// counter++; // 编译错误
};
// ✅ 正确:使用 mutable
[=]() mutable {
counter++; // 可以修改副本,但不影响原始变量
return counter;
};
3. 从简单到复杂的示例
示例1:最简单的 lambda
// 定义一个 lambda 并立即调用
auto result = []() {
return "Hello, Lambda!";
}(); // 注意这里的 () 表示立即调用
std::cout << result; // 输出: Hello, Lambda!
示例2:作为变量存储
// 定义一个 lambda 函数
auto add = [](int a, int b) {
return a + b;
};
// 像普通函数一样使用
int sum = add(10, 20); // sum = 30
std::cout << sum << std::endl;
// 另一个 lambda
auto greet = [](const std::string& name) {
return "Hello, " + name + "!";
};
std::cout << greet("Alice") << std::endl; // Hello, Alice!
示例3:捕获外部变量
int x = 5;
int y = 3;
// 捕获外部变量
auto multiply = [x, &y]() {
// x 是按值捕获(副本)
// y 是按引用捕获(原始变量)
int result = x * y;
y = 100; // 可以修改 y,因为是引用
return result;
};
std::cout << multiply() << std::endl; // 输出: 15
std::cout << y << std::endl; // 输出: 100(y 被修改)
std::cout << x << std::endl; // 输出: 5(x 未被修改)
示例4:在 STL 算法中使用
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 1. 使用 lambda 过滤偶数
auto it = std::remove_if(numbers.begin(), numbers.end(),
[](int n) {
return n % 2 == 0; // 如果是偶数,移除
});
numbers.erase(it, numbers.end());
// 2. 使用 lambda 排序(降序)
std::sort(numbers.begin(), numbers.end(),
[](int a, int b) {
return a > b; // 降序排序
});
// 3. 使用 lambda 转换
std::vector<int> squares;
std::transform(numbers.begin(), numbers.end(),
std::back_inserter(squares),
[](int n) {
return n * n;
});
// 4. 使用 lambda 打印
std::for_each(squares.begin(), squares.end(),
[](int value) {
std::cout << value << " ";
});
return 0;
}
示例5:实际应用场景
// 场景1:按钮点击事件(模拟)
class Button {
public:
void setOnClick(std::function<void()> handler) {
onClick = handler;
}
void click() {
if (onClick) onClick();
}
private:
std::function<void()> onClick;
};
int main() {
Button btn;
int clickCount = 0;
// 使用 lambda 作为事件处理器
btn.setOnClick([&clickCount]() {
clickCount++;
std::cout << "按钮被点击了!次数: " << clickCount << std::endl;
});
btn.click(); // 输出: 按钮被点击了!次数: 1
btn.click(); // 输出: 按钮被点击了!次数: 2
return 0;
}
// 场景2:线程任务
#include <thread>
#include <vector>
int main() {
std::vector<std::thread> threads;
std::mutex mtx;
// 创建 5 个线程,每个线程执行不同的任务
for (int i = 0; i < 5; i++) {
threads.emplace_back([i, &mtx]() {
std::lock_guard<std::mutex> lock(mtx);
std::cout << "线程 " << i << " 正在运行" << std::endl;
});
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
return 0;
}
4. 学习路径建议
第1步:从最简单开始
// 先写一个什么都不做的 lambda
[]() {};
// 添加简单的返回
[]() { return 42; };
// 添加参数
[](int x) { return x * 2; };
第2步:练习捕获
int outside = 10;
// 尝试不同的捕获方式
[outside]() { /* 使用 outside 的副本 */ };
[&outside]() { /* 使用 outside 的引用 */ };
[=]() { /* 按值捕获所有 */ };
[&]() { /* 按引用捕获所有 */ };
第3步:在实际场景中使用
// 1. 排序
std::sort(vec.begin(), vec.end(),
[](auto a, auto b) { return a < b; });
// 2. 查找
auto it = std::find_if(vec.begin(), vec.end(),
[](int x) { return x > 10; });
// 3. 条件变量(你的问题)
cv.wait(lock, [&]() { return !queue.empty(); });
5. 常见错误和注意事项
// ❌ 错误1:引用捕获局部变量(生命周期问题)
std::function<void()> createLambda() {
int local = 42;
return [&local]() { // 危险!local 将被销毁
std::cout << local;
};
}
// ✅ 修正:按值捕获
std::function<void()> createLambda() {
int local = 42;
return [local]() { // 安全:复制了 local
std::cout << local;
};
}
// ❌ 错误2:在 lambda 中修改按值捕获的变量(没有 mutable)
int x = 10;
[=]() {
// x++; // 编译错误
};
// ✅ 修正:使用 mutable
[=]() mutable {
x++; // 可以修改副本
};
// ❌ 错误3:复杂的 lambda 导致代码难读
// 如果 lambda 太长,考虑写成普通函数
6. 技巧和最佳实践
// 技巧1:给 lambda 起一个有意义的类型名
using CompareFunc = bool(*)(int, int);
CompareFunc lessThan = [](int a, int b) { return a < b; };
// 技巧2:使用 auto 避免复杂的类型声明
auto add = [](auto a, auto b) { return a + b; };
// 技巧3:lambda 可以作为返回值
auto makeMultiplier(int factor) {
return [factor](int x) { return x * factor; };
}
auto timesTwo = makeMultiplier(2);
std::cout << timesTwo(5); // 输出: 10
记住:lambda 表达式就是一个没有名字的函数,可以方便地写在需要函数的地方。 刚开始可能会觉得奇怪,但用多了就会发现它非常方便!

浙公网安备 33010602011771号