第 18.11 節
Lambda 表達式
0瀏覽次數0訪問次數--跳出率--平均停留
本節解決什麼問題
很多地方需要傳入一小段邏輯:排序規則、過濾條件、按鈕回調、定時器回調。舊寫法通常有三種:
- 寫一個普通函數。
- 寫一個函數指針。
- 寫一個函數對象,也就是帶
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:
constexprLambda、[*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 用按值捕獲,因為離開內部作用域後,name 和 score 已經銷燬。
關鍵語法解釋
| 示例 | 重點 | 說明 |
|---|---|---|
| 示例 1 | 舊回調寫法對比 | 普通函數、函數對象都能做回調,但 lambda 更適合局部短邏輯 |
| 示例 2 | 參數和返回值 | 返回類型通常可推導,分支返回不同類型時要顯式寫清楚 |
| 示例 3 | 捕獲列表 | [x] 拷貝,[&x] 引用 |
| 示例 4 | mutable | 修改的是 lambda 內部副本,不影響外部變量 |
| 示例 5 | STL 算法 | sort、find_if、count_if 常和 lambda 配合 |
| 示例 6 | 生命週期 | 保存回調、異步回調、線程回調中不要隨便引用捕獲局部變量 |
常見錯誤
- 保存回調時使用默認引用捕獲
[&],導致局部變量銷燬後仍被訪問。 - 以為按值捕獲會跟著外部變量變化。按值捕獲保存的是定義 lambda 時的副本。
- 想修改按值捕獲的副本,卻忘記加
mutable。 - 把有捕獲的 lambda 當成函數指針使用。有捕獲的 lambda 需要用模板參數、
auto變量或std::function保存。 - 異步場景捕獲
this後,對象先被銷燬。需要保證對象生命週期,或使用智能指針、[*this]等更明確的方式。
使用建議
- 小而局部的邏輯優先用 lambda。
- 捕獲列表儘量顯式寫
[x, &y],少用默認[=]和[&]。 - 保存回調、線程、定時器、異步操作中優先按值捕獲需要的數據。
- 只調用一次、不需要保存的回調,可以直接把 lambda 傳給算法或函數模板。
- 需要統一保存不同 lambda 時,再使用下一節的
std::function。
小結
- Lambda 是匿名可調用對象,寫法是
[捕获](参数) { 函数体 }。 - 捕獲列表決定 lambda 如何使用外部變量。
[x]是按值捕獲,[&x]是按引用捕獲。mutable允許修改按值捕獲的內部副本。- Lambda 最常用於 STL 算法、回調和異步任務。
- 生命週期是 Lambda 最容易出錯的地方,尤其是保存回調和異步回調。