目录
[一、Lambda 的基本语法](#一、Lambda 的基本语法)
[值捕获 [=]](#值捕获 [=])
[引用捕获 [&]](#引用捕获 [&])
[C++14 广义捕获(带初始值)](#C++14 广义捕获(带初始值))
[C++17 捕获 *this](#C++17 捕获 *this)
[三、mutable 关键字](#三、mutable 关键字)
[四、泛型 Lambda(C++14)](#四、泛型 Lambda(C++14))
[六、Lambda 与 std::function 的性能对比](#六、Lambda 与 std::function 的性能对比)
[八、Lambda 取代传统函数指针](#八、Lambda 取代传统函数指针)
[九、Lambda 与 STL 算法](#九、Lambda 与 STL 算法)
[1. 捕获引用导致悬空引用](#1. 捕获引用导致悬空引用)
[2. 默认捕获 [=] 捕获 this](#2. 默认捕获 [=] 捕获 this)
[3. 在循环中捕获引用变量](#3. 在循环中捕获引用变量)
[4. std::function 赋值开销大](#4. std::function 赋值开销大)
一、Lambda 的基本语法
cpp
[capture](parameters) -> return_type {
body
}
最简单形式:
cpp
auto greet = []() { cout << "Hello" << endl; };
greet(); // 调用
// 带参数
auto add = [](int a, int b) { return a + b; };
cout << add(3, 5); // 8
// 指定返回类型(通常可省略)
auto divide = [](double a, double b) -> double {
if (b == 0) return 0;
return a / b;
};
二、捕获列表(Capture)
捕获列表定义了 Lambda 如何访问外部变量。
值捕获 [=]
cpp
int x = 10, y = 20;
auto add = [=]() { return x + y; }; // 拷贝 x, y
cout << add(); // 30
x = 100; // 不影响 Lambda 内部的值
cout << add(); // 仍然是 30
引用捕获 [&]
cpp
int x = 10, y = 20;
auto add = [&]() { return x + y; };
cout << add(); // 30
x = 100; // Lambda 内部也会变
cout << add(); // 120
混合捕获
cpp
int a = 1, b = 2, c = 3;
auto func = [a, &b](int x) { // a 值捕获,b 引用捕获,c 不捕获
// return a + b + c; // ❌ c 不可用
return a + b + x;
};
特定变量捕获
cpp
int x = 10, y = 20, z = 30;
auto f1 = [x]() { return x; }; // 只捕获 x
auto f2 = [&y]() { return y; }; // 只捕获 y(引用)
auto f3 = [x, &y]() { return x + y; }; // x 值,y 引用
C++14 广义捕获(带初始值)
cpp
int x = 10;
auto f = [y = x + 5]() { return y; }; // y 用表达式初始化
cout << f(); // 15
auto g = [ptr = make_unique<int>(42)]() { return *ptr; };
C++17 捕获 *this
在成员函数中捕获 *this 的值(而非指针):
cpp
class Widget {
int value = 10;
public:
void memberFunc() {
auto f1 = [this]() { return value; }; // 捕获 this 指针
auto f2 = [*this]() { return value; }; // C++17:拷贝整个对象
// f2 修改不影响原对象
}
};
三、mutable 关键字
默认情况下,Lambda 的 operator() 是 const,不能修改值捕获的变量。加 mutable 可修改副本:
cpp
int count = 0;
auto increment = [count]() mutable {
return ++count; // 修改的是 Lambda 内部的副本
};
cout << increment(); // 1
cout << increment(); // 2
cout << count; // 0(原变量不变)
四、泛型 Lambda(C++14)
参数可以用 auto,让 Lambda 成为模板:
cpp
auto add = [](auto a, auto b) { return a + b; };
cout << add(3, 5); // 8
cout << add(3.14, 2.86); // 6.0
cout << add(string("a"), "b"); // "ab"
相当于编译器生成多个版本的 operator() 重载。
五、std::function:统一的可调用对象包装器
std::function 可以存储任何可调用对象:函数指针、Lambda、函数对象、成员函数。
cpp
#include <functional>
// 存储函数指针
int add(int a, int b) { return a + b; }
std::function<int(int, int)> f1 = add;
// 存储 Lambda
std::function<int(int, int)> f2 = [](int a, int b) { return a * b; };
// 存储函数对象
struct Multiply {
int operator()(int a, int b) const { return a * b; }
};
std::function<int(int, int)> f3 = Multiply();
// 使用
cout << f1(3, 5); // 8
cout << f2(3, 5); // 15
存储成员函数
成员函数需要一个对象实例:
cpp
struct Calculator {
int add(int a, int b) const { return a + b; }
};
Calculator calc;
// 使用 std::bind 或 Lambda
std::function<int(int, int)> f = [&calc](int a, int b) {
return calc.add(a, b);
};
// 或用 std::mem_fn
#include <functional>
std::function<int(const Calculator&, int, int)> f2 = &Calculator::add;
六、Lambda 与 std::function 的性能对比
| 特性 | 原始 Lambda | std::function 包装 |
|---|---|---|
| 类型 | 唯一匿名类型 | 类型擦除后的通用类型 |
| 内存 | 栈上(大小=捕获大小) | 堆上可能分配(取决于大小) |
| 调用开销 | 可内联 | 间接调用(虚函数风格) |
| 适用场景 | 性能敏感,局部使用 | 需要存储、传递时 |
结论 :能直接用 auto 就用 auto,只有在需要存储不同类型可调用对象时才用 std::function。
cpp
// 推荐
auto lambda = [](int x) { return x * 2; };
// 仅当需要统一类型容器时
vector<function<int(int)>> callbacks;
callbacks.push_back([](int x) { return x + 1; });
callbacks.push_back([](int x) { return x * 2; });
七、完整例子:回调系统
cpp
#include <iostream>
#include <vector>
#include <functional>
#include <string>
#include <algorithm>
using namespace std;
// 事件类型
enum class Event {
Click,
KeyPress,
MouseMove
};
// 事件管理器:存储不同事件的回调
class EventManager {
private:
using Callback = function<void(const string&)>;
vector<pair<Event, Callback>> handlers;
public:
void subscribe(Event e, Callback cb) {
handlers.emplace_back(e, move(cb));
}
void fire(Event e, const string& data) {
for (const auto& [event, callback] : handlers) {
if (event == e) {
callback(data);
}
}
}
};
int main() {
EventManager em;
// 注册 Lambda 回调
em.subscribe(Event::Click, [](const string& data) {
cout << "[Click 处理器 1] " << data << endl;
});
em.subscribe(Event::Click, [](const string& data) {
cout << "[Click 处理器 2] 收到点击: " << data << endl;
});
em.subscribe(Event::KeyPress, [](const string& key) {
cout << "[按键处理器] 按下: " << key << endl;
});
// 存储有状态的 Lambda
int clickCount = 0;
em.subscribe(Event::Click, [&clickCount](const string& data) {
clickCount++;
cout << "[计数器] 点击次数: " << clickCount << endl;
});
// 触发事件
cout << "=== 触发 Click 事件 ===" << endl;
em.fire(Event::Click, "按钮被按下");
cout << "\n=== 触发 KeyPress 事件 ===" << endl;
em.fire(Event::KeyPress, "Enter");
return 0;
}
输出:
text
=== 触发 Click 事件 ===
[Click 处理器 1] 按钮被按下
[Click 处理器 2] 收到点击: 按钮被按下
[计数器] 点击次数: 1
=== 触发 KeyPress 事件 ===
[按键处理器] 按下: Enter
八、Lambda 取代传统函数指针
传统 C 风格回调:
cpp
// 传统方式:需要定义全局函数或 static 成员
int compare_int(const void* a, const void* b) {
return *(int*)a - *(int*)b;
}
qsort(arr, n, sizeof(int), compare_int);
现代 C++ 方式:
cpp
vector<int> v = {3, 1, 4, 1, 5};
// 使用 Lambda
sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
// 自定义复杂排序
sort(v.begin(), v.end(), [](int a, int b) {
// 按绝对值降序,绝对值相同时按原值降序
int abs_a = abs(a), abs_b = abs(b);
if (abs_a != abs_b) return abs_a > abs_b;
return a > b;
});
优势:
-
逻辑定义在调用点,可读性好
-
可以捕获局部变量,不需要全局数据
-
编译器可内联,性能更好
九、Lambda 与 STL 算法
cpp
#include <algorithm>
#include <vector>
#include <numeric>
using namespace std;
// 查找第一个满足条件的元素
vector<int> v = {1, 2, 3, 4, 5, 6};
auto it = find_if(v.begin(), v.end(), [](int x) { return x > 3 && x % 2 == 0; });
// 找到 4
// 转换每个元素
transform(v.begin(), v.end(), v.begin(), [](int x) { return x * 2; });
// 条件删除
v.erase(remove_if(v.begin(), v.end(), [](int x) { return x < 5; }), v.end());
// 累加时处理每个元素
int sum = accumulate(v.begin(), v.end(), 0, [](int acc, int x) {
return acc + x * x; // 平方和
});
// 生成数据
generate_n(back_inserter(v), 10, [n = 0]() mutable { return n += 2; });
十、常见错误
1. 捕获引用导致悬空引用
cpp
function<int()> createCounter() {
int local = 0;
return [&local]() { return ++local; }; // ❌ 返回后 local 销毁
}
// 使用时会访问已销毁的内存
修正:值捕获 [=] 或 [local]。
2. 默认捕获 [=] 捕获 this
cpp
class Widget {
int x = 10;
public:
auto getLambda() {
return [=]() { return x; }; // [=] 捕获了 this 指针,不是 x 的副本
}
};
修正:C++17 可用 [*this] 拷贝整个对象,或显式捕获 [x]。
3. 在循环中捕获引用变量
cpp
vector<function<int()>> funcs;
for (int i = 0; i < 5; i++) {
funcs.push_back([&i]() { return i; }); // ❌ 所有 Lambda 共享同一个 i
}
for (auto& f : funcs) cout << f() << " "; // 输出 5 5 5 5 5
修正:值捕获 [i]。
4. std::function 赋值开销大
std::function 可能会分配堆内存。频繁复制大的 function 对象可能成为性能瓶颈。
十一、这一篇的收获
你现在应该理解:
-
Lambda 语法 :
[capture](params) mutable -> ret { body } -
捕获方式 :
[=]值、[&]引用、[this]、[*this](C++17) -
泛型 Lambda :
[](auto x, auto y)参数用auto -
std::function:类型擦除包装器,可存储任何可调用对象 -
性能 :优先用
auto保存 Lambda,需要统一类型时才用std::function
💡 小作业:实现一个
TaskScheduler类,支持延迟执行和周期性执行任务(存储std::function<void()>回调)。用 Lambda 注册任务,测试单个任务和循环任务。
下一篇预告 :第49篇《面向对象的单元测试:用GoogleTest测试类》------如何用 GoogleTest 测试 C++ 类?测试夹具(Test Fixture)复用对象,EXPECT_* vs ASSERT_*,以及如何 mock 虚接口。下篇讲 OOP 代码的测试实践。