【C++11】lambda 基本用法

 

lambda表达是c++中的可调用对象之一,在C++11中被引入到标准库中,使用时不需要包含任何头文件,但是编译时需要指定-std=c++11或其他支持c++11标准的编译命令(比如-std=c++0x-std=c++14-std=c++1y)。lambda表达式源于函数式编程的概念,简单来说它是一个匿名函数。它最大的作用就是不需要额外再写一个函数或者函数对象,避免了代码膨胀功能分散,让开发人员更加集中精力在手边的问题,提高生产效率。

基本概念和用法

c++中,普通的函数是长这样子的:

ret_value function_name(parameter) option { function_body; }

比如:

int get_value(int a) const {return a++;}

lambda表达式定义了一个匿名函数,并且可以捕获所定义的匿名函数外的变量。它的语法形式是:

[ capture ] ( parameter ) option -> return_type { body; };

其中:

  • capture 捕获列表
  • parameter 参数列表
  • option 函数选项
  • return_type 函数返回值类型
  • body 函数体

比如:

// defination
auto lamb = [](int a) -> int { return a++; };

// usage
std::cout << lamb(1) << std::endl;  // output: 2

组成lambda的各部分并不是都必须存在的:

  1. 当编译器可以推导出返回值类型的时候,可以省略返回值类型的部分
auto lamb = [](int i){return i;};   // OK, return type is int
auto lamb2 = [] () {return {1, 2};};    // Error
  1. lambda表达式没有参数时,参数列表可以省略
auto lamb = []{return 1;};      // OK

所以一个最简单的lambda表达式可以是下面这样,这段代码是可以通过编译的:

int main()
{
  []{};     // lambda expression
  return 0;
}

捕获列表

捕获列表是lambda表达式和普通函数区别最大的地方。[]内就是lambda表达式的捕获列表。一般来说,lambda表达式都是定义在其他函数内部,捕获列表的作用,就是使lambda表达式内部能够重用所有的外部变量。捕获列表可以有如下的形式:

  • [] 不捕获任何变量
  • [&]引用方式捕获外部作用域的所有变量
  • [=]赋值方式捕获外部作用域的所有变量
  • [=, &foo] 以赋值方式捕获外部作用域所有变量,以引用方式捕获foo变量
  • [bar] 以赋值方式捕获bar变量,不捕获其它变量
  • [this] 捕获当前类的this指针,让lambda表达式拥有和当前类成员同样的访问权限,可以修改类的成员变量,使用类的成员函数。如果已经使用了&或者=,就默认添加此选项。

捕获列表示例:

#include <iostream>

class TLambda
{
public:
    TLambda(): i_(0) { }
    int i_;

    void func(int x, int y) {
        int a;
        int b;

        // 无法访问i_, 必须捕获this,正确写法见l4
        auto l1 = [] () {
            return i_;
        };

        // 以赋值方式捕获所有外部变量,这里捕获了this, x, y
        auto l2 = [=] () {
            return i_ + x + y;
        };

        // 以引用方式捕获所有外部变量
        auto l3 = [&] () {
            return i_ + x + y;
        };

        auto l4 = [this] () {
            return i_;
        };

        // 以为没有捕获,所以无法访问x, y, 正确写法是l6
        auto l5 = [this] () {
            return i_ + x + y;
        };
        
        auto l6 = [this, x, y] () {
            return i_ + x + y;
        };

        auto l7 = [this] () {
            return ++i_;
        };

        // 错误,没有捕获a变量
        auto l8 = [] () {
            return a;
        };

        // a以赋值方式捕获,b以引用方式捕
        auto l9 = [a, &b] () {
            return a + (++b);
        };

        // 捕获所有外部变量,变量b以引用方式捕获,其他变量以赋值方式捕获
        auto l10 = [=, &b] () {
            return a + (++b);
        }

    }
};


int main()
{
    TLambda a;
    a.func(3, 4);

    return 0;
}

引用和赋值,就相当于函数参数中的按值传递和引用传递。如果lambda捕获的变量在外部作用域改变了,以赋值方式捕获的变量则不会改变。按值捕获的变量在lambda表达式内部也不能被修改,如果要修改,需使用引用捕获,或者显示的指定lambda表达式为 mutable

// [=]
int func(int a);

// [&]
int func(int& a);

// ------------------------------------------

int a = 0;
auto f = [=] {return a;};   // 按值捕获
a += 1;                    // a被修改
cout << f() << endl;        // output: 0

// ------------------------------------------

int a = 0;
auto f = [] {return a++;};      // Error
auto f = [] () mutable {return a++;}; // OK

lambda表达式的类型

上面一直使用auto关键字来自动推导lambda表达式的类型,那作为强类型语言的c++,这个lambda表达式到底是什么类型呢?lambda的表达式类型在c++11中被称为『闭包类型(Closure Tyep)』,是一个特殊的、匿名的、非联合(union)、非聚合(aggregate)的类类型。可以认为它是一个带有operator()的类,即防函数(functor)。因此可以使用std::functionstd::bind来存储和操作lambda表达式。

std::function<int (int)> f = [](int a) {return a;};
std::function<int (int)> f = std::bind([](int a){return a;}, std::placeholders::_1);
std::cout << f(22) << std::endl;

对于没有捕获任何变量的lambda,还可以转换成一个普通的函数指针。

using func_t = int (*)(int); 
func_t f = [](int a){return a;};
std::cout << f(22) << std::endl;

参考文档:

lambda表达式


作者:lcode
链接:https://www.jianshu.com/p/923d11151027

===========================================旧文=====================================

原文;https://www.shiyanlou.com/courses/1414

lambda 的语法比较简单,下面这个 lambda 返回一个字符串。

auto lambda = []{ return "hello world"; };
lambda(); //将返回 hello world 字符串

lambda 还可以传参数。

auto lambda = [](std::string str){ return str; };
lambda("hello world"); //将返回 hello world 字符串

可以看到 lambda 就像一个函数一样,可以接受参数和返回值,因为 lambda 就是一个匿名函数。它可以方便我们写出函数式的代码。

lambda 的语法一共有如下三种。

[ captures ] ( parameters ) -> return_type { body }
[ captures ] ( parameters ) { body }
[ captures ] { body }

语法中的关键词意义如下。

  • parameters: lambda 表达式接受的参数,就像函数参数一样
  • captures: 捕获变量,捕获变量的方式有两种:按引用捕获和按复制捕获
  • body: 语句块

常见的 lambda 表达式的写法如下。一般情况是不需要显式写出返回类型的,代码也会更简洁。如果 lambda 表达式没有参数的时候括号也是可以省略的。

[](int i){ return i; }
[](int i) ->int { return i; }
[](){ return 0; }
[]{ return 0; }

按引用捕获需要在 [ ] 内写 & ,按复制捕获为 = 。若什么都不写则不捕获外部变量。

int a = 1;
int b = 2;
[&a](int i){ return i; } //按引用捕获变量 a
[a](int i){ return i; } //按复制捕获变量 a
[=](int i){ return i; } //按复制捕获变量 a,b
[&, b](int i){ return i; } //按引用捕获变量,除了 b(b 按复制捕获)。

接下来在 code1 目录下新建一个 code0.cpp 文件。:

#include <iostream>

int main() {
    auto lambda = [] {
        std::cout << "hello lambda!" << std::endl;
    };
    lambda();
}

编译和运行代码:在 「build」 目录下执行

g++ ../code0.cpp -o code0 -std=c++11 && ./code0

输出结果:

hello lambda!

这是最简单的一个 lambda,接下来看其它几种形式的 lambda,打开并编辑 code1.cpp 文件:

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    auto lambda = []{ std::cout<<"hello lambda!"<<std::endl; };
    lambda();

    auto lambda1 = [](int i)->int { return i; };
    auto lambda2 = [](int i) { return i; };

    std::cout<<lambda1(1)<<std::endl;
    std::cout<<lambda2(2)<<std::endl;

    std::vector<int> v{1,2,3,4,5,6,7};
    std::for_each(v.begin(), v.end(), [](int i){
        std::cout<<i<<" ";
    });
    std::cout<<std::endl;
    return 0;
}

编译和运行代码:在 build 目录下执行

g++ ../code1.cpp -o code1 -std=c++11 && ./code1

输出结果:

hello lambda!
1
2
1 2 3 4 5 6 7

上面是 lambda 用于 stl 算法的经典例子。std::for_each 的时候,传入一个 lambda,用它把 vector 容器中的每个整数打印出来,这是 lambda 的一个典型应用场景,使用 lambda 让我们的代码变得简单清晰。

接下来我们实验一下 lambda 捕获变量的情况,在 code1 目录下新建 code2_1.cpp 文件:

#include <iostream>

int main() {
    int a = 1;
    int b = 2;
    int c = 3;

    auto lambda1 = [&]{
        a = 4;
        b = 5;
        c = 6;
    };

    lambda1();

    std::cout<<a<<" "<<b<<" "<<c<<std::endl;
}

编译和运行代码:在 build 目录下执行

g++ ../code2_1.cpp -o code2_1 -std=c++11 && ./code2_1

输出结果:

4 5 6

可以看到我们通过引用方式捕获变量后,就可以对变量进行修改了。接下来看按拷贝方式捕获的情况,在 code1 目录下新建 code2_2.cpp 文件:

#include <iostream>

int main() {
    int a = 1;
    int b = 2;
    int c = 3;

    auto lambda1 = [&]{
        a = 4;
        b = 5;
        c = 6;
    };

    lambda1();

    std::cout<<a<<" "<<b<<" "<<c<<std::endl;

    auto lambda2 = [a,b,c]() mutable{
        a = 1;
        b = 2;
        c = 3;
        std::cout<<"in lambda2 :"<<a<<" "<<b<<" "<<c<<std::endl;
    };

    lambda2();

    std::cout<<a<<" "<<b<<" "<<c<<std::endl;

    auto lambda3 = [=]() mutable{
        a = 10;
        b = 20;
        c = 30;
        std::cout<<"in lambda3 :"<<a<<" "<<b<<" "<<c<<std::endl;
    };

    lambda3();

    std::cout<<a<<" "<<b<<" "<<c<<std::endl;

}

编译和运行代码:在 build 目录下执行

g++ ../code2_2.cpp -o code2_2 -std=c++11 && ./code2_2

输出结果:

4 5 6
in lambda2 :1 2 3
4 5 6
in lambda3 :10 20 30
4 5 6

可以看到 lambda2 中 abc 按复制捕获,在 lambda 中修改 abc 是不会修改外面的 abc 的值。

lambda 在类中使用的时候需要捕获 this 指针才能访问类成员。

ambda 按引用捕获变量时要注意临时变量生命周期的问题,我们通过一个实验来看这个问题,在 code1 目录下新建 code3.cpp 文件,编写下面这些代码:

#include <iostream>
#include <string>
#include <functional>

std::function<void()> test(){
    std::string str = "hello";
    auto lambda = [&str]{
        std::cout<<str<<" "<<"lambda";
    };

    return lambda;
}

int main() {
    auto lambda1 = test();
    lambda1();
}

编译和运行代码:在 build 目录下执行

g++ ../code3.cpp -o code3 -std=c++11 && ./code3

输出结果:

将不会输出字符串,因为 test 函数中的 lambda 捕获的是一个临时变量 str 的引用,当 test 函数结束时 str 被析构,这时 lambda 捕获的 str 就变成一个无效的字符串,因此在 main 中再调用 lambda1 时是不会打印出字符串的。

摘自:实验课网站《C++ 实现 RPC 网络通讯库》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值