一点一滴成长

导航

C++11--lambda表达式

1、综述

  lambda表达式是一个匿名函数,它可以在函数内部定义,编译器会将lambda表达式当做一个函数对象。lambda表达式的形式为:[捕获列表] (参数列表) -> 返回类型{ 函数体 },其中“捕获列表”是表达式所在函数中定义的局部变量的列表,可以为空,而且lambda必须使用尾置返回来指定函数返回类型,eg:

int main()
{
    function<int(int)> func = [](int x)->int { return x * x; };
    cout << func(5) << endl;

    auto Func = [](int x)->int { return x * x; };
    cout << Func(10) << endl;

    int(*FuncPtr)(int) = Func;
    cout << (*FuncPtr)(20) << endl;

    return 0;
}
View Code

   lambda表达式的函数返回类型也可以省略,这时候lambda根据函数体中return语句自动推断出返回类型,如果函数体中没有return语句的话则会认为返回类型为void,代码示例如下。有些情况下,lambda不能自动推断出返回类型,比如函数体内含有多个return语句(if(x) return i; else return -1;),这个时候编译会出错,所以最保险的是不省略返回类型。

    function<int(int)> func = [](int x)/*->int*/{ return x * x; };

    auto Func = [](int x){ return x * x; };
View Code

  如果lambda表达式的没有参数的话还可以忽略参数列表,eg 

    function<int()> func = [] { return 100; };
    cout << func() << endl;

    auto Func = [] { return 100; };
    cout << Func() << endl;

    return 0;
View Code

   “捕获列表”是lambda表达式所在函数中定义的局部变量的列表,它用来指出在表达式内部可以使用这些变量。“捕获列表”中的变量只能是局部变量,且不能static类型,对于static类型的局部变量,表达式内部可以直接使用,而且static类型变量是引用捕获。而且捕获的变量在lambda中不能改变其值,除非使用mutable声明该lambda(在参数列表所在的括号后加mutable,如[]()mutable{ ... } )。前面说过编译器会将lambda表达式当做一个函数对象,所以“捕获列表”中的变量相当于是将其保存在了函数对象中,当函数对象释放的时候,捕获列表里的变量自动释放,eg:

 

    int i = 10;
    static int j = 5;
    std::shared_ptr<int> sp(new int);
    std::function<int()> Func = [i, sp]()
    {
        auto cnt = sp.use_count(); //cnt为2
        return i * j;
    };
    int n = Func(); //n为50
    auto cnt = sp.use_count(); //cnt为2
    Func = nullptr;
    cnt = sp.use_count(); //cnt为1
View Code

 

    lambda表达式可以用在STL算法中,如下代码展示了sort()算法分别使用函数指针,函数对象,lambda表达式的示例:  

bool myFunction(const int& i, const int& j) 
{ 
    return i > j; 
}

class myClass
{
public:
    bool operator() (const int& i, const int& j) { return i > j; }
};

int main()
{
    vector<int> v = list_of(5) (2) (4) (3) (1) (5);
    sort(v.begin(), v.end(), myFunction);
    sort(v.begin(), v.end(), myClass());
    sort(v.begin(), v.end(), [](const int& i, const int& j){ return i > j; });

    for (auto iter = v.begin(); iter != v.end(); iter++)
        cout << *iter << endl;

    return 0;
}
View Code

2、再谈变量的捕获

  “捕获列表”中的变量的捕获也可以是值捕获或引用捕获,如果是值捕获的话,传给捕获列表的变量在Lambda中实际上为变量的一个副本,而且被捕获的变量的值是在lambda表达式创建的时候就拷贝,而不是像函数参数的值传递那样在调用时拷贝,而且采用值捕获的变量如果会在函数体内修改的话还要使用mutable关键字来声明方法,eg:

int main()
{
    int n = 10;
    auto func = [n]()mutable{ return ++n;}; //lambda函数体内会修改n,所以应该加mutable关键字
    n = 0;
    auto num = func(); //n是lambda创建的时候拷贝而不是调用的时候拷贝,所以num是11而不是0

    return 0;
}
View Code

  如果是引用捕获的话在“捕获列表”中的捕获变量需要加&,eg:

int main()
{
    int n = 10;
    auto func = [&n](){ return ++n;}; //n是引用捕获
    n = 0;
    auto num = func(); //lambda调用的时候n为0,执行完lambda函数体后,num值为1,n为1

    return 0;
}
View Code

   我们也可以不传递“捕获列表”,让编译器根据lambda体中的变量使用代码来推断出哪些变量是捕获变量,即隐式捕获。为了指示由编译器推断捕获变量,应该在捕获列表中写一个=或&,=表示使用值引用方式,&表示采用引用捕获方式。如果我们希望对一部分变量采用值捕获,另一部分变量采用引用捕获,还可以混合使用隐式捕获和显示捕获。eg:

int main()
{
    int n = 10;
    auto func1 = [=]()mutable{ return ++n;}; //n是隐式值捕获
    auto func2 = [&]() {return ++n;}; //n是隐式引用捕获
    int i = 1, j = 2;
    auto func3 = [=, &i, &j]() {return n * i * j;}; //n是隐式值捕获,i和j是显示引用捕获
    auto func4 = [&, i, j]() {return n * i * j;}; //n是隐式引用捕获,i和j是显示值捕获

    return 0;
}
View Code

 可以在捕获列表中传入当前类的指针this,这样就可以在lamda中直接使用当前类的成员函数和成员变量:

class Foo
{
public:
    Foo()
    {
        Bar* b = new Bar;
        b->onClick = [this]{
            cout << "onClick" << endl;
            func();
        };
    }
    void func(){}
};

class Bar
{
public:
    std::function<void()> onClick;
};
View Code

 静态方法中静态成员的捕获:如下所示,showDlg()是类中的静态方法,_dlg是静态数据成员,lambda表达式需要使用_dlg的话,可以直接使用(VC++下, linux c++中不确定),不用添加this到捕获列表中,因为this只能用于非静态成员方法中。

void CIMVoiceCallDlg::showDlg()
{
    _dlg.reset(new CIMVoiceCallDlg);
    _dlg->closeWindow = [] {
        _dlg.reset();
    };
}
View Code

    捕获总结:

  1)、[]不捕获任何变量。
  2)、[bar]按值捕获bar变量。
       3)、[&foo]按引用捕获foo变量。
  4)、[=]捕获外部作用域中所有变量,并作为副本在函数体中使用,即按值捕获。
  5)、[&]捕获外部作用域中所有变量,并作为引用在函数体中使用,即按引用捕获。
  6)、[=,&foo]按值捕获外部作用域中所有变量,并按引用捕获foo变量。
  7)、[this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限,如果已经使用了&或者=,就默认添加此选项。

       8)、捕获的变量默认在lambda中不能被修改,除非使用mutable声明该lambda方法(在参数列表括号后加mutable)。

3、lambda与函数

  如果一个方法只在一个地方使用,也可以将其编写成lambda而不是函数,尤其是这个方法会被多次调用的情况,如下所示的mergeData方法,将输入的两个容器合并成一个容器,合并后的容器是有序的(因为使用了map),而且如果遇到相同的name的话,将两个content合并,而且合并后的content是不重复且有序的(因为使用了set):

struct SElement {
    std::string name;
    std::vector<std::string> content;
};

void mergeData(const std::vector<SElement>& vc1, const std::vector<SElement>& vc2, std::map<std::string, std::set<std::string>>& mapMerged)
{
    auto funMergeHelper = [&mapMerged](const std::vector<SElement>& vc) {
        for (const auto& elem : vc) {
            auto& setContent = mapMerged[elem.name]; //name不存在的话自动创建key-value,已存在的话直接获得key-value
            for (const auto& item : elem.content) {
                setContent.insert(item);
            }
        }
    };

    funMergeHelper(vc1);
    funMergeHelper(vc2);
}

  lambda表达式捕获变量的功能与bind绑定参数有相似的效果。

  对于子函数可以使用父函数中的局部变量这种行为,我们可以称做“闭包”,即“闭包”就是能够读取其他函数(父函数)中内部变量的函数(子函数),通常就是定义在一个函数内部的函数。可见lambda表达式和bind绑定参数都可以实现“闭包”。

posted on 2017-09-22 09:22  整鬼专家  阅读(403)  评论(0)    收藏  举报