C++11之lambda函數

最近一直在看mesos的源代碼,mesos中用到了很多C++11的新特性,lambda函數就是其中的一個。對於lambda函數簡單的來說就是java中的匿名函數。

語法定義

[capture] (paramenters) mutable->return-type {statement}

  1. [capture] 捕捉列表:捕捉上下文中的變量供函數使用
  2. (paramenters) 參數列表:跟普通參數列表相同,如果沒有參數則可以連同()一起省略
  3. mutable mutable修飾符:默認情況下,lanbda函數是一個const函數,mutable關鍵字可以取消其常量性,此時,即使參數列表爲空,也不可以省略參數列表
  4. ->return-type 返回類型:在沒有返回值時可以連同->一起省略,同時,在返回類型明確的情況下也可以省略,由編譯器進行推導
  5. {statement} 函數體:除了參數之外還可以使用捕捉的變量

簡單的lambda函數例子:

#include <iostream>
using namespace std;
int main( int argc, char* argv[]  )
{
    int boys = 3;
    int girls = 4;

    []{};//最簡單的lambda函數

    [=]{ return boys + girls; };//省略參數列表和返回類型

    auto fun1 = [&]( int c ){ girls = boys + c; };

    auto fun2 = [=, &girls]( int c )->int { return girls += boys + c; };

    auto totalChild = [ girls, &boys ]()->int { return girls + boys; };
    cout << totalChild();
    return 0;
}

捕捉列表的語法規則

  1. [var]表示值傳遞的方式捕捉變量
  2. [=]b表示值傳遞的方式捕捉所有的父作域的變量(包括this)
  3. [&var]表示引用傳遞捕捉變量
  4. [&]表示引用傳遞捕捉所有父作用域變量(包括this)
  5. [this]表示值傳遞的方式捕捉當前的this指針

    舉例:
    [=,&a,&b] 表示以引用傳遞的方式捕捉變量a和b,以值傳遞的方式捕捉其他變量
    [&,a,this]表示以值傳遞的方式捕捉變量a和this,以引用傳遞的方式捕捉其他變量

注意

不可以重複捕捉相同的變量
舉例:
[&,&a]變量a被捕捉兩次,這樣不可以
在塊作用域(在{}內的任何代碼都是有塊作用域的)以外的lambda函數捕捉列表必須爲空

lambda與仿函數

仿函數:重定義了成員函數operator()函數的一種自定義的類型對象,在代碼層面使用起來跟函數沒有區別。

仿函數的例子:

#include <iostream>

using namespace std;

class Tax
{
private:
    float rate;
    int base;
public:
    Tax( float r, int b ):rate(r), base(b){  }
    float operator() ( float money  ){ return ( money - base ) * rate; }
};

int main( int argc, char* argv[]  )
{
    Tax high( 0.4, 3000 );
    Tax middle( 0.25, 2000 );

    cout << "tax over 3w: " << high( 37500 ) << endl;
    cout << "tax over 2w: " << middle( 27500 ) << endl;
    return 0;
}

在通過一個例子介紹仿函數與lambda函數的對比:

#include <iostream>
using namespace std;
class AirportPrice
{
private:
    float _dutyfreerate;

public:
    AirportPrice( float price ):_dutyfreerate( price ){  }
    float operator() ( float price ){ return price * ( 1 - _dutyfreerate / 100 ); }
};
int main( int argc, char *argv[]  )
{
    float tax_rate = 5.5f;
    AirportPrice Change1( tax_rate );

    auto Change2 = [ tax_rate ]( float price )->float { return price * ( 1 - tax_rate / 100 ); };

    float purchased1 = Change1( 3699 );
    float purchased2 = Change2( 2899 );

    cout << purchased1 << endl;
    cout << purchased2 << endl;
    return 0;
}

通過上面的例子可以發現仿函數與lambda函數在使用上一樣,並且都可以捕捉一些變量作爲初始狀態,而事實上,編譯器在編譯階段是將lambda函數轉化成仿函數對象。

基礎使用

1.在使用效果上,仿函數等價於一個局部函數。對於運算比較複雜的函數,通常函數中會有大量的局部狀態和變量,這時函數需要實現一些“局部”功能,例如打印信息等,而這些局部功能不能與其他的函數共享,但是需要在此函數中重複使用,那麼使用lambda的捕捉列表功能相比於較獨立的全局靜態函數或者私有成員函數方便很多。

2.在編寫程序時,開發者通常會發現自己需要一些“常量”,但是這些常量的值卻由自己初始化狀態決定,例如:

#include <iostream>
using namespace std;
int Prioritize( int );
int allworks( int times  )
{
    int i;

    int x;
    try{
        for( i = 0; i < timesl i++  )
            x += Prioritize(i);//直接操作x函數
    }
    catch(...)
        x = 0;

    const int y = [=]{
        int i, val;
        try{
            for( i = 0; i < times; i++ )
                val += Prioritize(i);
        }
        catch(...)
            val = 0;
        return val;
    }();
}

其他

值傳遞捕捉列表與引用傳遞捕捉列表

對於按值方式傳遞的捕捉列表,其傳遞的值在lambda函數定義的時候就決定了;按引用傳遞的捕捉列表變量,其傳遞的值等於lambda函數調用時的值。
代碼例子:

#include <iostream>
using namespace std;

int main( int argc, char* argv[] )
{
    int j = 12;

    auto by_val_lambda = [=] { return j + 1;  };
    auto by_ref_lambda = [&] { return j + 1;  };

    cout << " by_val_lambda: " << by_val_lambda() << endl;
    cout << " by_ref_lambda: " << by_ref_lambda() << endl;

    j++;

    cout << " by_val_lambda: " << by_val_lambda() << endl;
    cout << " by_ref_lambda: " << by_ref_lambda() << endl;
    return 0;
}

運行結果如下:

 by_val_lambda: 13
 by_ref_lambda: 13
 by_val_lambda: 13
 by_ref_lambda: 14

lambda函數與函數指針之間的轉換

C++11允許lambda函數向函數指針轉換,但是要求lambda函數沒有捕捉到任何變量,且函數指針所示的函數原型,必須跟lambda函數有着相同的調用方式;同時,C++11不允許函數指針轉化成lambda函數。
代碼例子:

#include <iostream>

using namespace std;

int main( int argc, char* argv[] )
{
    int girls = 3;
    int boys = 4;

    typedef int (*allChild)( int x, int y );
    typedef int (*oneChild)( int x );

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

    allChild p;
    p = totalChild;

    /*編譯失敗
    oneChild q;
    q = totalChild;
    */

    decltype( totalChild ) allPeople = totalChild;//通過decltype函數獲得lambda函數的類型
    //編譯失敗
    //decltype( totalChild ) totalPeople = p;
    cout << (*p)( 5, 5 ) << endl;
    return 0;
}

mutable關鍵字

在C++11中。默認情況下lambda函數是一個const函數,按照規則const的成員函數不能在任何的函數體中改變非靜態成員變量的值。

代碼例子:

#include<iostream>
using namespace std;
int main( int argc,  char* argv[]  )
{
    int val = 10;

    /*
     * 編譯失敗
    auto const_val_lambda = [=]() { val = 3 };
    */
    //非const得lambda,可以修改常量數據
    auto mutable_val_lambda = [=]() mutable{ val = 3; cout << "lambda val = " << val << endl; };
    mutable_val_lambda();
    cout << "val = " << val << endl;

    //依然是const的lambda,不過沒有改動引用本身
    auto const_ref_lambda = [&]() { val = 3;  cout << "lambda val = " << val << endl;};
    const_ref_lambda();
    cout << "val = " << val <<  endl;

    //依然是const的lambda,通過參數傳遞val
    auto const_param_lanbda = [&]( int v ){ v = 13; cout << "lambda val = " << v << endl; };
    const_param_lanbda( val  );
    cout << "val = " << val << endl;

    return 0;
}

運行結果:

lambda val = 3
val = 10
lambda val = 3
val = 3
lambda val = 13
val = 3

其實編譯器將上述代碼中的const_val_lambda轉化成仿函數,代碼如下:

class const_val_lambda{
    public:
        const_val_lambda(int v):val(v){}
    public:
        void operator() ()const{ val = 3; }/*常量成員函數*/
    privateint val;
};

從上述的代碼來看,準確地說,lambda函數等價於const類型的operator()的仿函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章