zoukankan      html  css  js  c++  java
  • C++ Lambda 表达式

    或许,Lambda 表达式算得上是 C++ 11 新增特性中最激动人心的一个。这个全新的特性听起来很深奥,但却是很多其他语言早已提供(比如 C#)或者即将提供(比如 Java)的。简而言之,Lambda 表达式就是用于创建匿名函数的。GCC 4.5.x 和 Microsoft Visual Studio 早已提供了对 lambda 表达式的支持。在 GCC 4.7 中,默认是不开启 C++ 11 特性的,需要添加  -std=c++11 编译参数。而 VS2010 则默认开启。 
    为什么说 lambda 表达式如此激动人心呢?举一个例子。标准 C++ 库中有一个常用算法的库,其中提供了很多算法函数,比如 sort() 和 find()。这些函数通常需要提供一个“谓词函数 predicate function”。所谓谓词函数,就是进行一个操作用的临时函数。比如 find() 需要一个谓词,用于查找元素满足的条件;能够满足谓词函数的元素才会被查找出来。这样的谓词函数,使用临时的匿名函数,既可以减少函数数量,又会让代码变得清晰易读。

    语法

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

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

    下面举了几个Lambda函数的例子:

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

    基本的Lambda函数用法

    下面来看一个例子: 

    #include <algorithm> 
    #include <cmath>   
    void abssort(float *x, unsigned N) { 
      std::sort( x,  x + N,  [](float a, float b) {  return std::abs(a) < std::abs(b);  } );  
    }

    从上面的例子来看,尽管支持 lambda 表达式,但 C++ 的语法看起来却很“神奇”。lambda 表达式使用一对方括号作为开始的标识,类似于声明一个函数,只不过这个函数没有名字,也就是一个匿名函数。这个匿名函数接受两个参数,a 和 b;其返回值是一个 bool 类型的值,注意,返回值是自动推断的,不需要显式声明,不过这是有条件的!条件就是,lambda 表达式的语句只有一个 return。函数的作用是比较 a、b 的绝对值的大小。然后,在此例中,这个 lambda 表达式作为一个闭包被传递给 std::sort() 函数。 
    下面,我们来详细解释下这个神奇的语法到底代表着什么。 我们从另外一个例子开始: 
    std::cout << [](float f) { return std::abs(f); } (-3.5);

    输出值是什么?3.5!注意,这是一个函数对象(由 lambda 表达式生成),其实参是 -3.5,返回值是参数的绝对值。lambda 表达式的返回值类型是语言自动推断的,因为 std::abs() 的返回值就是 float。注意,前面我们也提到了,只有当 lambda 表达式中的语句“足够简单”,才能自动推断返回值类型。


    C++ 11 的这种语法,其实就是匿名函数声明之后马上调用(否则的话,如果这个匿名函数既不调用,又不作为闭包传递给其它函数,那么这个匿名函数就没有什么用处)。如果你觉得奇怪,那么来看看 JavaScript 的这种写法: 

    function() {} ();   
    function(a) {} (-3.5);

    C++ 11 的写法完全类似 JavaScript 的语法。 
    如果我不想让 lambda 表达式自动推断类型,或者是 lambda 表达式的内容很复杂,不能自动推断怎么办?比如,std::abs(float) 的返回值是 float,我想把它强制转型为 int。那么,此时,我们就必须显式指定 lambda 表达式返回值的类型: 
    std::cout << [](float f) -> int { return std::abs(f); } (-3.5);

    这个语句与前面的不同之处在于,lambda 表达式的返回时不是 float 而是 int。也就是说,上面语句的输出值是 3。返回值类型的概念同普通的函数返回值类型是完全一样的。 

    Lambda函数中的变量截取


    引入 lambda 表达式的前导符是一对方括号,称为 lambda 引入符(lambda-introducer)。lambda 引入符是有其自己的作用的,不仅仅是表明一个 lambda 表达式的开始那么简单。lambda 表达式可以使用与其相同范围 scope 内的变量。这个引入符的作用就是表明,其后的 lambda 表达式以何种方式使用(正式的术语是“捕获”)这些变量(这些变量能够在 lambda 表达式中被捕获,其实就是构成了一个闭包)。目前为止,我们看到的仅仅是一个空的方括号,其实,这个引入符是相当灵活的。例如: 

    float f0 = 1.0; 
    std::cout << [=](float f) { return f0 + std::abs(f); } (-3.5);

    其输出值是 4.5。[=] 意味着,lambda 表达式以传值的形式捕获同范围内的变量。另外一个例子: 
    float f0 = 1.0; 
    std::cout << [&](float f) { return f0 += std::abs(f); } (-3.5); std::cout << '
    ' << f0 << '
    ';

    输出值是 4.5 和 4.5。[&] 表明,lambda 表达式以传引用的方式捕获外部变量。那么,下一个例子: 
    float f0 = 1.0; 
    std::cout << [=](float f) mutable { return f0 += std::abs(f); } (-3.5); std::cout << '
    ' << f0 << '
    ';

    这个例子很有趣。首先,[=] 意味着,lambda 表达式以传值的形式捕获外部变量。C++ 11 标准说,如果以传值的形式捕获外部变量,那么,lambda 体不允许修改外部变量,对 f0 的任何修改都会引发编译错误。但是,注意,我们在 lambda 表达式前声明了 mutable 关键字,这就允许了 lambda 表达式体修改 f0 的值。因此,我们的例子本应报错,但是由于有 mutable 关键字,则不会报错。那么,你会觉得输出值是什么呢?答案是,4.5 和 1.0。为什么 f0 还是 1.0?因为我们是传值的,虽然在 lambda 表达式中对 f0 有了修改,但由于是传值的,外部的 f0 依然不会被修改。 
    上面的例子是,所有的变量要么传值,要么传引用。那么,是不是有混合机制呢?当然也有!比如下面的例子: 
    float f0 = 1.0f; float f1 = 10.0f; 
    std::cout << [=, &f0](float a) { return f0 += f1 + std::abs(a); } (-3.5); std::cout << '
    ' << f0 << '
    ';


    这个例子的输出是 14.5 和 14.5。在这个例子中,f0 通过引用被捕获,而其它变量,比如 f1 则是通过值被捕获。 
    下面我们来总结下所有出现的 lambda 引入符:

    []         // 不捕获任何外部变量

    [=]       // 以值的形式捕获所有外部变量 

    [&]       // 以引用形式捕获所有外部变量 

    [x, &y]  // x 以传值形式捕获,y 以引用形式捕获 

    [=, &z] // z 以引用形式捕获,其余变量以传值形式捕获  
    [&, x]   // x 以值的形式捕获,其余变量以引用形式捕获 
    [bar]   截取bar变量并且拷贝一份在函数体重使用,同时不截取其他变量

    [this]            截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。

    另外有一点需要注意。对于 [=] 或 [&] 的形式,lambda 表达式可以直接使用 this 指针。但是,对于 [] 的形式,如果要使用 this 指针,必须显式传入: 

    [this]() { this->someFunc(); }();

    Lambda函数和STL

    lambda函数的引入为STL的使用提供了极大的方便。比如下面这个例子,当你想便利一个vector的时候,原来你得这么写:

    vector<int> v;  
    v.push_back( 1 );  
    v.push_back( 2 );  
    //...  
    for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ )  
    {  
        cout << *itr;  
    }
    现在有了lambda函数你就可以这么写
    vector<int> v;  
    v.push_back( 1 );  
    v.push_back( 2 );  
    //...  
    for_each( v.begin(), v.end(), [] (int val)  
    {  
        cout << val;  
    } );

    而且这么写了之后执行效率反而提高了。因为编译器有可能使用”循环展开“来加速执行过程。


    至此,我们已经大致了解了 C++ 11 提供的 lambda 表达式的概念。建议通过结合 lambda 表达式与 std::sort() 或 std::for_each() 这样的标准函数来尝试使用一下吧!

    Lambda函数的用处

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

    #include <string>  
    #include <vector>  
      
    class AddressBook  
    {  
        public:  
        // 使用模板 
        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 )  
            {  
                // 调用该函数传递到findMatchingAddresses,看看它是否匹配
                if ( func( *itr ) )  
                {  
                    results.push_back( *itr );  
                }  
            }  
            return results;  
        }  
      
        private:  
        std::vector<std::string> _addresses;  
    };
    从上面代码可以看到,findMatchingAddressses函数提供的参数是Func类型,这是一个泛型类型。在使用过程中应该传入一个函数,然后分别对地址簿中每一个entry执行这个函数,如果返回值为真那么表明这个entry符合使用者的筛选要求,那么就应该放入结果当中。那么这个Func类型的参数如何传入呢?

    AddressBook global_address_book;  
      
    vector<string> findAddressesFromOrgs ()  
    {  
        return global_address_book.findMatchingAddresses(   
            //lambda表达式,[]表示开始
            [] (const string& addr) { return addr.find( ".org" ) != string::npos; }   
        );  
    }
    可以看到,我们在调用函数的时候直接定义了一个lambda函数。参数类型是

    const string& addr
    返回值是bool类型。
    如果用户要使用不同的方式查询的话,只要定义不同的lambda函数就可以了。

    我想对 .NET 熟悉的朋友应该有印象,没错就是LINQ。呵呵

  • 相关阅读:
    Oracle常用命令大全(很有用,做笔记)
    表格驱动编程在代码中的应用
    mac 利用svn下载远程代码出现Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo.
    FAILURE: Build failed with an exception.
    There is an internal error in the React performance measurement code.Did not expect componentDidMount timer to start while render timer is still in progress for another instance
    react native TypeError network request failed
    Android向系统相册中插入图片,相册中会出现两张 一样的图片(只是图片大小不一致)
    react-native Unrecognized font family ‘Lonicons’;
    react-native SyntaxError xxxxx/xx.js:Unexpected token (23:24)
    Application MyTest has not been registered. This is either due to a require() error during initialization or failure to call AppRegistry.registerComponent.
  • 原文地址:https://www.cnblogs.com/Aion/p/3448509.html
Copyright © 2011-2022 走看看