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

C++11的lambda表达式

        lambda来源于函数式编程的概念。C++11这次终于把lambda加进来了。

        lambda表达式有如下优点:

        1、声明式编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,好的可读性和可维护性。

        2、简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散,让开发者更加集中精力在手边的问题,同时也获取了更高的生产率。

        3、在需要的时间和地点实现功能闭包,使程序更灵活。

lambda表达式的概念和基本用法

        lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda表达式的语法形式可简单归纳如下:

[capture](params) opt -> ret { body; };

        其中:capture是捕获列表;params是参数表;opt是函数选项;ret是返回值类型;body是函数体。

        因此,一个完整的lambda表达式看起来像这样:
 

        auto f = [](int) -> int { return a + 1;};cout << f(1) << endl;                //输出:2

        可以看到,上面通过一行代码定义了一个小小闭包,用来输入加1并返回。

        在C++11中,lambda表达式的返回值是通过前面介绍的返回值后置语法来定义的。其实很多时候,lambda表达式的返回值是非常明显的,比如上例。因此,C++11中运行省略lambda表达式的返回值定义:

       

 auto f = [](int a) {return a + 1; };

        这样编译器就会根据return语句自动推导出返回值类型。

        需要注意的是,初始化列表不能用于返回值的自动推导。
 

        auto x1 = [](int i){ return i; };         //OK:return type is intauto x2 = [](){ return {1, 2};};        // error:无法推导出返回值类型

        这时我们需要显示给出具体的返回值类型。

        另外,lambda表达式在没有参数列表时,参数列表是可以省略的。因此像下面的写法都是正确的:
 

        auto f1 = [] () { return 1; };auto f2 = [] { return 1; };         //省略空参数表

        lambda表达式可以通过捕获列表捕获一定范围的变量:

        1、[]不捕获任何变量。

        2、[&]捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。

        3、[=]捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。

        4、[=, &foo]按值捕获外部作用域中所有变量,并按引用捕获foo变量。

        5、[bar]按值捕获bar变量,同时不捕获其他变量。

        6、[this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员函数和成员变量。

lambda表达式基本用法

#include <iostream>
using namespace std;class A
{
public:int i_ = 0;void func(int x, int y){///auto x1 = [] { return _i; };         ///error,没有捕获外部变量auto x2 = [=] { return i_ + x + y; };   ///OK,捕获所有外部变量auto x3 = [&] { return i_ + x + y; };   ///OK,捕获所有外部变量auto x4 = [this] { return i_; };        ///OK,捕获this指针///auto x5 = [this] { return i_ + x + y; }; ///error,没有捕获x,yauto x6 = [this, x, y] { return i_ + x + y; };  ///OK,捕获this指针,x,yauto x7 = [this]{ return i_ ++; };      ///OK,捕获this指针,并修改成员的值}};int main()
{int a = 0, b = 1;auto f1 = [] { return a; };     ///error,没有捕获外部变量auto f2 = [&] { return a++; };  ///OK,捕获所有外部变量,并对a执行自加运算auto f3 = [=] { return a; };    ///OK,捕获所有外部变量,并返回aauto f4 = [=] { return a++; };  ///error,a是以复制方式捕获的,无法修改auto f5 = [a] { return a + b; };    ///error,没有捕获变量bauto f6 = [a, &b] { return a + (b++); };    ///OK,捕获a和b的引用,并对b做自加运算auto f7 = [=, &b] { return a + (b++); };    ///OK,捕获所有外部变量和b的引用,并对b做自加运算return 0;
}

        从上例中可以看到,lambda表达式的捕获列表精细的控制了lambda表达式能够访问的外部变量,以及如何访问这些变量。

        需要注意的是,默认状态下lambda表达式无法修改通过复制方式捕获的外部变量。如果希望修改这些变量的话,我们需要使用引用方式进行捕获。

    int a = 0;auto f = [=] { return a; };a += 1;cout << f() << endl;

        在这个例子中,lambda表达式按值捕获了所有外部变量。在捕获的一瞬间,a的值就已经被复制到f中了。之后a被修改了,但此时f中存储的a仍然还是捕获时的值,因此,最终输出结果是0。

        如果希望lambda表达式在调用时能够及时访问外部变量,我们应当使用引用方式捕获。

        如果希望去修改按值捕获的外部变量,需要显示指明lambda表达式为mutable:

int main()
{int a = 0;///auto f1 = [=](){ return a++; };          ///error,修改按值捕获的外部变量auto f2 = [=]()mutable { return a++; };     ///OK,mutablereturn 0;
}

        需要注意的一点是,被mutable修改的lambda表达式就算没有参数也要写明参数列表。

        lambda表达式的类型在C++11中被称为“闭包类型”。它是一个特殊的,匿名的非nunion的类型。因此,我们可以认为它是一个带有operator()的类,即仿函数。因此,我们可以使用std::function和std::bind来存储和操作lambda表达式:

std::function<int(int)> f1 = [] (int a) { return a; };
std::function<int(void)> f2 = std::bind( [] (int a) { return a; }, 123);

        另外,对于没有捕获任何变量的lambda表达式,还可以被转换成一个普通的函数指针:

using func_t = int(*)(int);
func_t f = [] (int a) { return a; };
f(123);

        lambda表达式可以说是就地定义仿函数闭包的“语法糖”。它的捕获列表捕获住的任何外部变量,最终均会变成闭包类型的成员变量。而一个使用了成员变量的类的operator(),如果能直接被转换为普通的函数指针,那么lambda表达式本身的this指针就会丢失掉。而没有捕获任何外部变量的lambda表达式则不存在这个问题。

        这里也可以很自然的解释为何按值捕获无法修改捕获的外部变量。因为按照C++标志,lambda表达式的operator()默认是const的。一个const成员函数是无法修改成员变量的值的。而mutable的作用,就在于取消operator()的const。

声明式的编程风格,简洁的代码

        就地定义匿名函数,不再需要定义函数对象,大大简化了标准库算法的调用。在C++11之前,我们要调用for_each函数将vector中的偶数打印出来,代码如下所示:

    
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
using namespace std;class CountEven
{
public:CountEven(int& c):count(c){}void operator()(int val){if (!(val & 1)){++count;}}private:int& count;
};int main()
{int count = 0;vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};for_each(v.begin(), v.end(), CountEven(count));cout << "The number of even is " << count << endl;return 0;
}

        这样写既繁琐又容易出错。有了lambda表达式以后,我们可以使用真正的闭包概念来替换这里的仿函数,代码如下所示:

        


#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
using namespace std;class CountEven
{
public:CountEven(int& c):count(c){}void operator()(int val){if (!(val & 1)){++count;}}private:int& count;
};int main()
{int count = 0;vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};for_each(v.begin(), v.end(), CountEven(count));cout << "The number of even is " << count << endl;count = 0;for_each(v.begin(), v.end(), [&count](int val){if (!(val & 1)){++ count;}});cout << "The number of even is " << count << endl;return 0;
}

        lambda表达式的价值在于,就地封装短小的功能闭包,可以及其方便的表达出我们希望执行的具体操作,并让上下文结合得更加紧密。

相关文章:

  • nlp之加载电商评论集
  • 真机环境配置教程
  • 项目进度延误,危机管理5大注意事项
  • 评论功能的选择难题:数据结构如何选定?
  • 什么是React中的有状态组件(stateful component)和无状态组件(stateless component)?
  • [每周一更]-(第69期):特殊及面试的GIT问题解析
  • 计算机网络重点概念整理-第七章 网络安全【期末复习|考研复习】
  • Redis学习笔记3:基于springboot的lettuce redis客户端validateConnection连接有效性检查
  • 2023-10-28 LeetCode每日一题(从数量最多的堆取走礼物)
  • PS 安装教程 2022版(全网最详细图文教程)
  • 自动驾驶之—LaneAF学习相关总结
  • 为什么Open3D可视化TensorFlow张量速度超慢
  • Go实现MapReduce
  • element-ui vue2 iframe 嵌入外链新解
  • 【C++初探:简单易懂的入门指南】一
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • [译]前端离线指南(上)
  • 【从零开始安装kubernetes-1.7.3】2.flannel、docker以及Harbor的配置以及作用
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • ES2017异步函数现已正式可用
  • iOS 颜色设置看我就够了
  • JavaScript工作原理(五):深入了解WebSockets,HTTP/2和SSE,以及如何选择
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • PHP 小技巧
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 深度学习在携程攻略社区的应用
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 我有几个粽子,和一个故事
  • 用quicker-worker.js轻松跑一个大数据遍历
  • 原生js练习题---第五课
  • ​LeetCode解法汇总1276. 不浪费原料的汉堡制作方案
  • #14vue3生成表单并跳转到外部地址的方式
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • (13):Silverlight 2 数据与通信之WebRequest
  • (3)选择元素——(17)练习(Exercises)
  • (4)(4.6) Triducer
  • (8)STL算法之替换
  • (Bean工厂的后处理器入门)学习Spring的第七天
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (动态规划)5. 最长回文子串 java解决
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)德国人的记事本
  • (转)使用VMware vSphere标准交换机设置网络连接
  • .htaccess 强制https 单独排除某个目录
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能
  • .NET Framework杂记
  • .net redis定时_一场由fork引发的超时,让我们重新探讨了Redis的抖动问题
  • .net refrector
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .net程序集学习心得
  • .NET中winform传递参数至Url并获得返回值或文件