C++11:匿名函数(lambda函数/表达式)及其用法

C++11:匿名函数(lambda函数/表达式)及其用法

C++11提供了对匿名函数的支持,称为Lambda函数(也叫Lambda表达式). Lambda表达式具体形式如下:

[capture](parameters)->return-type{body}

如果没有参数,空的圆括号()可以省略.返回值也可以省略,如果函数体只由一条return语句组成或返回类型为void的话.形如:

[capture](parameters){body}

几个例子:

[](int x, int y) { return x + y; } // 隐式返回类型
[](int& x) { ++x; }   // 没有return语句 -> lambda 函数的返回类型是'void'
[]() { ++global_x; }  // 没有参数,仅访问某个全局变量
[]{ ++global_x; }     // 与上一个相同,省略了()

可以向这样返回指定类型:

[](int x, int y) -> int { int z = x + y; return z; }

在这个例子中创建了一个临时变量z来存储中间值. 和普通函数一样,这个中间值不会保存到下次调用. 什么也不返回的Lambda函数可以省略返回类型, 而不需要使用 -> void 形式.

  Lambda函数可以引用在它之外声明的变量. 这些变量的集合叫做一个闭包. 闭包被定义在Lambda表达式声明中的方括号[]内. 这个机制允许这些变量被按值或按引用捕获.下面这些例子就是:

[]        //未定义变量.试图在Lambda内使用任何外部变量都是错误的.
[x, &y]   //x 按值捕获, y 按引用捕获.
[&]       //用到的任何外部变量都隐式按引用捕获
[=]       //用到的任何外部变量都隐式按值捕获
[&, x]    //x显式地按值捕获. 其它变量按引用捕获
[=, &z]   //z按引用捕获. 其它变量按值捕获

这里所谓的捕获,其实上就是global作用域里能够找到相应的变量名,并且对变量名进行操作。

实例程序:

int main() {
    std::vector<int> some_list;
    int total = 0;
    for (int i=0;i<5;++i) {
        some_list.push_back(i);
    }
    std::for_each(begin(some_list), end(some_list), [&total](int x) {
        total += x;
    });
    std::cout << total << std::endl;
    return 0;
}

求出list中所有元素的总和。变量total被存为lambda函数闭包的一部分. 因为它是栈变量(局部变量)total的引用,所以可以改变它的值.

std::vector<int> some_list;
  int total = 0;
  int value = 5;
  std::for_each(begin(some_list), end(some_list), [&, value, this](int x) 
  {
    total += x * value * this->some_func();
  });

此例中total会存为引用, value则会存一份值拷贝. 对this的捕获比较特殊, 它只能按值捕获. this只有当包含它的最靠近它的函数不是静态成员函数时才能被捕获.对protect和priviate成员来说, 这个lambda函数与创建它的成员函数有相同的访问控制. 如果this被捕获了,不管是显式还隐式的,那么它的类的作用域对Lambda函数就是可见的. 访问this的成员不必使用this->语法,可以直接访问.

不同编译器的具体实现可以有所不同,但期望的结果是:按引用捕获的任何变量,lambda函数实际存储的应该是这些变量在创建这个lambda函数的函数的栈指针,而不是lambda函数本身栈变量的引用.

不管怎样, 因为大数lambda函数都很小且在局部作用中, 与候选的内联函数很类似, 所以按引用捕获的那些变量不需要额外的存储空间.如果一个闭包含有局部变量的引用,在超出创建它的作用域之外的地方被使用的话,这种行为是未定义的!

lambda函数是一个依赖于实现的函数对象类型,这个类型的名字只有编译器知道. 如果用户想把lambda函数做为一个参数来传递, 那么形参的类型必须是模板类型或者必须能创建一个std::function类似的对象去捕获lambda函数.使用 auto关键字可以帮助存储lambda函数.

auto my_lambda_func = [&](int x) { /*...*/ };
auto my_onheap_lambda_func = new auto([=](int x) { /*...*/ });

实例代码:把你函数存储在变量、数组或vector中,并把他们当做命名参数来传递。

#include<functional>
#include<vector>
#include<iostream>
double eval(std::function<double(double)> f, double x = 2.0){return f(x);}
int main()
{
     std::function<double(double)> f0    = [](double x){return 1;};
     auto                          f1    = [](double x){return x;};
     decltype(f0)                  fa[3] = {f0,f1,[](double x){return x*x;}};
     std::vector<decltype(f0)>     fv    = {f0,f1};
     fv.push_back                  ([](double x){return x*x;});
     for(int i=0;i<fv.size();i++)  std::cout << fv[i](2.0) << "\n";
     for(int i=0;i<3;i++)          std::cout << fa[i](2.0) << "\n";
     for(auto &f : fv)             std::cout << f(2.0) << "\n";
     for(auto &f : fa)             std::cout << f(2.0) << "\n";
     std::cout << eval(f0) << "\n";
     std::cout << eval(f1) << "\n";
     return 0;
}

 一个没有指定任何捕获的lambda函数,可以显式转换成一个具有相同声明形式函数指针.所以,像下面这样做是合法的:

int main() {
    auto a_lambda_func = [](int x) { std::cout << x << std::endl; };
    void(*func_ptr)(int) = a_lambda_func;
    func_ptr(4); //calls the lambda.
    return 0;
}

匿名函数的用处:

假设你设计了一个地址簿的类。现在你要提供函数查询这个地址簿,可能根据姓名查询,可能根据地址查询,还有可能两者结合。要是你为这些情况都写个函数,那么你一定就跪了。所以你应该提供一个接口,能方便地让用户自定义自己的查询方式。在这里可以使用lambda函数来实现这个功能。

#include <string>  
#include <vector>  

class AddressBook  
{  
    public:  
    // using a template allows us to ignore the differences between functors, function pointers   
    // and lambda  
    template<typename Func>  
    std::vector<std::string> findMatchingAddresses (Func func)  
    {   
        std::vector<std::string> results;  
        for ( auto itr = _addresses.begin(), end = _addresses.end(); itr != end; ++itr )  
        {  
            // call the function passed into findMatchingAddresses and see if it matches  
            if ( func( *itr ) )  
            {  
                results.push_back( *itr );  
            }  
        }  
        return results;  
    }  

    private:  
    std::vector<std::string> _addresses;  
};  

从上面代码可以看到,findMatchingAddressses函数提供的参数是Func类型,这是一个泛型类型。在使用过程中应该传入一个函数,然后分别对地址簿中每一个entry执行这个函数,如果返回值为真那么表明这个entry符合使用者的筛选要求,那么就应该放入结果当中。那么这个Func类型的参数如何传入呢?

#include <string>
#include <vector>
#include <iostream>
using namespace std;

class AddressBook {
public:
    // using a template allows us to ignore the differences between functors, function pointers
    // and lambda
    template<typename Func>
    std::vector<std::string> findMatchingAddresses (Func func)
    {
        std::vector<std::string> results;
        for ( auto itr = _addresses.begin(), end = _addresses.end(); itr != end; ++itr )
        {
            // call the function passed into findMatchingAddresses and see if it matches
            if ( func( *itr ) )
            {
                results.push_back( *itr );
            }
        }
        return results;
    }
    std::vector<std::string> _addresses;
};
AddressBook global_address_book;

vector<string> findAddressesFromOrgs () {
    return
    global_address_book.findMatchingAddresses
    (// we're declaring a lambda here; the [] signals the start
    [] (const string& addr) -> bool { return addr.find( ".org" ) != string::npos; }
    );
}
int main() {
    global_address_book._addresses.push_back("yan.org");
    auto result = findAddressesFromOrgs();
    std::cout << result[0] << std::endl;
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值