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

    概述
    C++ 11中引入了新的lamdba表达式,使用也很简单,我最喜欢的是不用给函数取名称,每次给函数取名称都感觉自己读书太少~

    1、lambda表达式
    lambda表达式可以理解为一个匿名的内联函数。和函数一样,lambda表达式具有一个返回类型、一个参数列表和一个函数体。与函数不一样的是lambda必须使用尾置返回类型。一个lambda表达式表示一个可调用的代码单元。

    语法:[capture list] (parameter list) -> return type {function body}

    capture list:表示捕获列表,是一个lambda所在函数中定义的局部变量列表
    parameter list:表示参数列表
    return type:返回类型
    function body:函数体

    我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体,忽略参数列表等价于指定一个空函数列表,忽略返回类型,lambda会根据函数体中的代码推断出来(如果函数体直接return,则是void类型)。例如:

    auto f = [ ] {return 42;};
    cout << f() << endl;

    lambda的调用方式与普通函数的调用方式相同。

    与函数的几点不同在于:

    lambda表达式不能有默认参数。因此,一个lambda表达式调用的实参数目永远与形参数目相等。
    所有参数必须有参数名。
    不支持可变参数。
    2、捕获列表
    如果没有进行捕获,则lambda表达式函数体内只能使用参数列表中的变量。捕获就是明确的指明lambda能使用的局部变量(指调用lambda的地方局部变量)。

    这里记住两点就行了:

    lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。
    捕获列表只用于非静态局部变量,lambda可以直接使用静态局部变量和在它所在函数之外声明的名字。
    例如:

    void func()
    {
    static int i = 10;
    int j = 20;
    auto f1 = [ ] () { return j; }; //编译出错,没有进行捕获,函数体内不能使用
    auto f2 = [ ] () { return i; }; //静态变量无需捕获
    auto f3 = [j] () { return j; }; //进行了捕获,函数体内可以使用了
    };

    与函数参数传递类似,变量的捕获方式也可以是值或者引用。这里列出不同的捕获列表的方式,后面进行解释:

    2.1、值捕获
    与传递参数类似,采用值捕获的前提是变量可以拷贝。与参数不同,不捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。例如:

    void func()
    {
    size_t v1 = 42;
    auto f = [ v1 ] { return v1; }; //使用了值捕获,将v1拷贝到名为f的可调用对象
    v1 = 0;
    auto j = f(); //j为42,f保存了我们创建它是v1的拷贝
    }

    由于被捕获的值实在lambda创建时拷贝,因此在随后对其修改不会影响到lambda内部对应的值。

    默认情况下:如果以传值方式捕获外部变量,则在Lambda表达式函数体中不能修改该外部变量的值。

    2.2、引用捕获
    和函数引用参数一样,一个引用类型的变量在函数体内改变时,实际上使用的是引用所绑定的对象。

    void func()
    {
    size_t v1 = 42;
    auto f = [ &v1 ] { return v1; }; //引用捕获,将v1拷贝到名为f的可调用对象
    v1 = 0;
    auto j = f(); //j为42,f保存了我们创建它是v1的拷贝
    }

    如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候是存在的。lambda捕获的都是局部变量,这些变量在函数结束后就不复存在了。如果lambda可能在函数结束后执行,这里就会出现问题。

    有一些不可拷贝对象,只能使用引用捕获的方式,比如ostream对象。

    2.3、隐式捕获
    除了显示列出我们希望使用的来自所在函数的局部变量之外,我们还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。

    隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量

    int main()
    {
    int a = 123;
    auto f = [ = ] { cout << a << endl; }; //值捕获
    f(); // 输出:123

    auto f1 = [ & ] { cout << a++ << endl; }; //引用捕获
    f1(); //输出:123(采用了后++)

    cout << a << endl; //输出 124
    }

    2.4、混合方式捕获
    lambda还支持混合方式捕获,即同时使用显示捕获和隐式捕获。

    混合捕获时,捕获列表中的第一个元素必须是 = 或 &,此符号指定了默认捕获的方式是值捕获或引用捕获 。

    需要注意的是:显示捕获的变量必须使用和默认捕获不同的方式捕获。例如:

    void func()
    {
    int i = 10;
    int j = 20;
    auto f1 = [ =, &i] () { return j + i; }; //正确,默认值捕获,显示是引用捕获
    auto f2 = [ =, i] () { return i + j; }; //编译出错,默认值捕获,显示值捕获,冲突了
    auto f3 = [ &, &i] () { return i +j; }; //编译出错,默认引用捕获,显示引用捕获,冲突了
    };

    2.5、修改值捕获的值
    在Lambda表达式中,如果以传值方式捕获外部变量,则函数体中不能修改该外部变量,否则会引发编译错误。

    如果你希望被值捕获的值被改变,就必须在参数列表首加上关键字mutable。

    语法变为:[capture list] (parameter list) mutable -> return type {function body}

    int main()
    {
    int a = 123;
    auto f = [a]()mutable { cout << ++a; }; // 不会报错

    cout << a << endl; // 输出:123
    f(); // 输出:124
    }

    3、返回类型
    在默认的规则下,返回类型如下:

    如果只包含单一的return语句,那么根据return 的类型确定返回类型。
    如果除了return 还有别的语句,那么返回void。
    所以,有返回类型时,一定要自己显示的进行说明。

    4、lambda表达式的本质
    当我们编写了一个lambda之后,编译器将该表达式翻译成一个未命名类的未命名对象。该类含有一个重载的函数调用运算符。

    4.1、采用值捕获
    在采用值捕获时,lambda形成的类相当于含有自己的数据成员,同时创建构造函数,令其使用捕获的变量的值来初始化数据成员。例如两个数加法的方法:

    int func()
    {
    int a =10;
    int b = 20;
    auto addfun = [=] (const int c ) -> int { return a+c; };

    int c = addfun(b);
    cout << c << endl;
    };

    就等同于:

    class Myclass
    {
    public:
    Myclass( int a ) : m_a(a){}; //该形参对应捕获的变量

    //该调用运算符的返回类型、形参和函数体都与lambda一致
    int operator()(const int c) const
    {
    return a + c;
    }

    private:
    int m_a; //该数据对应通过值捕获的变量
    };

    lambda表达式产生的类不含有默认构造函数、赋值运算符及默认析构函数。因为不含默认构造函数,因此要想使用这个类必须提供一个实参。

    默认情况下,由lambda产生类当中的调用运算符是一个const成员函数,所以值捕获的值不能修改。如果加上mutable相当于去掉const。这样上面的很多限制就能讲通了。

    4.2、采用引用捕获
    如果lambda采用引用捕获的方式,编译器可以直接使用该引用而无须在lambda对象产生的类中将其存储为数据成员。

    唯一需要注意的是,变量将由程序负责确保执行时引用的对象确实存在。

    感谢大家,我是假装很努力的YoungYangD(小羊)。

    参考资料:
    《C++ primer 第五版》
    https://www.cnblogs.com/lustar/p/7531605.html

  • 相关阅读:
    Perl的比较操作符
    Perl的变量
    应用负载均衡之LVS(五):lvs和nginx的wrr加权调度算法规律分析
    sharding:谁都能读懂的分库、分表、分区
    MySQL中间件之ProxySQL(15):ProxySQL代理MySQL组复制
    MySQL中间件之ProxySQL(12):禁止多路路由
    MySQL中间件之ProxySQL(14):ProxySQL+PXC
    haproxy(8):haproxy代理MySQL要考虑的问题
    PXC快速入门
    vscode指定扩展安装位置
  • 原文地址:https://www.cnblogs.com/SchrodingerDoggy/p/14654401.html
Copyright © 2011-2022 走看看