• C++11、14、17里的lambda表达式简介


    复习的时候发现lambda表达式很有意思,以前又没有注意过,因此记录一下。

    一、lambda表达式:C++11

    C++11加入了lambda表达式。总的来说一个lambda表达式表达了一个函数对象。例如下面这个例子:

    1 int a = 1;
    2 auto out = [](int x) { cout << x << endl; };
    3     // lambda表达式的本质是一个函数,用auto来判断它的类型。
    4     //用[]可以声明一个lambda表达式。
    5 out(a);  // 1

    lambda表达式的模式就是“[](参数){函数体;}”,用auto可以把一个lambda表达式表达的函数对象存在一个变量中。

    “[]”是声明lambda表达式的语句,它有一些其他的模式,使得lambda表达式可以使用外部变量(叫做lambda表达式捕获外部变量),如下:

     1 auto nxt = [=]() { cout << a + 1 << endl; };
     2     //[]中写=,表明用传值(临时变量)的方式使用外部变量。
     3     //若其中什么都没有,则不可以使用外部变量。
     4 nxt();   // 2
     5 out(a);  // 1
     6 
     7 auto ind = [&]() { cout << ++a << endl; };
     8     //[]中写&,表明用引用的方式使用外部变量。
     9 ind();   // 2
    10 out(a);  // 2
    11 
    12 int b = 5;
    13 auto mul = [b, &a]() { cout << (a *= b) << endl; };
    14     //可以规定外部变量的用法,如此例,b即用传值,a即用引用。
    15     //传值都应该写在引用之前。
    16 mul();   // 10
    17 out(a);  // 10
    18 out(b);  // 5

    更严格一点,lambda表达式可以被规定类型:

    1 auto fnxt = [](int x) -> double { return double(x) + 0.01; };
    2     //在参数列表后面写上->可以规定返回类型,否则编译器会自行判断。
    3     //然而这个类型与auto所指代的类型是不同的。
    4 cout << fnxt(a) << endl;  // 10.01

    lambda表达式也可以不保存在变量里,作为一个临时变量。这是一个更方便的应用方法,譬如应用在STL算法里:

     1 cout << [](int x, int y) { return x + y; }(a, b) << endl;  // 15
     2     // lambda表达式当然也可以是一个临时变量
     3 
     4 int arr[5]{1, 6, 4, 3, 5};
     5 sort(arr, arr + 5, [](int x, int y) -> bool { return x < y; });
     6 for_each(arr, arr + 5, [](int x) { cout << x << " "; });
     7     //自然也可以当作函数对象传入某些STL方法
     8 cout << endl;  // 1 3 4 5 6
     9 for_each(arr, arr + 5, out);
    10     /*
    11     1
    12     3
    13     4
    14     5
    15     6
    16     */

    lambda表达式也可以作为一个返回值。这可以导致一些复杂的应用:

    1 auto strAdd = [](string x) { return [=](string y) { return y + x; }; };
    2     // lambda表达式是可以嵌套的。
    3     //此处定义了一个结合函数,允许两个(),作用是把前一个字符串接到后一个上。
    4     //第一个()返回的是一个需要一个()的lambda函数。
    5     //注意在内层lambda表达式看来,外层的形参也是外部变量,因此需要传值使用。
    6     // lambda表达式过多时分号是要格外注意的。
    7 cout << strAdd("world!")("Hello ") << endl;  // Hello world!

    二、泛型lambda:C++14

    C++11里引入了如上的lambda表达式,是极其方便的,然而还有一些限制没有被考虑到。譬如,虽然lambda表达式像是一个函数类,但是却不能是一个函数类模板。

    C++14里解决了这个问题(这被称为泛型lambda表达式):

    1 auto Tsqr = [](auto x) { return x * x; };
    2     // c++14里相对c++11添加了这一功能,允许lambda表达式用auto做参数类型。
    3     //相当于lambda表达式也可以是一个模板。
    4 cout << Tsqr(10) << " " << Tsqr(2.5) << endl;  // 100 6.25

    上面这段代码类似于下面的形式:

    1 template<class T>
    2 T Tsqr (T val) { return val * val; }

    三、constexpr lambda等:C++17

    还有一些其它的问题在C++17里被解决。其中之一是在类里的lambda表达式的问题,它不能平白地捕获类的成员变量,比如:

    1 class C {
    2     int val;
    3     public:
    4     C():val(0){}
    5     C(int a):val(a){}
    6     void print(){
    7         []() { cout << val << endl; }();
    8     }
    9 };

    这段代码编译时会报下面这样的错:

    work.cpp: In lambda function:
    work.cpp:15:24: error: 'this' was not captured for this lambda function
             []() { cout << val << endl; }();
                            ^~~
    work.cpp:15:24: error: invalid use of non-static data member 'C::val'
    work.cpp:10:9: note: declared here
         int val;

    报这个错的原因也很显然:传给成员函数的其实是this指针,因此在C++11、14里想完成这种工作应该这样写:

     1 class C {
     2     int val;
     3     public:
     4     C():val(0){}
     5     C(int a):val(a){}
     6     void print(){
     7         [this]() { cout << val << endl; }();
     8         //c++11、14中想要让lambda函数使用成员变量,需要传this进去
     9     }
    10 };

    可是这样子做又有新的问题,也就是this传入的是一个指针,这可能导致一些未知的问题。譬如当lambda表达式被调用时,this已经被销毁(在多线程编程时可能会遇到)。C++17提供了一个方案:

     1 class C {
     2     int val;
     3 
     4    public:
     5     C() : val(0) {}
     6     C(int a) : val(a) {}
     7     ~C() { cout << "destructed" << endl; }
     8     void print() {
     9         [*this]() { cout << val << endl; }();
    10         // c++11、14中想要让lambda函数使用成员变量,需要传this进去。
    11         // c++17中用*this表示传递一个拷贝进去。
    12     }
    13 };

    执行下面这段代码会看到析构函数被调用了两次:

    C(13).print();  // 13
        //destructed
        //destructed

    C++17里另一个改变是lambda表达式现在可以被视作一个constexpr常量,只要表达式里没有用到静态变量、虚函数(多态)、try/throw/catch、new/delete。

    (注:C++11之后加入的constexpr表示一个量是“可以在编译时就算出值”的常量。相比之下,const则没有这个限制,譬如函数形参里的const引用。可见constexpr是一个更严格的常量。严格地讲,constexpr是“常量”,而const是“只读”。)

    譬如下面的代码:

     1 template <int N>
     2 class D {
     3     int n = N;
     4 };
     5 
     6 int x;
     7 constexpr int y = 10;
     8 auto f = [](int x) { return x + 1; };
     9 D<x> d1;     // error
    10 D<y> d2;     // pass
    11 D<f(7)> d3;  // c++17 : pass

    以上就是C++11~17里对lambda表达式的引入和补充。

  • 相关阅读:
    [Real World Haskell翻译]第24章 并发和多核编程 第一部分并发编程
    [Real World Haskell翻译]第22章 扩展示例:Web客户端编程
    [Real World Haskell翻译]第27章 网络通信和系统日志 Sockets and Syslog
    druid 解密
    git clone 所有远程分支到本地
    十、Delphi 把Json转成Delphi实体类
    XML External Entity attack/XXE攻击
    大家好!
    XXE攻防——XML外部实体注入
    ubuntu安装Go环境
  • 原文地址:https://www.cnblogs.com/halifuda/p/14745049.html
Copyright © 2020-2023  润新知