一文讀懂c++11 Lambda表達(dá)式
1.簡介
1.1定義
C++11新增了很多特性,Lambda表達(dá)式(Lambda expression)就是其中之一,很多語言都提供了 Lambda 表達(dá)式,如 Python,Java ,C#等。本質(zhì)上, Lambda 表達(dá)式是一個可調(diào)用的代碼單元[1]^{[1]}[1]。實際上是一個閉包(closure),類似于一個匿名函數(shù),擁有捕獲所在作用域中變量的能力,能夠?qū)⒑瘮?shù)做為對象一樣使用,通常用來實現(xiàn)回調(diào)函數(shù)、代理等功能。Lambda表達(dá)式是函數(shù)式編程的基礎(chǔ),C++11引入了Lambda則彌補(bǔ)了C++在函數(shù)式編程方面的空缺。
1.2作用
以往C++需要傳入一個函數(shù)的時候,必須事先進(jìn)行聲明,視情況可以聲明為一個普通函數(shù)然后傳入函數(shù)指針,或者聲明一個仿函數(shù)(functor,函數(shù)對象),然后傳入一個對象。比如C++的STL中很多算法函數(shù)模板需要傳入謂詞(predicate)來作為判斷條件,如排序算法sort。謂詞就是一個可調(diào)用的表達(dá)式,其返回結(jié)果是一個能用作條件的值。標(biāo)準(zhǔn)庫算法所使用的謂詞分為兩類:一元謂詞(unary predicate,只接受單一參數(shù))和二元謂詞(binary predicate,接受兩個參數(shù))。接受謂詞的算法對輸入序列中的元素調(diào)用謂詞,因此元素類型必須能轉(zhuǎn)換為謂詞的參數(shù)類型。如下面使用sort()傳入比較函數(shù)shorter()(這里的比較函數(shù)shorter()就是謂詞)將字符串按長度由短至長排列。
//謂詞:比較函數(shù),用來按長度排列字符串 bool shorter(const string& s1,const string& s2) { return s1.size()<s2.size(); } //按長度由短至長排列words std::sort(words.begin(),words.end(),shorter);
Lambda表達(dá)式可以像函數(shù)指針、仿函數(shù)一樣,作為一個可調(diào)用對象(callable object)被使用,比如作為謂詞傳入標(biāo)準(zhǔn)庫算法。
也許有人會問,有了函數(shù)指針、函數(shù)對象為何還要引入Lambda呢?函數(shù)對象能維護(hù)狀態(tài),但語法開銷大,而函數(shù)指針語法開銷小,卻沒法保存函數(shù)體內(nèi)的狀態(tài)。如果你覺得魚和熊掌不可兼得,那你可錯了。Lambda函數(shù)結(jié)合了兩者的優(yōu)點(diǎn),讓你寫出優(yōu)雅簡潔的代碼。
1.3語法格式
Lambda 表達(dá)式就是一個可調(diào)用的代碼單元,我們可以將其理解為一個未命名的內(nèi)聯(lián)函數(shù)。與任何函數(shù)類似,一個Lambda具有一個返回類型、一個參數(shù)列表和一個函數(shù)體。但與函數(shù)不同,Lambda可以定義在函數(shù)內(nèi)部,其語法格式如下:
[capture list](parameter list) mutable(可選) 異常屬性->return type{function body}
capture list(捕獲列表)是一個Lambda所在函數(shù)中定義的局部變量的列表,通常為空,表示Lambda不使用它所在函數(shù)中的任何局部變量。parameter list(參數(shù)列表)、return type(返回類型)、function body(函數(shù)體)與任何普通函數(shù)基本一致,但是Lambda的參數(shù)列表不能有默認(rèn)參數(shù),且必須使用尾置返回類型。 mutable表示Lambda能夠修改捕獲的變量,省略了mutable,則不能修改。異常屬性則指定Lambda可能會拋出的異常類型。
其中Lambda表達(dá)式必須的部分只有capture list和function body。在Lambda忽略參數(shù)列表時表示指定一個空參數(shù)列表,忽略返回類型時,Lambda可根據(jù)函數(shù)體中的代碼推斷出返回類型。例如:
auto f=[]{return 42;}
我們定義了一個可調(diào)用對象f,它不接受任何參數(shù),返回42。auto關(guān)鍵字實際會將 Lambda 表達(dá)式轉(zhuǎn)換成一種類似于std::function的內(nèi)部類型(但并不是std::function類型,雖然與std::function“兼容”)。所以,我們也可以這么寫:
std::function<int()> Lambda = [] () -> int { return val * 100;};
如果你對std::function<int()>這種寫法感到很神奇,可以查看 C++ 11 的有關(guān)std::function的用法。簡單來說,std::function<int()>是一個實例化后的模板類,代表一個可調(diào)用的對象,接受 0 個參數(shù),返回值是int。所以,當(dāng)我們需要一個接受一個double作為參數(shù),返回int的對象時,就可以寫作:std::function<int(double)>[3]^{[3]}[3]。
1.4調(diào)用方式
Lambda表達(dá)式的調(diào)用方式與普通函數(shù)的調(diào)用方式相同,上面Lambda表達(dá)式的調(diào)用方式如下:
cout<<f()<<endl; //打印42 //或者直接調(diào)用 cout<<[]{return 42;}()<<endl;
我們還可以定義一個單參數(shù)的Lambda,實現(xiàn)上面字符串排序的shorter()比較函數(shù)的功能:
auto f=[](cosnt string& a,const string& b) { return a.size()<b.size(); } //將Lambda傳入排序算法sort中 sort(words.begin(),word2.end(),[](cosnt string& a,const string& b){ return a.size()<b.size(); }); //或者 sort(words.begin(),word2.end(),f);
2.Lambda的捕獲列表
Lambda可以獲?。ú东@)它所在作用域中的變量值,由捕獲列表(capture list)指定在Lambda 表達(dá)式的代碼內(nèi)可使用的外部變量。比如雖然一個Lambda可以出現(xiàn)在一個函數(shù)中,使用其局部變量,但它只能使用那些在捕獲列表中明確指明的變量。Lambda在捕獲所需的外部變量有兩種方式:引用和值。我們可以在捕獲列表中設(shè)置各變量的捕獲方式。如果沒有設(shè)置捕獲列表,Lambda默認(rèn)不能捕獲任何的變量。捕獲方式具體有如下幾種:
- [] 不截取任何變量
- [&} 截取外部作用域中所有變量,并作為引用在函數(shù)體中使用
- [=] 截取外部作用域中所有變量,并拷貝一份在函數(shù)體中使用
- [=,&valist] 截取外部作用域中所有變量,并拷貝一份在函數(shù)體中使用,但是對以逗號分隔valist使用引用
- [&,valist] 以引用的方式捕獲外部作用域中所有變量,對以逗號分隔的變量列表valist使用值的方式捕獲
- [valist] 對以逗號分隔的變量列表valist使用值的方式捕獲
- [&valist] 對以逗號分隔的變量列表valist使用引用的方式捕獲
- [this] 截取當(dāng)前類中的this指針。如果已經(jīng)使用了&或者=就默認(rèn)添加此選項。
在[]中設(shè)置捕獲列表,就可以在Lambda中使用變量a了,這里使用按值(=, by value)捕獲。
#include <iostream> int main() { int a = 123; auto lambda = [=]()->void { std::cout << "In Lambda: " << a << std::endl; }; lambda(); return 0; }
編譯運(yùn)行結(jié)果如下:
In Lambda: 123
按值傳遞到Lambda中的變量,默認(rèn)是不可變的(immutable),如果需要在Lambda中進(jìn)行修改的話,需要在形參列表后添加mutable關(guān)鍵字(按值傳遞無法改變Lambda外變量的值)。
#include <iostream> int main() { int a = 123; std::cout << a << std::endl; auto lambda = [=]() mutable ->void{ a = 234; std::cout << "In Lambda: " << a << std::endl; }; lambda(); std::cout << a << std::endl; return 0; }
編譯運(yùn)行結(jié)果為:
123
In Lambda: 234 //可以修改
123 //注意這里的值,并沒有改變
如果沒有添加mutable,則編譯出錯:
$ g++ main.cpp -std=c++11
main.cpp:9:5: error: cannot assign to a variable captured by copy in a non-mutable Lambda
a = 234;
~ ^
1 error generated.
看到這,不禁要問,這魔法般的變量捕獲是怎么實現(xiàn)的呢?原來,Lambda是通過創(chuàng)建個類來實現(xiàn)的。這個類重載了操作符(),一個Lambda函數(shù)是該類的一個實例。當(dāng)該類被構(gòu)造時,周圍的變量就傳遞給構(gòu)造函數(shù)并以成員變量保存起來,看起來跟函數(shù)對象(仿函數(shù))很相似,但是C++11標(biāo)準(zhǔn)建議使用Lambda表達(dá)式,而不是函數(shù)對象,Lambda表達(dá)式更加輕量高效,易于使用和理解[4]^{[4]}[4]。
3.Lambda的類型
lambda函數(shù)的類型看起來和函數(shù)指針很像,都是把函數(shù)賦值給了一個變量。實際上,lambda函數(shù)是用仿函數(shù)實現(xiàn)的,它看起來又像是一種自定義的類。而事實上,lambda類型并不是簡單的函數(shù)指針類型或者自定義類型,lambda函數(shù)是一個閉包(closure)的類,C++11標(biāo)準(zhǔn)規(guī)定,closure類型是特有的、匿名且非聯(lián)合體的class類型。每個lambda表達(dá)式都會產(chǎn)生一個閉包類型的臨時對象(右值)。因此,嚴(yán)格來說,lambda函數(shù)并非函數(shù)指針,但是C++11允許lambda表達(dá)式向函數(shù)指針轉(zhuǎn)換,前提是沒有捕捉任何變量且函數(shù)指針?biāo)赶虻暮瘮?shù)必須跟lambda函數(shù)有相同的調(diào)用方式。
typedef int(*pfunc)(int x, int y); int main() { auto func = [](int x, int y)->int { return x + y; }; pfunc p1 = nullptr; p1 = func; //lambda表達(dá)式向函數(shù)指針轉(zhuǎn)換 std::cout << p1(1, 2) << std::endl; return 0; }
4.lambda的常量性和mutable關(guān)鍵字
C++11中,默認(rèn)情況下lambda函數(shù)是一個const函數(shù),按照規(guī)則,一個const成員函數(shù)是不能在函數(shù)體內(nèi)改變非靜態(tài)成員變量的值。
int main() { int val = 0; auto const_val_lambda = [=] { val = 3; }; // 編譯失敗,不能在const的lambda函數(shù)中修改按值捕獲的變量val auto mutable_val_lambda = [=]() mutable { val = 3; }; auto const_ref_lambda = [&] { val = 3; }; auto const_param_lambda = [](int v) { v = 3; }; const_param_lambda(val); return 0; }
閱讀代碼,注意以下幾點(diǎn):
(1)可以看到在const的lambda函數(shù)中無法修改按值捕捉到的變量。lambda函數(shù)是通過仿函數(shù)來實現(xiàn)的,捕捉到的變量相當(dāng)于是仿函數(shù)類中的成員變量,而lambda函數(shù)相當(dāng)于是成員函數(shù),const成員函數(shù)自然不能修改普通成員變量;
(2)使用引用的方式捕獲的變量在常量成員函數(shù)中值被更改則不會導(dǎo)致錯誤,其原因簡單地說,由于const_ref_lambda 不會改變引用本身,而只會改變引用的值,所以編譯通過;
(3)使用mutable修飾的mutable_val_lambda,去除了const屬性,所以可以修改按值方式捕獲到的變量;
(4)按值傳遞參數(shù)的const_param_lambda修改的是傳入lambda函數(shù)的實參,當(dāng)然不會有問題。
5.Lambda的常見用法
(1)Lambda函數(shù)和STL
Lambda函數(shù)的引入為STL的使用提供了極大的方便。比如下面這個例子,當(dāng)你想遍歷一個vector的時候,原來你得這么寫:
vector<int> v={1,2,3,4,5,6,7,8,9}; //傳統(tǒng)的for循環(huán) for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ ) { cout << *itr; } //函數(shù)指針 void printFunc(int v) { cout<<v; } for_each(v.begin(),v.end(),printFunc); //仿函數(shù) struct CPrintFunc { void operator() (int val)const { cout << val; } }; for_each(v.begin(),v.end(),CPrintFunc());
現(xiàn)在有了Lambda函數(shù)你就可以這么寫:
for_each(v.begin(),v.end(),[](int val) { cout << val; });
很明顯,相比于傳統(tǒng)的for循環(huán)、函數(shù)指針和仿函數(shù),使用lambda函數(shù)更加簡潔。如果處理vector成員的業(yè)務(wù)代碼更加復(fù)雜,那么更能凸顯Lambda函數(shù)的便捷。而且這么寫之后執(zhí)行效率反而會提高,因為編譯器有可能使用循環(huán)展開來加速執(zhí)行過程。
以上就是一文讀懂c++11 Lambda表達(dá)式的詳細(xì)內(nèi)容,更多關(guān)于c++11 Lambda表達(dá)式的資料請關(guān)注腳本之家其它相關(guān)文章!
- C++11中l(wèi)ambda、std::function和std:bind詳解
- C++11/14 線程中使用Lambda函數(shù)的方法
- 淺談C++11新引入的lambda表達(dá)式
- 結(jié)合C++11新特性來學(xué)習(xí)C++中l(wèi)ambda表達(dá)式的用法
- 淺析C++11新特性的Lambda表達(dá)式
- C++11 lambda表達(dá)式在回調(diào)函數(shù)中的使用方式
- C++11?lambda(匿名函數(shù))表達(dá)式詳細(xì)介紹
- C++11中的可變參數(shù)模板/lambda表達(dá)式
- 深入解析C++11?lambda表達(dá)式/包裝器/線程庫
- 深入理解C++11:探索lambda函數(shù)的奧秘
相關(guān)文章
基于C語言實現(xiàn)圖書管理信息系統(tǒng)設(shè)計
這篇文章主要為大家詳細(xì)介紹了基于C語言實現(xiàn)圖書管理信息系統(tǒng)設(shè)計與實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01詳解C++ 共享數(shù)據(jù)保護(hù)機(jī)制
這篇文章主要介紹了詳解C++ 共享數(shù)據(jù)保護(hù)機(jī)制的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用c++,感興趣的朋友可以了解下2021-02-02C語言 解決不用+、-、×、÷數(shù)字運(yùn)算符做加法的實現(xiàn)方法
本篇文章是對在C語言中解決不用+、-、×、÷數(shù)字運(yùn)算符做加法的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05Visual Studio Code 配置C、C++環(huán)境/編譯并運(yùn)行的流程分析
這篇文章主要介紹了Visual Studio Code 配置C、C++環(huán)境/編譯并運(yùn)行的流程分析,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05詳解C語言中動態(tài)內(nèi)存管理及柔性數(shù)組的使用
這篇文章主要為大家詳細(xì)介紹一下C語言中動態(tài)內(nèi)存管理以及柔性數(shù)組的使用方法,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)C語言有一定的幫助,需要的可以參考一下2022-07-07