1. 程式人生 > 其它 >C++11——Lambda表示式

C++11——Lambda表示式

轉載來自:https://subingwen.cn/cpp/lambda/

1. 基本用法
lambda 表示式是 C++11 最重要也是最常用的特性之一,這是現代程式語言的一個特點,lambda 表示式有如下的一些優點:

宣告式的程式設計風格:就地匿名定義目標函式或函式物件,不需要額外寫一個命名函式或函式物件。
簡潔:避免了程式碼膨脹和功能分散,讓開發更加高效。
在需要的時間和地點實現功能閉包,使程式更加靈活。
lambda 表示式定義了一個匿名函式,並且可以捕獲一定範圍內的變數。lambda 表示式的語法形式簡單歸納如下:

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


其中 capture 是捕獲列表,params 是引數列表,opt 是函式選項,ret 是返回值型別,body 是函式體。

捕獲列表 []: 捕獲一定範圍內的變數

引數列表 (): 和普通函式的引數列表一樣,如果沒有引數引數列表可以省略不寫。

auto f = [](){return 1;} // 沒有引數, 引數列表為空
auto f = []{return 1;} // 沒有引數, 引數列表省略不寫

opt 選項, 不需要可以省略

mutable: 可以修改按值傳遞進來的拷貝(注意是能修改拷貝,而不是值本身)
exception: 指定函式丟擲的異常,如丟擲整數型別的異常,可以使用 throw ();


返回值型別:在 C++11 中,lambda 表示式的返回值是通過返回值後置語法來定義的。

函式體:函式的實現,這部分不能省略,但函式體可以為空。

2. 捕獲列表

lambda 表示式的捕獲列表可以捕獲一定範圍內的變數,具體使用方式如下:

[] - 不捕捉任何變數
[&] - 捕獲外部作用域中所有變數,並作為引用在函式體內使用 (按引用捕獲)
[=] - 捕獲外部作用域中所有變數,並作為副本在函式體內使用 (按值捕獲)
拷貝的副本在匿名函式體內部是隻讀的
[=, &foo] - 按值捕獲外部作用域中所有變數,並按照引用捕獲外部變數 foo
[bar] - 按值捕獲 bar 變數,同時不捕獲其他變數


[&bar] - 按引用捕獲 bar 變數,同時不捕獲其他變數
[this] - 捕獲當前類中的 this 指標

讓 lambda 表示式擁有和當前類成員函式同樣的訪問許可權
如果已經使用了 & 或者 =, 預設新增此選項
下面通過一個例子,看一下初始化列表的具體用法:

// ConsoleApplication1.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//

#include <iostream>
#include <functional>
using namespace std;

class Test
{
public:
    void output(int x, int y)
    {
        auto x1 = [] {return m_number; }; // error
        auto x2 = [=] {return m_number + x + y; }; // ok
        auto x3 = [&] {return m_number + x + y; }; // ok
        auto x4 = [this] {return m_number; }; // ok
        auto x5 = [this] {return m_number + x + y; }; // error
        auto x6 = [this, x, y] {return m_number + x + y; }; // ok
        auto x7 = [this] {return m_number++; }; // ok
    }
    int m_number = 100;
};
int main() {
    return 0;
}

x1:錯誤,沒有捕獲外部變數,不能使用類成員 m_number
x2:正確,以值拷貝的方式捕獲所有外部變數
x3:正確,以引用的方式捕獲所有外部變數
x4:正確,捕獲 this 指標,可訪問物件內部成員
x5:錯誤,捕獲 this 指標,可訪問類內部成員,沒有捕獲到變數 x,y,因此不能訪問。
x6:正確,捕獲 this 指標,x,y
x7:正確,捕獲 this 指標,並且可以修改物件內部變數的值

int main(void)
{
    int a = 10, b = 20;
    auto f1 = [] {return a; }; // error
    auto f2 = [&] {return a++; }; // ok
    auto f3 = [=] {return a; }; // ok
    auto f4 = [=] {return a++; }; // error
    auto f5 = [a] {return a + b; }; // error
    auto f6 = [a, &b] {return a + (b++); }; // ok
    auto f7 = [=, &b] {return a + (b++); }; // ok

    return 0;
}

f1:錯誤,沒有捕獲外部變數,因此無法訪問變數 a
f2:正確,使用引用的方式捕獲外部變數,可讀寫
f3:正確,使用值拷貝的方式捕獲外部變數,可讀
f4:錯誤,使用值拷貝的方式捕獲外部變數,可讀不能寫
f5:錯誤,使用拷貝的方式捕獲了外部變數 a,沒有捕獲外部變數 b,因此無法訪問變數 b
f6:正確,使用拷貝的方式捕獲了外部變數 a,只讀,使用引用的方式捕獲外部變數 b,可讀寫
f7:正確,使用值拷貝的方式捕獲所有外部變數以及 b 的引用,b 可讀寫,其他只讀
在匿名函式內部,需要通過 lambda 表示式的捕獲列表控制如何捕獲外部變數,以及訪問哪些變數。預設狀態下 lambda 表示式無法修改通過複製方式捕獲外部變數,如果希望修改這些外部變數,需要通過引用的方式進行捕獲。

3. 返回值
很多時候,lambda 表示式的返回值是非常明顯的因此在 C++11 中允許省略 lambda 表示式的返回值。

// 完整的lambda表示式定義
auto f = [](int a) -> int
{
    return a + 10;
};

// 忽略返回值的lambda表示式定義
auto f = [](int a)
{
    return a + 10;
};

一般情況下,不指定 lambda 表示式的返回值,編譯器會根據 return 語句自動推導返回值的型別,但需要注意的是 labmda表示式不能通過列表初始化自動推匯出返回值型別。

// ok,可以自動推匯出返回值型別
auto f = [](int i)
{
    return i;
}

// error,不能推匯出返回值型別
auto f1 = []()
{
    return { 1, 2 }; // 基於列表初始化推導返回值,錯誤
}

4. 函式本質
使用 lambda 表示式捕獲列表捕獲外部變數,如果希望去修改按值捕獲的外部變數,那麼應該如何處理呢?這就需要使用 mutable 選項,被mutable修改是lambda表示式就算沒有引數也要寫明引數列表,並且可以去掉按值捕獲的外部變數的只讀(const)屬性。

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


最後再剖析一下為什麼通過值拷貝的方式捕獲的外部變數是隻讀的:

lambda表示式的型別在C++11中會被看做是一個帶operator()的類,即仿函式。
按照C++標準,lambda表示式的operator()預設是const的,一個const成員函式是無法修改成員變數值的。
mutable 選項的作用就在於取消 operator () 的 const 屬性。

因為 lambda 表示式在 C++ 中會被看做是一個仿函式,因此可以使用std::function和std::bind來儲存和操作lambda表示式:

#include <iostream>
#include <functional>
using namespace std;

int main(void)
{
    // 包裝可呼叫函式
    std::function<int(int)> f1 = [](int a) {return a; };
    // 繫結可呼叫函式
    std::function<int(int)> f2 = bind([](int a) {return a; }, placeholders::_1);

    // 函式呼叫
    cout << f1(100) << endl;
    cout << f2(200) << endl;
    return 0;
}

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

using func_ptr = int(*)(int);
// 沒有捕獲任何外部變數的匿名函式
func_ptr f = [](int a)
{
    return a;
};
// 函式呼叫
f(1314);