• 转:C++ Lambda表达式


    Visual Studio 2010(下称VS2010)中的Visual C++编译器包含了对4项(正式发布后可能更多)C++0x 特性的支持,分别为lambda表达式auto关键字static_assert,和右值引用(rvalue references)。此篇文章将对前三项进行详细解释,rvalue references将在后续文章中解释。

    相关资料:
    C++0x language feature status:
    http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2705.html
    C++0x library feature status:
    http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2706.html
    C++0x Working Draft:
    http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2798.pdf

    Lambda表达式

    在C++0x中“lambda表达式”隐式的定义并构造了一个匿名的函数对象,它和普通的函数对象的行为是类似的。下面是“Hello World”的lambda:

    #include <algorithm>
    #include <iostream>
    #include <ostream>
    #include <vector>
    using namespace std;
    
    int main() 
    {
        vector<int> v;
        for (int i = 0; i < 10; ++i) 
        {
            v.push_back(i);
        }
        for_each(v.begin(),v.end(),[](int i) { cout<<i<<ends; } );
    }

      代码中的“[]”叫做“lambda前导符”,它告诉编译器一个lambda表达式开始。而“(int n)”是lambda表达式中的参数声明部分,它告诉编译器,这个匿名的函数对象中,函数调用运算符(“()”运算符)调用时接受的参数。最后“{ cout << n << " "; }”大括号语句作为这个匿名函数对象的函数调用符的函数体。默认情况下,匿名函数对象的返回值为void

    因此,上面的C++0x代码实质上等价于下面的C++98代码:

    #include <algorithm>
    #include <iostream>
    #include <ostream>
    #include <vector>
    using namespace std;
    
    struct LambdaFunctor {
        void operator()(int n) const {
            cout << n << " ";
        }
    };
    
    int main() {
        vector<int> v;
    
        for (int i = 0; i < 10; ++i) {
            v.push_back(i);
        }
    
        for_each(v.begin(), v.end(), LambdaFunctor());
        cout << endl;
    }

      现在,我不再说“那个匿名的函数对象的函数调用运算符的返回值是void”,而是直接说“这个lambda返回void”,但是,非常重要的一点是:一定要记住lambda表达式干了什么:定义了一个类(LambdaFunctor),然后构造了它的对象

    当然了,lambda的大括号表达式中,可以包含多个语句:

     
    #include <algorithm>
    #include <iostream>
    #include <ostream>
    #include <vector>
    using namespace std;
    
    int main() {
        vector v;
    
        for (int i = 0; i < 10; ++i) {
            v.push_back(i);
        }
    
        for_each(v.begin(), v.end(), [](int n) {
            cout << n;
    
            if (n % 2 == 0) {
                cout << " even ";
            } else {
                cout << " odd ";
            }
        });
    
        cout << endl;
    }

    0 even 1 odd 2 even 3 odd 4 even 5 odd 6 even 7 odd 8 even 9 odd

      现在,lambda也不必总是返回void,如果一个lambda的大括号表达式为{ return expression; },那么这个lambda的返回值类型将被自动推断为expression的类型.

    #include <algorithm>
    #include <deque>
    #include <iostream>
    #include <iterator>
    #include <ostream>
    #include <vector>
    using namespace std;
    
    int main() {
        vector<int> v;
    
        for (int i = 0; i < 10; ++i) {
            v.push_back(i);
        }
    
        deque<int> d;
    
        transform(v.begin(), v.end(), front_inserter(d), [](int n) { return n * n * n; });
    
        for_each(d.begin(), d.end(), [](int n) { cout << n << " "; });
        cout << endl;
    
    }

    729 512 343 216 125 64 27 8 1 0

    这里,n * n * n的类型为int,所以,lambda返回值类型为int。

     有些更加复杂的lambda表达式无法做出类型推断,你需要显式的指定它

    #include <algorithm>
    #include <deque>
    #include <iostream>
    #include <iterator>
    #include <ostream>
    #include <vector>
    using namespace std;
    
    int main() {
        vector<int> v;
    
        for (int i = 0; i < 10; ++i) {
            v.push_back(i);
        }
    
        deque<int> d;
    
        transform(v.begin(), v.end(), front_inserter(d), [](int n) -> double {
            if (n % 2 == 0) {
                return n * n * n;
            } else {
                return n / 2.0;
            }
        });
    
        for_each(d.begin(), d.end(), [](double x) { cout << x << " "; });
        cout << endl;
    }

    照道理应该输出:

    4.5 512 3.5 216 2.5 64 1.5 8 0.5 0

    可是vs2010输出了:

    4 512 3 216 2 64 1 8 0 0
    请按任意键继续. . .

    说明vs2010对lambda的支持还不是很好

       “-> double”是可选的“lambda返回值类型从句”。为什么返回值类型声明没有像以前声明函数的时候放在前面呢?因为如果不把“[]”放在最前面的话,编译器将无法知道这是一个lambda表达式。

    这里,如果你忘记了声明返回值类型,编译器将会报告错误

     error C3499: a lambda that has been specified to have a void return type cannot return a value

    vs2010: error C3499: 已指定返回类型为 void 的 lambda 无法返回值

     

      上面列出的所有lambda都是无状态的:它们构造出来的匿名函数对象不包含任何数据成员。你也可以获得有状态的lambda,方法是通过“捕获”局部变量。空白的lambda前导符“[]”表示这是一个无状态的lambda,但是在“[]”内部,你可以指定一个捕获组

    #include <algorithm>
    #include <iostream>
    #include <ostream>
    #include <vector>
    using namespace std;
    
    int main() {
        vector<int> v;
    
        for (int i = 0; i < 10; ++i) {
            v.push_back(i);
        }
    
        int x = 0;
        int y = 0;
    
        // op>>()不会读取输入流中最后一个回车换行符,
        // 这有时候会带来很多问题。我推荐
        // 避免使用它,而使用非成员函数
        // getline(cin, str)读取一整行,
        // 然后再解析它。但是为了简洁起见,
        // 我会继续使用 op>>():
    
        cout << "Input: ";
        cin >> x >> y;
    
        v.erase(remove_if(v.begin(),v.end(),[x,y](int n) { return x<n&&y>n; } ),v.end());
    
        for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });
        cout << endl;
    }

    Input: 4 7
    0 1 2 3 4 7 8 9  (因为移除了4到7之间的元素)

    如果你忘记了写捕获组,编译器会报告错误:

     error C3493: 'x' cannot be implicitly captured as no default capture mode has been specified

    (一会儿我将会介绍默认捕获)

     记住,lambda表达式隐式的定义了一个匿名的函数对象,“{ return x < n && n < y; }”就是它函数调用符的函数体,所以尽管看起来大括号语句是在main()的范围内,但在概念上它是在main()函数范围之外的,lambda表达式中不能使用没有捕获的main()函数内的局部变量

    这是把上面程序等价转换后的代码:

    #include <algorithm>
    #include <iostream>
    #include <iterator>
    #include <ostream>
    #include <vector>
    using namespace std;
    
    class LambdaFunctor {
    public:
        LambdaFunctor(int a, int b) : m_a(a), m_b(b) { }
    
        bool operator()(int n) const { return m_a < n && n < m_b; }
    
    private:
        int m_a;
        int m_b;
    };
    
    int main() {
        vector<int>  v;
    
        for (int i = 0; i < 10; ++i) {
            v.push_back(i);
        }
    
        int x = 0;
        int y = 0;
    
        cout << "Input: ";
        cin >> x >> y; // EVIL!
    
        v.erase(remove_if(v.begin(), v.end(), LambdaFunctor(x, y)), v.end());
    
        copy(v.begin(), v.end(), ostream_iterator(cout, " "));
        cout << endl;
    }

    Input: 4 7
    0 1 2 3 4 7 8 9

      这里,你可以清楚的看到捕获变量是按值传递的。局部变量的拷贝被存储在了函数对象中。这样,函数对象存活的时间可以比被捕获的局部变量长。但是,需要注意几点:

      (a)捕获的变量在lambda中不可修改,因为默认情况下生成的是const函数调用运算符;

       (b)有些对象的复制操作开销比较大;

       (c)局部变量的更新将不会影响lambda内已经被捕获过的副本(这符合通常的值传递语义)。一会儿,我将解释在需要的时候如何应对上述三种情况。

    但是首先,如果你想捕获所有的局部变量,可以不必逐项列出它们,而只需告诉编译器“按值传递捕获所有变量”。实现方式是使用前导符“[=]”:

    #include <algorithm>
    #include <iostream>
    #include <ostream>
    #include <vector>
    using namespace std;
    
    int main() {
        vector v;
    
        for (int i = 0; i < 10; ++i) {
            v.push_back(i);
        }
    
        int x = 0;
        int y = 0;
    
        cout << "Input: ";
        cin >> x >> y; // EVIL!
    
        v.erase(remove_if(v.begin(), v.end(), [=](int n) { return x < n && n < y; }), v.end());
    
        for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });
        cout << endl;
    }

    Input: 4 7
    0 1 2 3 4 7 8 9

    当编译器看到lambda中的x和y,会将它们从main()中按值捕获进来

     对于情况(a)无法修改捕获的变量,如何处理呢?默认情况下,lambda的函数调用运算符是const的,但是你可以通过mutable关键字将其变为非const:

    http://hi.baidu.com/icexile/blog/item/a9836460f05fcbd58cb10dd0.html

    http://technet.microsoft.com/zh-cn/subscriptions/dd293603(v=vs.110).aspx

    http://developer.51cto.com/art/201207/345807.htm

    http://www.devbean.info/2012/05/cpp11-lambda/

    http://www.cppblog.com/len/archive/2011/04/22/50286.html

     http://www.cnblogs.com/haippy/archive/2013/05/31/3111560.html

     上面网址部分内容:

    C++11 的 lambda 表达式规范如下:

    [ capture ] ( params ) mutable exception attribute -> ret { body } (1)  
    [ capture ] ( params ) -> ret { body } (2)  
    [ capture ] ( params ) { body } (3)  
    [ capture ] { body } (4)  

    其中

    • (1) 是完整的 lambda 表达式形式,
    • (2) const 类型的 lambda 表达式,该类型的表达式不能改捕获("capture")列表中的值。
    • (3)省略了返回值类型的 lambda 表达式,但是该 lambda 表达式的返回类型可以按照下列规则推演出来:
      • 如果 lambda 代码块中包含了 return 语句,则该 lambda 表达式的返回类型由 return 语句的返回类型确定。
      • 如果没有 return 语句,则类似 void f(...) 函数。
    • 省略了参数列表,类似于无参函数 f()。

    mutable 修饰符说明 lambda 表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获对象的 non-const 方法。

    exception 说明 lambda 表达式是否抛出异常(noexcept),以及抛出何种异常,类似于void f(throw(X, Y)。

    attribute 用来声明属性。

    另外,capture 指定了在可见域范围内 lambda 表达式的代码内可见得外部变量的列表,具体解释如下:

      • [a,&b] a变量以值的方式呗捕获,b以引用的方式被捕获。
      • [this] 以值的方式捕获 this 指针。
      • [&] 以引用的方式捕获所有的外部自动变量。
      • [=] 以值的方式捕获所有的外部自动变量。
      • [] 不捕获外部的任何变量。
      • 例子:
      • #include <vector>
        #include <iostream>
        #include <algorithm>
        #include <functional>
         
        int main()
        {
            std::vector<int> c { 1,2,3,4,5,6,7 };
            int x = 5;
            c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; } ), c.end());
         
            std::cout << "c: ";
            for (auto i: c) {
                std::cout << i << ' ';
            }
            std::cout << '\n';
         
            // the type of a closure cannot be named, but can be inferred with auto
            auto func1 = [](int i) { return i+4; };
            std::cout << "func1: " << func1(6) << '\n'; 
         
            // like all callable objects, closures can be captured in std::function
            // (this may incur unnecessary overhead)
            std::function<int(int)> func2 = [](int i) { return i+4; };
            std::cout << "func2: " << func2(6) << '\n'; 
        }
  • 相关阅读:
    NHibernate之旅(2):第一个NHibernate程序
    Motion sensing game (Ping Pong Game)
    Java学习之道:Java操作Excel之导出下载
    安装和升级--基础--许可证信息--title and Copyright information
    spring 文件上传功能实现
    这些常见的网络故障,你都知道如何解决吗
    这些常见的网络故障,你都知道如何解决吗
    这些常见的网络故障,你都知道如何解决吗
    限流
    限流
  • 原文地址:https://www.cnblogs.com/youxin/p/2582568.html
Copyright © 2020-2023  润新知