首頁 > 軟體

C++11 lambda(匿名函數)表示式詳細介紹

2022-07-11 14:01:14

前言

Lambda(匿名函數)表示式是C++11最重要的特性之一,lambda來源於函數語言程式設計的概念,也是現代程式語言的一個特點。

優點如下:

  • 宣告式程式設計風格:就地匿名定義目標函數或函數物件,有更好的可讀性和可維護性。
  • 簡潔:不需要額外寫一個命名函數或函數物件,,避免了程式碼膨脹和功能分散。
  • 更加靈活:在需要的時間和地點實現功能閉包。

概念及基本用法

lambda表示式定義了一個匿名函數,並且可以捕獲一定範圍內的變數。語法形式如下:

[ capture ] ( params ) opt -> ret { body; };

  • capture:捕獲列表
  • params:參數列
  • opt:函數選項
  • ret:返回值型別
  • body:函數體

一個完整的lambda表示式是這樣:

auto f = [](int a) -> int {return a + 1;};
cout << f(3) << endl;  //輸出4

以上定義了一個完整的lambda,但是在實際的使用中,可以省略其返回值的定義,編譯器會根據return語句進行自動推導返回值型別。

省略過後如下:

auto f = [](int a) {return a + 1;};

需要注意的是,初始化列表不能用於返回值的自動推導:

如:auto f = [](){return {1,2};}; //error:無法推導返回值型別

另外,如果表示式沒有參數列時,也可以省略,如:

auto f = []{return 1;};

捕獲變數

lambda表示式可以通過捕獲列表捕獲一定範圍內的變數,主要有以下幾種情況:

  • [] 不捕獲任何變數
  • [&]捕獲外部作用域中所有變數,並作為參照在函數體中使用(按參照捕獲)
  • [=]捕獲外部作用域中所有變數,並作為副本在函數體重使用(按值捕獲)
  • [=,&foo] 按值捕獲外部作用域中所有變數,並按參照捕獲foo變數
  • [bar] 按值捕獲bar變數,同時不捕獲其他變數
  • [this] 捕獲當前類中的this指標,讓表示式擁有和當前類成員函數同樣的存取許可權。如果已經使用了&或者=,就預設新增此選項。捕獲this的目的是可以在lambda中使用當前類的成員變數和成員函數。

通過範例來看具體用法:

class A 
{
public:
    int i_ = 0;
    
    void func(int x,int y)
    {
        auto x1 = []{return i_;};  // error,沒有捕獲外部變數
        auto x2 = [=]{return i_ + x + y;}; //ok,按值捕獲所有外部變數
        auto x3 = [&]{return i_ + x + y;}; //ok,按參照捕獲所有外部變數
        auto x4 = [this]{return i_;}; //ok,捕獲this指標
        auto x5 = [this]{return i_ + x + y;}; //error,沒有捕獲x和y變數
        auto x6 = [this,x,y]{return i_ + x + y;}; //ok,捕獲了this指標和x、y變數
        auto x7 = [this]{return i_++;}; //ok,捕獲了this指標,修改成員變數的值
    }
};
int a = 0 , b = 0 ;
auto f1 = []{return a;}; // error,沒有捕獲外部變數
auto f2 = [&]{return a++;}; //ok,捕獲所有外部變數,並對a變數自加
auto f3 = [=]{return a;}; //ok,捕獲所有外部變數,並返回a
auto f4 = [=]{return a++;}; //error,a變數是以複製方式捕獲的,不能修改
auto f5 = [a]{return a+b;}; //error,沒有捕獲b變數
auto f6 = [a,&b]{return a+ (b++);}; //ok,捕獲a以及b的參照,對b進行自加
auto f7 = [=,&b]{return a+ (b++);}; //ok, 捕獲所有外部變數和b的參照,對b進行自加

需要注意的是,lambda無法修改按值捕獲的外部變數,如果需要修改外部變數,可以通過參照方式捕獲。

關於lambda表示式的延遲呼叫很容易出錯,如下:

int a = 0;
auto f = [=]{return a;};
a += 1;
cout << f() << endl;

以上範例中,lambda按值捕獲了所有外部變數,在捕獲的時候 a的值就已經被複制到 f 中了,之後a被修改,但是f裡面儲存的a仍然是捕獲時的值,所以最終輸出的是 0.

如果希望lambda表示式在呼叫的時候能夠存取外部變數,需要使用參照方式捕獲。

所以簡單來說,按值捕獲,外部變數會被複制一份儲存在lambda表示式變數中。

如果是按值捕獲並且又想修改外部變數,可以顯示指明lambda表示式為mutable:

int a = 0;
auto f1 = [=]{return a++;};  //error,修改按值捕獲的外部變數
auto f2 = [=]() mutable {return a++;}; //ok

被mutable修飾的lambda表示式就算沒有引數也要寫明參數列。

lambda表示式型別

lambda表示式的型別在C++11中被稱為“閉包型別”,它是一個特殊的,匿名的非nunion的型別。

可以認為它是帶有一個operator()的類,即仿函數。
我們可以通過std::function和std::bind來儲存和操作lambda表示式:

std::function<int(int)> f1 = [](int a){return a;};
std::function<int(void)> f2 = std::bind([](int a){return a;},123);

另外,對於沒有捕獲任何變數的lambda表示式,還可以被轉換成一個普通的函數指標:

using func_t = int(*)(int);
func_t f = [](int a){return a;};
f(123);

lambda可以說是就地定義仿函數閉包的“語法 糖”。它的捕獲列表捕獲住任何外部變數,最終都會變為閉包型別的成員變數。而一個成員變數的類的operator(),如果能直接被轉換為普通的函數指標,那麼lambda表示式本身的this指標就丟掉了。而沒有捕獲任何外部變數的lambda表示式則不存在這個問題。

需要注意的是,沒有捕獲變數的lambda表示式可以直接轉換為函數指標,而捕獲變數的lambda表示式則不能轉換為函數指標。如下:

typedef void(*Ptr)(int*);
Ptr p = [](int *p){delete p;};  //ok
Ptr p1 = [&](int *p){delete p;}; //error

前面說到的按值捕獲無法修改捕獲的外部變數,因為按照C++標準,lambda表示式的operator()預設是const的,一個const成員函數是無法修改成員變數的值,而mutable的作用,就是取消operator()的const限制。

宣告式的程式設計風格

通過範例來看一下lambda的使用,在C++11之前,如果要用for_each函數將陣列中的偶數數量列印出來,程式碼如下:

#include <vector>
#include <algorithm>
class Count
{
public:
    Count(int &val):num(val){}
    void operator()(int val){
        if(!(val & 1)){
            ++num;
        }
    }
private:
    int &num;
};

int main()
{
    std::vector<int> v = {1,2,3,4,5,6,7};
    int count = 0;
    for_each(v.begin(),v.end(),Count(count));
    std::cout << count << endl;
    return 0;
}

如果使用lambda表示式,就可以簡化一下,真正使用閉包概念來替換這裡的仿函數。

#include <vector>
#include <algorithm>

int main()
{
    std::vector<int> v = {1,2,3,4,5,6,7};
    int count = 0;
    for_each(v.begin(),v.end(),[&count](int val){
        if(!(val & 1)){
            ++count;
        }
    });

    std::cout << count << endl;
    return 0;
}

lambda表示式的價值在於,就地封裝短小的功能閉包,方便地表達出我們希望執行的具體操作,並讓上下文結合的更加緊,程式碼更加簡潔,更靈活,也提高了開發效率及可維護性。

參考:《深入應用C++11》

總結

到此這篇關於C++11 lambda(匿名函數)表示式詳細介紹的文章就介紹到這了,更多相關C++11 lambda表示式內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com