【c++面向对象编程】第48篇:Lambda表达式与std::function:OOP中的函数式编程
目录
一、Lambda 的基本语法
二、捕获列表(Capture)
值捕获 [=]
引用捕获 [&]
混合捕获
特定变量捕获
C++14 广义捕获(带初始值)
C++17 捕获 *this
三、mutable 关键字
四、泛型 Lambda(C++14)
五、std::function:统一的可调用对象包装器
存储成员函数
六、Lambda 与 std::function 的性能对比
七、完整例子:回调系统
八、Lambda 取代传统函数指针
九、Lambda 与 STL 算法
十、常见错误
1. 捕获引用导致悬空引用
2. 默认捕获 [=] 捕获 this
3. 在循环中捕获引用变量
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)参数用autostd::function:类型擦除包装器,可存储任何可调用对象性能:优先用
auto保存 Lambda,需要统一类型时才用std::function
💡 小作业:实现一个
TaskScheduler类,支持延迟执行和周期性执行任务(存储std::function<void()>回调)。用 Lambda 注册任务,测试单个任务和循环任务。
下一篇预告:第49篇《面向对象的单元测试:用GoogleTest测试类》——如何用 GoogleTest 测试 C++ 类?测试夹具(Test Fixture)复用对象,EXPECT_*vsASSERT_*,以及如何 mock 虚接口。下篇讲 OOP 代码的测试实践。
