C++ lambda 匿名函數(shù)深入解析
1、基本介紹
C++11 引入的 lambda 匿名函數(shù)(Lambda Expression)是一種輕量級的函數(shù)對象,可在需要函數(shù)的地方直接定義,無需單獨(dú)聲明,極大簡化了代碼編寫(尤其是回調(diào)函數(shù)、算法謂詞等場景)。
基本語法:
[capture-list] (parameter-list) mutable noexcept(optional) -> return-type { function-body }| 組成部分 | 說明 |
|---|---|
capture-list | 捕獲列表:指定如何捕獲 lambda 所在作用域的局部變量(值捕獲、引用捕獲等),不可省略。 |
parameter-list | 參數(shù)列表:與普通函數(shù)的參數(shù)列表一致(可省略,若無形參)。 |
mutable | 可選關(guān)鍵字:允許在 lambda 內(nèi)部修改值捕獲的變量(默認(rèn)值捕獲變量為 const)。 |
noexcept | 可選:指定 lambda 是否可能拋出異常(C++11 起)。 |
-> return-type | 返回類型:可選,若函數(shù)體僅有一條 return 語句,編譯器可自動推導(dǎo)返回類型。 |
function-body | 函數(shù)體:lambda 的執(zhí)行邏輯。 |
簡單的例子
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// 使用 Lambda 表達(dá)式作為 std::sort 的比較準(zhǔn)則
// 按降序排序
std::sort(v.begin(), v.end(),
[](int a, int b) { return a > b; } // Lambda 表達(dá)式
);
for (int i : v) {
std::cout << i << " ";
}
// 輸出: 9 6 5 4 3 2 1 1
return 0;
}2、捕獲列表
捕獲列表定義了 Lambda 表達(dá)式如何從其所在的作用域中訪問外部變量。
2.1 值捕獲
將外部變量的值拷貝到 Lambda 中。Lambda 內(nèi)部修改不會影響外部變量。
int main() {
int x = 10;
int y = 20;
// 值捕獲:將 x 和 y 的當(dāng)前值拷貝到 Lambda 中
auto lambda_val = [x, y]() {
std::cout << "Inside lambda (by value): " << x << ", " << y << std::endl;
// x++; // 錯誤!默認(rèn)情況下,值捕獲的變量是 const 的。
};
x = y = 100; // 修改外部變量
lambda_val(); // 調(diào)用 Lambda
// 輸出: Inside lambda (by value): 10, 20
return 0;
}2.2 引用捕獲
捕獲外部變量的引用。Lambda 內(nèi)部修改會直接影響外部變量。
int main() {
int x = 10;
int y = 20;
// 引用捕獲:捕獲 x 和 y 的引用
auto lambda_ref = [&x, &y]() {
std::cout << "Inside lambda (by ref): " << x << ", " << y << std::endl;
x++; y++; // 修改會影響外部變量
};
lambda_ref(); // 調(diào)用 Lambda
std::cout << "After lambda: " << x << ", " << y << std::endl;
// 輸出:
// Inside lambda (by ref): 10, 20
// After lambda: 11, 21
return 0;
}2.3 隱式捕獲
讓編譯器根據(jù) Lambda 體內(nèi)的代碼自動推斷需要捕獲哪些變量。
[=]:以值捕獲的方式捕獲所有使用到的外部變量。[&]:以引用捕獲的方式捕獲所有使用到的外部變量。
int a = 1, b = 2, c = 3;
// 隱式值捕獲:自動捕獲所有使用到的外部變量 (a, b)
auto lambda1 = [=]() { std::cout << a + b << std::endl; };
// 注意:c 沒有被使用,所以不會被捕獲
// 隱式引用捕獲:自動捕獲所有使用到的外部變量 (a, c)
auto lambda2 = [&]() { std::cout << a + c << std::endl; c = 100; }; 注意:應(yīng)謹(jǐn)慎使用隱式捕獲,尤其是 [&],因為它可能讓你無意中修改外部變量或引入懸空引用。
2.4 混合捕獲
int a = 1, b = 2, c = 3, d = 4;
// a 顯式值捕獲,b 顯式引用捕獲,其他使用到的變量按值捕獲(但這里沒有其他變量了)
auto lambda1 = [=, &b]() { /* a by value, b by ref */ };
// a 顯式引用捕獲,b 顯式值捕獲,其他使用到的變量按引用捕獲(但這里沒有其他變量了)
auto lambda2 = [&, b]() { /* a by ref, b by value */ };
// 錯誤!不能混合相同的捕獲模式: [=, a] 或 [&, &b]2.5 捕獲 this 指針
在類的成員函數(shù)中,Lambda 可以通過值 [this] 或引用 [&] 捕獲 this 指針,從而訪問類的成員變量和函數(shù)。
class MyClass {
public:
void doSomething() {
// 捕獲 this,從而可以訪問成員變量 value
auto lambda = [this]() {
std::cout << "Value: " << value << std::endl;
memberFunction();
};
lambda();
}
private:
int value = 42;
void memberFunction() { std::cout << "Member func called\n"; }
};2.6 初始化捕獲(C++14)
允許在捕獲時初始化變量(類似變量聲明),解決 “移動捕獲” 等場景
#include <vector>
#include <utility> // for std::move
int main() {
vector<int> v = {1, 2, 3};
// 初始化捕獲:將v移動到lambda內(nèi)部的vec(避免拷貝大容器)
auto func = [vec = move(v)] {
cout << "vec size: " << vec.size() << endl;
};
func(); // 輸出:vec size: 3
// cout << v.size() << endl; // 錯誤:v已被移動,處于無效狀態(tài)
return 0;
}3、mutable 關(guān)鍵字
默認(rèn)情況下,對于值捕獲的變量,Lambda 的 operator() 是 const 的,這意味著你不能在 Lambda 體內(nèi)修改這些拷貝。
使用 mutable 關(guān)鍵字可以移除這個 const 限制。
int main() {
int count = 0;
// 沒有 mutable: 錯誤!不能修改值捕獲的變量。
// auto lambda = [count]() { count++; };
// 使用 mutable
auto lambda = [count]() mutable {
count++; // 現(xiàn)在可以修改了
std::cout << "Count inside lambda: " << count << std::endl;
};
lambda(); // 輸出: Count inside lambda: 1
lambda(); // 輸出: Count inside lambda: 2
std::cout << "Count outside: " << count << std::endl; // 輸出: Count outside: 0
// 注意:修改的是 Lambda 內(nèi)部的副本,不影響外部變量。
return 0;
}重要:mutable 允許你修改的是 Lambda 內(nèi)部副本的值,對外部變量毫無影響。引用捕獲不需要 mutable。
4、返回類型
編譯器通??梢宰詣油茖?dǎo) Lambda 的返回類型。但如果函數(shù)體中有多個返回語句且類型不同,或者你想要更明確的代碼,可以顯式指定。
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 編譯器自動推導(dǎo)返回類型為 bool
auto is_even = [](int n) { return n % 2 == 0; };
// 顯式指定返回類型為 double (使用尾置返回類型語法)
auto divide = [](int a, int b) -> double {
if (b == 0) {
return 0.0; // 返回 double
}
return static_cast<double>(a) / b; // 返回 double
};5、常見用法與示例
5.1 與 STL 算法結(jié)合
std::vector<int> vec = {5, 3, 8, 1, 9};
// 計算大于 5 的元素數(shù)量
int count = std::count_if(vec.begin(), vec.end(),
[](int n) { return n > 5; });
// 將所有元素翻倍
std::for_each(vec.begin(), vec.end(),
[](int& n) { n *= 2; }); // 注意:需要引用才能修改原值
vector<int> nums = {3, 1, 4, 1, 5, 9};
// 用lambda作為sort的比較函數(shù)(降序排序)
sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b;
}); // nums變?yōu)椋?,5,4,3,1,1
// 用lambda作為find_if的條件(查找偶數(shù))
auto it = find_if(nums.begin(), nums.end(), [](int x) {
return x % 2 == 0;
});
if (it != nums.end()) {
cout << "找到偶數(shù):" << *it << endl; // 輸出:4
}5.2 并發(fā)編程中的任務(wù)
線程或異步任務(wù)(std::thread、std::async)需要執(zhí)行函數(shù),lambda 可直接定義任務(wù)邏輯
#include <thread>
#include <future>
int main() {
// 線程任務(wù)用lambda定義
thread t([] {
cout << "線程執(zhí)行中..." << endl;
});
t.join();
// 異步任務(wù)用lambda定義
future<int> fut = async([] {
return 1 + 2;
});
cout << "異步結(jié)果:" << fut.get() << endl; // 輸出:3
return 0;
}5.3 自定義比較器
std::map<std::string, int> name_age;
// 按值(年齡)排序,而不是鍵(名字)
std::vector<std::pair<std::string, int>> vec(name_age.begin(), name_age.end());
std::sort(vec.begin(), vec.end(),
[](const auto& a, const auto& b) { return a.second < b.second; });6、常見問題
1. Lambda 表達(dá)式中的捕獲列表 [=] 和 [&] 有什么區(qū)別?[=] 表示隱式值捕獲,Lambda 體內(nèi)使用的所有外部變量都會將其當(dāng)前值拷貝一份到 Lambda 對象中。[&] 表示隱式引用捕獲,Lambda 體內(nèi)使用的所有外部變量都會以其引用被捕獲,在 Lambda 內(nèi)部修改它們會影響外部變量。應(yīng)謹(jǐn)慎使用 [&],以免造成意外的副作用或懸空引用。
2. 什么是“初始化捕獲”(Init Capture)?它解決什么問題?初始化捕獲(C++14)允許在捕獲列表中直接初始化一個新的成員變量。它主要解決了移動捕獲的問題。例如,你不能用普通捕獲移動一個 std::unique_ptr(因為無法拷貝),但可以用 [p = std::move(unique_ptr)] 將其所有權(quán)移動到 Lambda 內(nèi)部。它也允許你以任意表達(dá)式初始化捕獲的變量。
3. mutable 關(guān)鍵字在 Lambda 中起什么作用?默認(rèn)情況下,對于值捕獲的變量,Lambda 的函數(shù)調(diào)用運(yùn)算符 (operator()) 是 const 的,這意味著你不能修改這些捕獲的副本。mutable 關(guān)鍵字移除了這個 const 限制,允許你修改 Lambda 內(nèi)部的值捕獲變量。需要注意的是,這修改的只是副本,不影響外部原始變量。
4. Lambda 表達(dá)式的類型是什么?如何存儲或傳遞一個 Lambda?每個 Lambda 表達(dá)式都會生成一個唯一的、編譯器生成的、未命名的類型(閉包類型)。存儲和傳遞它的最佳方式是:
- 使用
auto進(jìn)行初始化(auto lambda = [...](){...};)。 - 使用
std::function(如std::function<void()>),這會帶來一些類型擦除的開銷,但非常靈活。 - 在模板中使用(
template<typename F> void foo(F func)),這是零開銷的方式。
5. 在類的成員函數(shù)中,Lambda 如何訪問類的成員變量?需要通過捕獲 this 指針。使用 [this] 或 [&](隱式捕獲)可以捕獲當(dāng)前對象的 this 指針,從而在 Lambda 內(nèi)部訪問類的成員變量和成員函數(shù)。需要注意的是,如果 Lambda 的生命周期可能比對象更長(例如,被放入一個全局隊列),這會導(dǎo)致懸空 this 指針。C++17 的 [\*this] 可以按值捕獲整個對象的副本,避免這個問題。
到此這篇關(guān)于C++ lambda 匿名函數(shù)深入解析的文章就介紹到這了,更多相關(guān)C++ lambda 匿名函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
TortoiseSVN忽略(Global?ignore)提交文件設(shè)定方式
文章介紹兩種SVN忽略雜碎文件的方法:本地配置忽略規(guī)則(修改config文件中的global-ignores參數(shù))和服務(wù)器端設(shè)置(通過svn:ignore屬性),方法一適合個人,方法二需管理員權(quán)限,均需注意空格格式避免報錯2025-08-08
Memcached簡介_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Memcached簡介,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08
在mac上安裝虛擬機(jī)搭載Windows服務(wù)的方法
這篇文章主要介紹了在mac上安裝虛擬機(jī)搭載Windows服務(wù)的方法,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-12-12
curl.exe安裝使用的最全參數(shù)詳解以及常用命令匯總
Curl是一個功能強(qiáng)大的命令行工具,可以看做是命令行瀏覽器,用于與服務(wù)器進(jìn)行數(shù)據(jù)交互,支持多種數(shù)據(jù)傳輸協(xié)議,如HTTP、HTTPS、FTP等,它支持文件的上傳和下載,它是一款開源軟件,在多個操作系統(tǒng)上均可運(yùn)行,包括Windows、Linux、macOS等2024-04-04
華為服務(wù)器RAID陣列卡配置教程 SR430 LSISAS3108(EFI/UEFI模式)
最近采購了華為服務(wù)器的服務(wù)器第一次做陣列,沒想到使用了以后發(fā)現(xiàn)與dell的陣列卡配置差不多,這里就為大家分享一下SR430 LSISAS3108EFI/UEFI模式下陣列卡的配置方法2025-02-02

