第 18.11 節

Lambda 表達式

0瀏覽次數0訪問次數--跳出率--平均停留

本節解決什麼問題

很多地方需要傳入一小段邏輯:排序規則、過濾條件、按鈕回調、定時器回調。舊寫法通常有三種:

  1. 寫一個普通函數。
  2. 寫一個函數指針。
  3. 寫一個函數對象,也就是帶 operator() 的類或結構體。

這些寫法都能工作,但小邏輯會被迫挪到遠處,或者要額外寫一個類型。Lambda 表達式讓你可以在使用現場直接寫一個小函數,而且可以捕獲外部變量。

這個特性是什麼

Lambda 是匿名可調用對象。它看起來像一個函數,但本質上是編譯器生成的一個類對象。

基本結構是:

部分例子含義
捕獲列表[x, &y]從外部作用域拿哪些變量
參數列表(int a, int b)調用 lambda 時傳入什麼
返回值-> int返回類型,通常可以省略
函數體{ return a + b; }真正執行的代碼

完整形式可以寫成 [x](int n) -> int { return x + n; },常見情況下返回值類型可以省略。

C++ 標準版本

  • C++11:基礎 Lambda。
  • C++14:泛型 Lambda,也就是參數可以寫 auto
  • C++17:constexpr Lambda、[*this] 捕獲。

Lambda 是語言特性,不需要額外頭文件。只有配合 STL 算法、std::function 等庫工具時,才需要包含對應頭文件。

捕獲列表速查

寫法含義適用場景
[]不捕獲外部變量只用參數或局部臨時變量
[x]按值捕獲 x保存一份副本,適合保存回調
[&x]按引用捕獲 x需要修改外部變量,且能保證生命週期
[x, &y]混合捕獲明確表達哪些拷貝、哪些引用
[=]默認按值捕獲用到的變量小例子方便,工程中不建議濫用
[&]默認按引用捕獲用到的變量異步或保存回調中尤其危險
[this]捕獲當前對象指針對象必須比 lambda 活得久
[*this]捕獲當前對象副本C++17 起可用,適合避免懸空 this

示例代碼

示例 1:舊回調寫法和 lambda 的區別

普通函數和函數對象都可以作為算法條件,但 Lambda 更適合寫簡短的局部邏輯。

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

bool is_even(int n)
{
    return n % 2 == 0;
}

struct GreaterThan
{
    int limit;

    bool operator()(int n) const
    {
        return n > limit;
    }
};

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // vector 是动态数组,元素数量可以在运行时变化。
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6};

    int even_count = std::count_if(numbers.begin(), numbers.end(), is_even);
    std::cout << "even count = " << even_count << "\n";

    int greater_count1 = std::count_if(numbers.begin(), numbers.end(), GreaterThan{3});
    std::cout << "> 3 count (functor) = " << greater_count1 << "\n";

    int limit = 3;
    int greater_count2 = std::count_if(numbers.begin(), numbers.end(),
                                       [limit](int n) {
                                           return n > limit;
                                       });
    std::cout << "> 3 count (lambda) = " << greater_count2 << "\n";

    return 0;
}

運行結果

even count = 3
> 3 count (functor) = 3
> 3 count (lambda) = 3

示例 2:最基本的 lambda 語法、參數和返回值

#include <iostream>
#include <string>

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    auto add = [](int a, int b) {
        return a + b;
    };

    auto describe_score = [](int score) -> std::string {
        if (score >= 60)
        {
            return "pass";
        }
        return "fail";
    };

    std::cout << "add(3, 5) = " << add(3, 5) << "\n";
    std::cout << "score 80 is " << describe_score(80) << "\n";
    std::cout << "score 40 is " << describe_score(40) << "\n";

    // 返回 0 表示程序正常结束。
    return 0;
}

運行結果

add(3, 5) = 8
score 80 is pass
score 40 is fail

示例 3:按值捕獲和按引用捕獲

按值捕獲會保存定義 lambda 時的副本;按引用捕獲會訪問外部變量本身。

#include <iostream>

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    int score = 10;

    auto add_by_value = [score](int bonus) {
        return score + bonus;
    };

    auto add_by_ref = [&score](int bonus) {
        score += bonus;
        return score;
    };

    score = 20;

    std::cout << "value capture result = " << add_by_value(5) << "\n";
    std::cout << "ref capture result = " << add_by_ref(5) << "\n";
    std::cout << "score after ref capture = " << score << "\n";

    // 返回 0 表示程序正常结束。
    return 0;
}

運行結果

value capture result = 15
ref capture result = 25
score after ref capture = 25

示例 4:mutable 允許修改按值捕獲的副本

按值捕獲的變量默認在 lambda 內是隻讀的。加上 mutable 後,可以修改 lambda 自己保存的副本,但不會修改外部變量。

#include <iostream>

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    int start = 0;

    auto counter = [start]() mutable {
        ++start;
        return start;
    };

    std::cout << "counter() = " << counter() << "\n";
    std::cout << "counter() = " << counter() << "\n";
    std::cout << "outside start = " << start << "\n";

    // 返回 0 表示程序正常结束。
    return 0;
}

運行結果

counter() = 1
counter() = 2
outside start = 0

示例 5:lambda 配合 STL 算法

Lambda 和 STL 算法配合時最常見:排序、查找、計數、轉換都可以把局部邏輯直接寫在調用處。

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

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // vector 是动态数组,元素数量可以在运行时变化。
    std::vector<std::string> names = {"Bob", "Alice", "Charlie", "David"};

    std::sort(names.begin(), names.end(),
              [](const std::string& a, const std::string& b) {
                  if (a.size() == b.size())
                  {
                      return a < b;
                  }
                  return a.size() < b.size();
              });

    std::cout << "sort by length: ";
    for (const auto& name : names)
    {
        std::cout << name << " ";
    }
    std::cout << "\n";

    int min_length = 6;
    auto it = std::find_if(names.begin(), names.end(),
                           [min_length](const std::string& name) {
                               return name.size() >= static_cast<std::size_t>(min_length);
                           });

    if (it != names.end())
    {
        std::cout << "first long name = " << *it << "\n";
    }

    return 0;
}

運行結果

sort by length: Bob Alice David Charlie
first long name = Charlie

示例 6:保存回調時要注意捕獲生命週期

如果 lambda 只是立刻調用,引用捕獲通常看起來沒問題;如果保存到容器、線程、定時器或異步回調裡,lambda 可能晚於局部變量執行。保存回調時優先按值捕獲需要的數據。

#include <functional>
#include <iostream>
#include <string>
#include <vector>

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // std::function 可以保存普通函数、lambda 或函数对象。
    // vector 是动态数组,元素数量可以在运行时变化。
    std::vector<std::function<void()>> callbacks;

    {
        std::string name = "Alice";
        int score = 95;

        auto print_now = [&name, &score]() {
            std::cout << "now: " << name << " " << score << "\n";
        };
        print_now();

        callbacks.push_back([name, score]() {
            std::cout << "saved: " << name << " " << score << "\n";
        });
    }

    for (const auto& callback : callbacks)
    {
        callback();
    }

    return 0;
}

運行結果

now: Alice 95
saved: Alice 95

這裡 print_now 立刻調用,所以引用捕獲沒問題。保存到 callbacks 的 lambda 用按值捕獲,因為離開內部作用域後,namescore 已經銷燬。

關鍵語法解釋

示例重點說明
示例 1舊回調寫法對比普通函數、函數對象都能做回調,但 lambda 更適合局部短邏輯
示例 2參數和返回值返回類型通常可推導,分支返回不同類型時要顯式寫清楚
示例 3捕獲列表[x] 拷貝,[&x] 引用
示例 4mutable修改的是 lambda 內部副本,不影響外部變量
示例 5STL 算法sortfind_ifcount_if 常和 lambda 配合
示例 6生命週期保存回調、異步回調、線程回調中不要隨便引用捕獲局部變量

常見錯誤

  1. 保存回調時使用默認引用捕獲 [&],導致局部變量銷燬後仍被訪問。
  2. 以為按值捕獲會跟著外部變量變化。按值捕獲保存的是定義 lambda 時的副本。
  3. 想修改按值捕獲的副本,卻忘記加 mutable
  4. 把有捕獲的 lambda 當成函數指針使用。有捕獲的 lambda 需要用模板參數、auto 變量或 std::function 保存。
  5. 異步場景捕獲 this 後,對象先被銷燬。需要保證對象生命週期,或使用智能指針、[*this] 等更明確的方式。

使用建議

  1. 小而局部的邏輯優先用 lambda。
  2. 捕獲列表儘量顯式寫 [x, &y],少用默認 [=][&]
  3. 保存回調、線程、定時器、異步操作中優先按值捕獲需要的數據。
  4. 只調用一次、不需要保存的回調,可以直接把 lambda 傳給算法或函數模板。
  5. 需要統一保存不同 lambda 時,再使用下一節的 std::function

小結

  • Lambda 是匿名可調用對象,寫法是 [捕获](参数) { 函数体 }
  • 捕獲列表決定 lambda 如何使用外部變量。
  • [x] 是按值捕獲,[&x] 是按引用捕獲。
  • mutable 允許修改按值捕獲的內部副本。
  • Lambda 最常用於 STL 算法、回調和異步任務。
  • 生命週期是 Lambda 最容易出錯的地方,尤其是保存回調和異步回調。
音乐页