现代 C++ 编程实战
吴咏炜
前 Intel 资深软件架构师
34196 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
加餐 (1讲)
现代 C++ 编程实战
15
15
1.0x
00:00/00:00
登录|注册

16 | 函数对象和lambda:进入函数式编程

按值捕获外围对象
按引用捕获
默认捕获符
可立即进行求值
adder
add_2
test1, test2, test3
add_2
bind1st 和 bind2nd
adder
map<string, function<int(int, int)>>
bind1st 和 bind2nd 的替代
sum
变量捕获
Lambda 表达式
函数的指针和引用
函数对象(function object)
std::function
std::bind
Currying
Lambda calculus
匿名函数
函数对象
function 模板
bind 模板
泛型 lambda 表达式
Lambda 表达式
C++98 的函数对象
参考资料
函数对象和lambda:进入函数式编程

该思维导图由 AI 生成,仅供参考

你好,我是吴咏炜。
本讲我们将介绍函数对象,尤其是匿名函数对象——lambda 表达式。今天的内容说难不难,但可能跟你的日常思维方式有较大的区别,建议你一定要试验一下文中的代码(使用 xeus-cling 的同学要注意:xeus-cling 似乎不太喜欢有 lambda 的代码😓;遇到有问题时,还是只能回到普通的编译执行方式了)。

C++98 的函数对象

函数对象(function object)[1] 自 C++98 开始就已经被标准化了。从概念上来说,函数对象是一个可以被当作函数来用的对象。它有时也会被叫做 functor,但这个术语在范畴论里有着完全不同的含义,还是不用为妙——否则玩函数式编程的人可能会朝着你大皱眉头的。
下面的代码定义了一个简单的加 n 的函数对象类(根据一般的惯例,我们使用了 struct 关键字而不是 class 关键字):
struct adder {
adder(int n) : n_(n) {}
int operator()(int x) const
{
return x + n_;
}
private:
int n_;
};
它看起来相当普通,唯一有点特别的地方就是定义了一个 operator(),这个运算符允许我们像调用函数一样使用小括号的语法。随后,我们可以定义一个实际的函数对象,如 C++11 形式的:
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

函数对象和lambda表达式是C++中函数式编程的重要概念。函数对象可以被当作函数来使用,而lambda表达式则是一种匿名函数,可以在需要时立即求值。它们的使用可以简化代码,提升性能并使代码更清晰。变量捕获和泛型lambda表达式是lambda表达式中的重要细节,增加了可组合性。此外,`bind`模板和`function`模板在泛型编程和函数式编程中发挥着重要作用。总的来说,函数对象和lambda表达式可以使代码更简洁、更灵活。读者可以通过本文了解函数式编程中的重要概念,并掌握相关技术特点,从而在实际编程中更加灵活运用。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《现代 C++ 编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(24)

  • 最新
  • 精选
  • zhengfan
    吴老师,您好。 四刷本讲。 我对您文中的这一句介绍有点好奇,“每个 lambda 表达式都有一个全局唯一的类型”。 请问这是怎么做到的?本质上不应该是一个函数指针么?这么规定的目的是什么?

    作者回复: 还真不是函数指针。可以认为编译器帮你组合出了一个唯一的类型名称吧。 举一个例子,如果你写: auto adder = [n](int x) { return n + x; }; 编译器产生的代码类似于: struct lambda_1266ab7e { lambda_1266ab7e(int n) : n_{n} {} auto operator()(int x) const { return n_ + x; } private: int n_; }; auto adder = lambda_1266ab7e(n);

    2020-07-10
    30
  • 廖熊猫
    老师新年快乐。 lambda表达式大概是生成了一个匿名的struct吧,实现了operator(), 捕获的话对应struct上的字段。

    作者回复: 新年快乐。 对,概念上就是这样。

    2020-01-02
    16
  • tt
    1、感觉lambda表达式就是C++中的闭包。 2、lambda表达式可以立即进行求职,这一点和JavaScript里的立即执行函数(Imdiately Invoked Function Expression,IIFE)一样。在JavaScript里,它是用来解决作用域缺陷的。 感觉在动态语言里被用到极致的闭包等特性,因为C++的强大、完备,在C++里很普通。 lambda的定义对应一个匿名函数对象,捕获就是构造这个对象时某种方式的初始化过程,用lambda表达式隐藏了这个过程,只保留了这个意思,更直观和写意。 老师,我对协程很感兴趣,C++会有协程么?隐约感觉捕获变量这个东西是不是可以用在实现协程上? 最后,祝老师新年快乐!

    作者回复: 对,就是闭包。 Stackful 协程见 Boost.Coroutine2。Stackless 协程已经进入 C++20,第 30 讲讨论。🤓 新年快乐!

    2020-01-02
    9
  • tt
    老师,回过头来看得时候,遇到了一个问题。 在用LAMBDA表达式解决多重初始化路径的问题时,说到这样还可以提高性能,因为不需要默认构造和不需要拷贝/移动。可是在第10讲中讲返回值优化的时候,不是说如果返回值时有条件判断,编译器都被会难倒,从而导致NRVO失效么(函数getA_duang)?

    作者回复: 注意我这儿用的是 return Obj(…) 的形式,不是有名变量的返回(Obj a{…} 然后 再 return a),不属于 named return value optimization 的情况。NRVO 指的是本地变量的返回。C++17 开始,prvalue 从语言上作了特殊解释,要求这样的返回直接构造到目的位置。

    2020-02-14
    2
    5
  • 总统老唐
    2020第一课,吴老师新年好

    作者回复: 谢谢🙏。在这儿也顺祝所有的同学们新年好!😇🎈🎊

    2020-01-01
    4
  • Daniel
    class task { public: task(int data) : data_(data) {} auto lazy_launch() { return [this, count = get_count()]() mutable { ostringstream oss; oss << "Done work " << data_ << " (No. " << count << ") in thread " << this_thread::get_id() << '\n'; msg_ = oss.str(); calculate(); }; } void calculate() { this_thread::sleep_for(100ms); cout << msg_; } private: int data_; string msg_; }; 输出: Done work 37 (No. 2) in thread 3 Done work 37 (No. 2) in thread 3 按引用捕获,应该是线程1输出之前msg_被线程2覆盖了。

    作者回复: 是这样。😊

    2022-03-21
    3
  • 罗 乾 林
    编译器遇到lambda 表达式时,产生一个匿名的函数对象,各种捕获相当于按值或者按引用设置给匿名对象的成员字段。 不对的地方,望老师指正。 对function<int(int, int)>这货怎么实现的比较好奇,大多数模板参数都是类型,做的都是是类型推导,这货居然是int(int, int)

    作者回复: lambda表达式的理解没啥问题。 int(int, int) 也是一个类型:一个接受两个整数参数、返回一个整数的函数。function 的主要复杂性,应该是需要处理函数、函数指针、函数对象等各种情况。函数对象的大小不确定,因而 function 需要在堆上分配内存。operator() 我记得相当于一个虚函数调用的复杂度。

    2020-01-01
    2
  • 西雨川久
    我提一个小问题: 原文:虽然函数名字叫 accumulate——累加——但它的行为是通过第四个参数可修改的。我们把上面的加号 + 改成星号 *,上面的计算就从从 1 加到 5 变成了算 5 的阶乘了。 实际上,还需要把第三个参数改成1,否则结果是0.

    作者回复: 嗯嗯,你说得很对。👍

    2022-11-17归属地:江苏
    1
  • ReCharge
    & 加本地变量名标明对其按引用捕获(不能在默认捕获符 & 后出现;因其已自动按引用捕获所有本地变量) 老师这句话有问题么?括号内外感觉描述矛盾 &本地变量名:这种写法不被允许么?

    作者回复: 我的意思是,可以写 [&a, &b],也可以写 [=, &a, &b],但不能写 [&, &a, &b]。

    2022-06-11
    1
  • zhengfan
    吴老师您好,请教一下: 对于形如[]() mutable {}的lamda表达式 还能够被认为是一个constexpr吗?

    作者回复: 有意思的问题。实测下来,C++ 不看形式,而是看实质。按你这个形式,能编译过。但如果这个 lambda 表达式真的会修改自己的存储的数值,则不可以是 constexpr。 见: https://godbolt.org/z/wc5axG 写 auto l = [x = 1]() { return x++; }; constexpr auto l = [x = 1]() mutable { return x++; }; 都不行。 auto l = [x = 1]() mutable { return x++; }; 可以。

    2020-06-30
    2
    1
收起评论
显示
设置
留言
24
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部