C++ Lambda 學習筆記

C++ 中,對於一個對象或一個表達式,如果可以對其使用調用運算符(()),則稱它是可調用的。即,如果 e 是可調用的,則可以這樣使用:

e(args)

其中,args 是一個逗號分隔的一個或多個參數的列表。

C++ 中可調用對象除了我們熟悉的函數或函數指針外,還包括函數對象以及 lambda 表達式。

本文重點講述 lambda 表達式。

lambda 表達式表示一個可調用的代碼單元,我們可以將其理解爲一個未命名的內聯函數。一個 lambda 表達式具有如下形式:

[capture list](parameter list) -> return type {function body}
  • capture list:捕獲列表,lambda 表達式所在函數中定義的局部變量列表
  • parameter list:參數列表
  • return type:返回類型
  • function body:函數體

lambda 表達式可以忽略參數列表和返回類型,但必須包含捕獲列表和函數體:

auto f = [] {return 10;};

上述代碼中,我們定義了一個可調用對象 f,它不接受參數,返回 42。

lambda 的調用方式與普通函數的調用方式一樣:

cout << f() << endl;

輸出:

10

向 lambda 傳遞參數

lambda 可以帶上參數,例如:

[](const string &s1, const string &s2) {
     return s1.size() < s2.size();
};

上述定義的 lambda 帶上兩個 const string & 類型的參數,lambda 函數體比較兩個 string 的長度並返回 bool 值。

可以使用此 lambda 來調用 std::stable_sort 排序。當 std::stable_sort 需要比較兩個元素時,它就會調用指定的這個 lambda。

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;


int main() {
    vector<string> words{"leehao.me", "lee", "hao", "leehao"};
    std::stable_sort(words.begin(), words.end(),
                     [](const string &s1, const string &s2) {
                         return s1.size() < s2.size();
                     });

    for (const auto &word : words) {
        cout << word << endl;
    }

    return 0;
}

輸出:

lee
hao
leehao
leehao.me

lambda 捕獲列表

一個 lambda 可以使用其所在函數中的局部變量,但需要明確指明這些變量。lambda 捕獲列表可以用來聲明這些需要使用的局部變量。例如:

[sz](const string &s) {
    return s.size() >= sz;
};

由於此 lambda 捕獲 sz,因此,可以在函數體中使用 sz。如果未捕獲 sz,則會報編譯錯誤:

// 編譯錯誤:sz 未捕獲
[](const string &s) {
    return s.size() >= sz;
};

lambda 本質上是函數對象。當向一個函數參數傳遞一個 lambda 時,同時定義了一個新的函數對象類型並生成一個此類型的未命名對象。

因此,從 lambda 生成的函數對象類型包含此 lambda 所捕獲的變量的數據成員,且 lambda 的數據成員在 lambda 對象創建時被初始化。

值捕獲

類似參數傳遞,變量的捕獲方式可以是值或引用。與傳遞參數類似,採用值捕獲的前提是變量可以拷貝。與參數不同,被捕獲的變量的值是在 lambda 創建時拷貝,而不是調用時拷貝:

void fcn1()
{
    size_t v1 = 42;  // 局部變量
    // 將 v1 拷貝到名爲 f 的可調用對象
    auto f = [v1] {return v1;};
    v1 = 0;
    auto j = f();  // j 爲 42,f 保存了 lambda 創建時 v1 的拷貝,即 42
    cout << j << endl;
}

輸出:

42

由於被捕獲變量的值是在 lambda 創建時拷貝,因此,隨後對其修改不會影響到 lambda 內對應的值。

引用捕獲

定義 lamda 時可以採用引用方式捕獲變量:

void fcn2()
{
    size_t v1 = 42;  // 局部變量
    // 對象 f2 包含 v1 的引用
    auto f2 = [&v1] {return v1;};
    v1 = 0;
    auto j = f2();  // j 爲 0,f2 保存 v1 的引用,而非拷貝
    cout << j << endl;
}

輸出:

0

v1 前的 & 表示 v1 以引用方式捕獲。當我們在 lambda 函數體內使用此變量,實際上使用的是引用所綁定的對象。因此,當 v1 在修改後,對象 f2 引用的 v1 也發生修改。

當以引用方式捕獲一個變量時,必須保證在 lambda 執行時,變量是存在的。

lambda 返回類型

上述例子中,我們並沒有爲 lambda 指定返回類型,這是由於編譯器可以正常推斷出 lambda 的返回類型。

例如,使用標準庫算法 transform 和一個 lambda 來將整型向量中每個負數轉化爲其絕對值:

vector<int> vi {1, -2, 3, 2, -4, 5};
std::transform(vi.begin(), vi.end(), vi.begin(),
        [](int i) {return i < 0 ? -i : i;});

transform 第 4 個參數是一個 lambda。
lambda 函數體是單一的 return 語句,我們沒有指定返回類型,這是由於編譯器可以根據條件運算符來推斷出返回類型。

也可以指定 lambda 的返回類型:

vector<int> vi {1, -2, 3, 2, -4, 5};
std::transform(vi.begin(), vi.end(), vi.begin(),
        [](int i) -> int
        {if (i < 0) return -i; else return i;});

transform 第 4 個參數是一個 lambda,它的捕獲列表爲空,接受一個 int 參數,返回一個 int 值。

可以輸出 vi 元素的值:

for (auto i : vi) {
    cout << i <<  endl;
}

輸出:

1
2
3
2
4
5

參考資料

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