C++11 lambda表達式與包裝器function與bind詳解
一、lambda表達式
1.1 概念
C++11 引入的 Lambda 表達式是一種就地定義的匿名函數(shù)對象。它允許你在需要函數(shù)的地方直接寫一段邏輯,而不必提前定義一個命名函數(shù)或手寫一個完整的仿函數(shù)類。
在學(xué)習(xí)lambda表達式之前,我們的使用的可調(diào)用對象只有函數(shù)指針和仿函數(shù)對象,函數(shù)指針的類型定義起來比較麻煩,仿函數(shù)要定義一個類(重載()),相對會比較麻煩。使用lambda去定義可調(diào)用對象,既簡單又方便。
與普通函數(shù)不同,lambda表達式可以在函數(shù)內(nèi)部實現(xiàn)。
語法格式:
[捕捉列表](參數(shù))-> return type {函數(shù)體}舉個簡單的例子:
auto f = [](int a, int b) -> int { return a + b; };
cout << f(2, 3) << endl;
說明:
- 開頭的 [ ]必須要寫,因為編譯器根據(jù)[ ]來判別其為lambda表達式;
- ()即為參數(shù)列表,如果不需要參數(shù),()可以省略;
- -> return type:返回值類型,也可以省略,編譯器會自動推導(dǎo);
- { }即函數(shù)體,與普通函數(shù)一樣。
1.2 捕捉列表
顧名思義,就是將函數(shù)體中會用到的變量,對象等進行抓取,使得其在函數(shù)體內(nèi)部可用。
void test()
{
int a = 2, b = 3;
auto Add = [a, b]() {return a + b; };
cout << Add() << endl; // 調(diào)用
}那么捕捉有什么講究呢?具體有四種捕捉方式:
- 值捕捉
- 我們在上面直接將變量名或?qū)ο笤诓蹲搅斜聿蹲?,就是值捕捉?/li>
- 引用捕捉
- 引用捕捉就是在變量前加
&。
- 引用捕捉就是在變量前加
void test()
{
int a = 2, b = 3;
auto Add = [&a, &b]() {return a + b; };
cout << Add() << endl; // 調(diào)用
}
- 顯示捕捉
- 我們在捕捉列表,直接寫明要捕捉的變量或?qū)ο缶褪秋@示捕捉。顯示捕捉時既可以值捕捉,也可以引用捕捉,同樣也可以在一個捕捉列表中同時出現(xiàn),值捕捉和引用捕捉。
- 隱式捕捉
- 隱式捕捉有隱式值捕捉,即直接在捕捉列表中寫一個 =;隱式引用捕捉,即在捕捉列表直接寫一個 &。
需要注意:
在用到隱式捕捉時,= 或 & 必須寫在捕捉列表最前面,兩者不能同時出現(xiàn)。如果此時想混合捕捉,且前面已經(jīng)是 =,那么后面必須全部是引用捕捉;同理,如果前面已經(jīng)是 &,那么后面必須全部是值捕捉。
void test()
{
int a = 2, b = 3, c = 4;
auto Add1 = [=]() {return a + b + c; }; // 隱式值捕捉
auto Add2 = [&]() {return a + b + c; }; // 隱式引用捕捉
// 混合捕捉
auto Add3 = [=, &b]() {return a + b + c; };
auto Add4 = [&, c]() {return a + b + c; };
// 錯誤示范:隱式值捕捉,后面又顯示值捕捉(重復(fù))
// auto Add2 = [=, b, c]() {return a + b + c; };
}
細(xì)節(jié)補充:
默認(rèn)情況下,值捕捉是被const修飾的,也就是說值捕捉的過來的對象不能修改,mutable加在參數(shù)列表的后面可以取消其常量性,也就說使用該修飾符后,值捕捉的對象就可以修改了,但是修改還是形參對象,不會影響實參。使用該修飾符后,參數(shù)列表不可省略(即使參數(shù)為空)。
引用捕捉的對象可以修改。
int main()
{
int a = 1, b = 1, c = 1, d = 1;
auto f = [a, b, &c, &d]() {
// a++; // 報錯
// b++; // 報錯
c++;
d++;
};
f(); // 調(diào)用
cout << "c = " << c << ", d = " << d << endl;
return 0;
}
int main()
{
int a = 1, b = 1, c = 1, d = 1;
auto f = [a, b, &c, &d]() mutable {
a++;
b++;
return a + b;
};
cout << f() << endl; // 調(diào)用
// 并不改變實參
cout << "a = " << a << ", b = " << b << endl;
return 0;
}
對于全局變量和靜態(tài)局部變量不需要捕捉,lambda表達式中可以直接使用。這也意味著lambda表達式如果定義在全局位置,捕捉列表必須為空。
1.3 lambda的應(yīng)用
對于需要自定義排序的類對象,我們不再需要重載operator(),而是直接用lambda表達式。
#include <vector>
#include <algorithm>
#include <string>
struct Product
{
std::string _name;
double _price;
int _stock;
Product(std::string name, double price, int stock)
:_name(name),
_price(price),
_stock(stock)
{ }
};
struct Compare
{
bool operator()(const Product& p1, const Product& p2)
{
return p1._price < p2._price;
}
};
int main()
{
std::vector<Product> products = {
{"iPhone", 5999.0, 50},
{"MacBook", 12999.0, 20},
{"AirPods", 1299.0, 200}
};
// 重載operator()
std::sort(products.begin(), products.end(), Compare());
// lambda表達式:按價格降序
std::sort(products.begin(), products.end(),
[](const Product& a, const Product& b) {
return a._price > b._price;
});
return 0;
}1.4 lambda原理
其實就是編譯器在底層將我們寫的lambda表達式轉(zhuǎn)換成了一個仿函數(shù),實際上并沒有所謂的lambda表達式。
auto f = [x, &y](int a) -> int { return x + a + y; };
// 上面的 Lambda,編譯器會翻譯成類似下面的類:
class __lambda_unique_id { // 編譯器生成的唯一類名
int x; // 值捕獲的成員(拷貝)
int& y; // 引用捕獲的成員(引用)
public:
__lambda_unique_id(int _x, int& _y) : x(_x), y(_y) {}
int operator()(int a) const { // 函數(shù)調(diào)用運算符
return x + a + y;
}
};
// 實際調(diào)用時:
// auto f = __lambda_unique_id(x, y);
// f(5); // 等價于 f.operator()(5);二、包裝器
2.1 function
什么是function,就是將具有相同類型返回值,參數(shù)列表的可調(diào)用對象進行包裝,讓可調(diào)用對象對外呈現(xiàn)一種類型,其頭文件為 <functional>??烧{(diào)用對象有4個:函數(shù)指針,仿函數(shù),lambda和bind,bind是什么我們下面介紹?。。?/p>
四種可調(diào)用對象,四種完全不同的類型:
int plain_func(int, int) { return 0; } // 函數(shù)指針類型:int(*)(int,int)
struct Functor { int operator()(int, int) {} }; // 仿函數(shù)類型:Functor
auto lambda = [](int, int) { return 0; }; // lambda類型:編譯器生成的匿名類
auto bound = std::bind(plain_func, _1, _2); // bind類型:std::_Bind<...>
std::function可以包裝存儲這些對象,這些對象成為function的目標(biāo)。若std::function不含目標(biāo),則稱它為空。調(diào)用空std::function的目標(biāo)就會導(dǎo)致拋出std::bad_function_call異常。
function的用法:
std::function<返回類型(參數(shù)類型列表)> f;
所以對于上面的四個可調(diào)用對象,就可以進行統(tǒng)一包裝:
std::function<int(int,int)> f1 = plain_func; // ? std::function<int(int,int)> f2 = Functor(); // ? std::function<int(int,int)> f3 = lambda; // ? std::function<int(int,int)> f4 = bound; // ?
應(yīng)用場景:
用function對象調(diào)用成員函數(shù):
痛點:由于成員函數(shù)第一個參數(shù)默認(rèn)為this指針,當(dāng)我們將成員函數(shù)直接賦值給function對象就會報錯。
解決:lambda捕捉this。
class A
{
public:
int Add(int a, int b)
{
return a + b;
}
};
int main()
{
A a;
// 想要調(diào)用成員函數(shù)
// func_t func = a.Add(2, 3); // 報錯
// 解決:
std::function<int(int, int)> func = [&a](int x, int y) {
return a.Add(x, y); };
cout << func(2, 3) << endl;
return 0;
}2.2 bind
std::bind也是一個可調(diào)用對象的包裝器,相當(dāng)于函數(shù)適配器,其頭文件也是 <functional>。有什么特點呢?
它解決的核心問題:固定部分參數(shù),留下部分參數(shù)延遲傳入。
bind用法:
auto newCall = bind(Call, arg_list); // 如果想要調(diào)用類A中的成員函數(shù)Call auto newCall = bind(&A::Call, arg_list); // 注意:格式必須是&類名::成員函數(shù)名
其中newCall本身是一個可調(diào)用對象,arg_list是一個逗號分隔的參數(shù)列表,對應(yīng)給定的Call的參數(shù)。當(dāng)我們調(diào)用newCall時,newCall會調(diào)用Call,并傳給它arg_list中的參數(shù)。
為什么說bind支持固定參數(shù)呢?
arg_list中的參數(shù)可能包含形如_n的名字,其中n是一個整數(shù),這些參數(shù)是占位符,表示newCall的參數(shù),它們占據(jù)了傳遞給newCall的參數(shù)的位置。數(shù)值n表示生成的可調(diào)用對象中參數(shù)的位置:_1為Call的第一個參數(shù),_2為第二個參數(shù),以此類推。_1/_2/_3…這些占位符放到placeholders的一個命名空間中。
int Sub(int x, int y) { return (x - y) * 10; }
void test01()
{
auto f = bind(Sub, _1, _2);
cout << f(2, 3) << endl;
}
void test02()
{
auto f = bind(Sub, _2, _1); // 調(diào)整參數(shù)順序
cout << f(2, 3) << endl;
}
常用場景一:調(diào)整參數(shù)個數(shù)(綁定個別參數(shù))
此時,對于一些固定不變的參數(shù),就不需要再傳了?。?!
void test03()
{
auto f1 = bind(Sub, 5, _1);
cout << f1(3) << endl;
auto f2 = bind(Sub, _1, 5);
cout << f2(3) << endl;
}常用場景二:成員函數(shù)調(diào)用
struct A
{
int Sub(int a, int b) { return (a - b) * 10; }
};
void Test()
{
A a;
auto f1 = bind(&A::Sub, &a, _1, _2);
// function包裝
std::function<int(int, int)> f2 = bind(&A::Sub, &a, _1, _2);
cout << f1(2, 3) << endl;
cout << f2(5, 2) << endl;
}std::bind 綁定成員函數(shù)時,把對象指針/引用作為第一個參數(shù)傳入,就填上了 this 的位置,生成的可調(diào)用對象不再需要外部提供 this,編譯器視角是 返回類型(類名, 參數(shù)列表)*,同樣可以解決我們調(diào)用成員函數(shù)時this指針的問題。
這些特性在未來寫代碼的過程中其實非常常見,也很實用,希望能夠幫助到大家,上面其實就有我所遇到的場景,如果再有什么實用的場景我也會及時地補充。
到此這篇關(guān)于C++11 lambda表達式與包裝器function與bind詳解的文章就介紹到這了,更多相關(guān)C++11 lambda表達式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C++11中l(wèi)ambda、std::function和std:bind詳解
- C++11?lambda(匿名函數(shù))表達式詳細(xì)介紹
- 淺談C++11新引入的lambda表達式
- 淺析C++11新特性的Lambda表達式
- 結(jié)合C++11新特性來學(xué)習(xí)C++中l(wèi)ambda表達式的用法
- C++11中bind綁定器和function函數(shù)對象介紹
- C++11 中的std::function和std::bind詳解
- C++11 學(xué)習(xí)筆記之std::function和bind綁定器
- c++11 符號修飾與函數(shù)簽名、函數(shù)指針、匿名函數(shù)、仿函數(shù)、std::function與std::bind
相關(guān)文章
Windows配置VSCode+CMake+Ninja+Boost.Test的C++開發(fā)環(huán)境(教程詳解)
這篇文章主要介紹了Windows配置VSCode+CMake+Ninja+Boost.Test的C++開發(fā)環(huán)境,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05
在Ubuntu中安裝VSCode并配置C/C++開發(fā)環(huán)境的方法步驟
這篇文章主要介紹了在Ubuntu中安裝VSCode并配置C/C++開發(fā)環(huán)境的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
C++調(diào)用python(執(zhí)行py文件)的全過程
這篇文章主要給大家介紹了關(guān)于C++調(diào)用python(執(zhí)行py文件)的相關(guān)資料,文中通過圖文以及實例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2021-12-12
VC++植物大戰(zhàn)僵尸中文版修改器實現(xiàn)代碼
這篇文章主要介紹了VC++植物大戰(zhàn)僵尸中文版修改器實現(xiàn)代碼,可實現(xiàn)植物大戰(zhàn)僵尸中的無限陽光與無冷卻時間功能,需要的朋友可以參考下2015-04-04
C++版本基于ros將文件夾中的圖像轉(zhuǎn)換為bag包
這篇文章主要介紹了C++版本基于ros將文件夾中的圖像轉(zhuǎn)換為bag包,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-01-01

