当前位置: 首页 > news >正文

【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 的性能对比

特性原始 Lambdastd::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_*vsASSERT_*,以及如何 mock 虚接口。下篇讲 OOP 代码的测试实践。

http://www.cnnetsun.cn/news/2535211.html

相关文章:

  • 山东防爆监控哪个品牌好用
  • 3分钟解决网易云音乐格式限制:免费NCM转换工具完全指南
  • ComfyUI Manager 终极安装指南:3种方法轻松管理AI工作流节点
  • CANN NPU 功耗优化:推理服务的能效比提升实战
  • 2026论文写作工具红黑榜:AI论文网站怎么选?清单来了
  • AI Agent Harness 在智能客服领域的应用
  • 2026年论文党必备:盘点2026年倾心之选的的降AIGC网站
  • 为什么92%的Lindy自动化项目在第90天遭遇断崖式停滞?资深架构师紧急披露3个临界预警信号
  • 10_函数递归_从阶乘到递归调用栈
  • C++ 学习笔记---容器---vector(后续会更新)
  • CANN-ops-nn-昇腾NPU神经网络算子的积木盒子
  • 从翻车到封神:1个被低估的--no参数+2个隐藏材质关键词,让水面倒影清晰度突破人眼分辨极限
  • 如何用开源工具实现自动化硬件适配?OpCore-Simplify让跨平台部署变得简单
  • gcc下载地址
  • Keil C166嵌入式开发中的宽字符实现与优化
  • 飞行人形机器人空气动力学建模与CFD仿真实践
  • 抖音内容批量下载实战指南:从单视频到用户主页的高效方案
  • 企业内如何通过Taotoken实现API访问控制与审计
  • PostgreSQL 性能优化:从 3 秒到 30 毫秒,我做了这 5 件事
  • 文件上传漏洞深度解析:从getshell到六维纵深防御
  • IDA与Frida协同逆向:静态定位+动态Hook实战指南
  • Unity风格化山脉管线:轮廓生成+分层材质+程序植被
  • ThingsVis v1.1.15 版本更新:补齐嵌入与运维体验短板,多场景集成更可靠
  • 鸿蒙签名验证报错UNABLE_TO_VERIFY_LEAF_SIGNATURE根因解析
  • PE-bear:专注PE文件结构解析的静态分析利器
  • DeepSeek垂直搜索性能崩塌预警信号:当QPS>127且P99延迟突增>413ms时,必须立即执行的5项熔断操作(含Prometheus监控告警Rule模板)
  • KNN算法如何赋能GIS空间邻近性分析
  • 西班牙法院驳回西甲对 NordVPN 罚款请求,屏蔽令案件仍在审理
  • GPT-4混合专家架构真相:稀疏激活与动态路由原理
  • 学术演示文稿制作困境与LaTeX模板解决方案