zoukankan      html  css  js  c++  java
  • C++ lambda表达式

    C++11 引入了lambda表达式,这个特性的最普遍应用,就是配合泛型算法。

    泛型算法,采用了迭代器操作,从而使得各种不同的容器能使用一套算法。泛型算法允许我们定制自己的操作,即传递一个可调用对象,lambda其实也是一个可调用对象。下面先介绍下lambda表达式的基本结构和特性,最后再深入探索lambda表达式的本质。

    一、lambda表达式的基本构成

    lambda表达式表示了一个可调用的单元代码,与函数类似,具有一个返回类型,一个参数列表,一个函数体,不同之处在于,还有一个捕获参数列表。

    [](int a, int b)->bool{return a > b; }

    需要注意的是,返回类型必须使用尾置返回。参数列表和返回类型都是可以忽略的,但是捕获列表和函数体必须保留。个人猜想,省略了捕获列表和函数体可能会使得编译器无法判断类型。

    lambda既然是个可调用对象,那么它的调用方法也与一般的函数对象一致:

    class Increase
    {
    public:
        void operator()(int& val){++val;}
    };
    int main()
    {
        int i = 1, j = 1;
        auto f = [](int& j){ ++j; };//等价于Increase
        f(j);//1
        Increase Inc;
        Inc(i);//2
    }//1与2等价

    二、捕获

    捕获可以说是lambda最难理解的部分。所谓捕获,指的是允许lambda使用它所在函数中的局部变量。对于函数外的变量以及函数内的static变量,无需进行捕获即可直接使用。

    捕获有两种方式,一种是值捕获,另外一种是引用捕获。

    static i=1;
    int j=1;
    auto f1 = [&j]()->int{ ++j; return j; };//引用捕获
    auto f2 = [j](){return j; };//值捕获
    auto f3 = [](){return i; }//静态变量无需捕获

    引用捕获,实际上就是引用了一个局部变量。既然是引用,就要注意引用存在的问题。必须确保引用的变量在整个lambda执行的周期内都是存在的,并且为一个正确的值。

    值捕获,相当于函数参数值传递的过程。因此,必须保证捕获的量是可拷贝的。此外,针对捕获的值为指针或者引用类型,也存在确保对象依然存在的问题。

    因此,应对尽量减少捕获,避免潜在的问题。可能的话,尽量避免捕获指针或者引用。

    隐式捕获

    隐式捕获是一种简略的方法,下面举个例子来说明一下:

    int i = 1, j = 1;
    auto f = [=, &j](){return i + (++j); };

    前面“=”或者“&”说明默认捕获的类型,如果有需要显示捕获的另外一种类型,在后面单独列出即可。

    修改捕获量的值

    auto f2 = [j](){return ++j; };//error,因为这是个右值
    auto f4 = [j]()mutable{return ++j; };//正确

    值捕获是无法修改变量值的,加一个mutable即可。

    三、返回类型

    前面的例子中我们可以看到,我们没有显示说明返回类型,也得到了正确的结果。不过,默认的规则如下

    1、如果仅有return ,那么根据return 的类型确定返回类型。

    2、如果除了return 还有别的语句,那么返回void。

    所以,不仅有return语句时,尽量要自己显示说明返回类型。

    四、lambda表达式的本质

    我们知道,定义了调用运算符的对象,可以称为函数对象,即这个对象可以像函数一样被调用,行为类似函数。

    与lambda一样,函数对象同样可以作为泛型算法的自定义操作:

    class Bigger//1
    {
    public:
        bool operator()(int a, int b){ return a > b; }
    };
    bool bigger(int a, int b){ return a > b; }
    int main()
    {
        vector<int> vec{ 2, 0, 1, 3, 3, 0, 1, 9, 7, 7 };
        sort(vec.begin(), vec.end(), [](int a, int b)->bool{return a > b; });//2
        sort(vec.begin(), vec.end(), bigger);
        sort(vec.begin(), vec.end(), Bigger());
    }

    上面的例子中,分别用了lambda表达式、函数指针、函数对象来进行了排序,结果是相同的。

    事实上,lambda表达式就是一个函数对象。当编写了一个lambda表达式的时候,编译器将该表达式翻译成一个未命名类的未命名对象。

    lambda表达式产生的类中会有一个重载的函数调用运算符。如果没有捕获任何变量,如上面我们定义的lambda(1)和函数对象(2),其实是完全相同的。

    如果lambda采用引用捕获的方式,那么该变量其实不是lambda对象的成员,所以无需进行额外操作。

    如果采用值捕获,那么就相当于在对象内部创建了自己的成员,因此需要增加一部分内容:

    class F
    {
    public:
        F(int n) :num(n){}
        int operator()(){ return num; }
    private:
        int num;
    };
    int num = 100;
    auto f = [num](){return num; };//等价于F

    即增加了一个用捕获的值进行构造的构造函数。

    lambda表达式产生的类,不含有默认构造函数、赋值运算符和默认析构函数,其他成员由需要捕获的类型确定。

    本文只是简单介绍了lambda的基本操作和功能,以及lambda表达式编译时产生函数对象的行为,更加深入的内容,本人暂时还不清楚0 0

  • 相关阅读:
    检索COM类工厂中CLSID为{00024500-0000-0000-C000-000000000046}的组件时失败
    VSTO 开发中 应用ActionPane、CustomTaskPane
    Thread.Join()的详解
    HBase笔记
    Hive命令详解
    视频地址
    几种表
    如何将数据导入到hive中
    hdfs笔记
    分区表简介
  • 原文地址:https://www.cnblogs.com/lustar/p/7531605.html
Copyright © 2011-2022 走看看