一文讀懂c++11 Lambda表達式
1.簡介
1.1定義
C++11新增了很多特性,Lambda表達式(Lambda expression)就是其中之一,很多語言都提供了 Lambda 表達式,如 Python,Java ,C#等。本質上, Lambda 表達式是一個可調用的代碼單元[1]^{[1]}[1]。實際上是一個閉包(closure),類似于一個匿名函數(shù),擁有捕獲所在作用域中變量的能力,能夠將函數(shù)做為對象一樣使用,通常用來實現(xiàn)回調函數(shù)、代理等功能。Lambda表達式是函數(shù)式編程的基礎,C++11引入了Lambda則彌補了C++在函數(shù)式編程方面的空缺。
1.2作用
以往C++需要傳入一個函數(shù)的時候,必須事先進行聲明,視情況可以聲明為一個普通函數(shù)然后傳入函數(shù)指針,或者聲明一個仿函數(shù)(functor,函數(shù)對象),然后傳入一個對象。比如C++的STL中很多算法函數(shù)模板需要傳入謂詞(predicate)來作為判斷條件,如排序算法sort。謂詞就是一個可調用的表達式,其返回結果是一個能用作條件的值。標準庫算法所使用的謂詞分為兩類:一元謂詞(unary predicate,只接受單一參數(shù))和二元謂詞(binary predicate,接受兩個參數(shù))。接受謂詞的算法對輸入序列中的元素調用謂詞,因此元素類型必須能轉換為謂詞的參數(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表達式可以像函數(shù)指針、仿函數(shù)一樣,作為一個可調用對象(callable object)被使用,比如作為謂詞傳入標準庫算法。
也許有人會問,有了函數(shù)指針、函數(shù)對象為何還要引入Lambda呢?函數(shù)對象能維護狀態(tài),但語法開銷大,而函數(shù)指針語法開銷小,卻沒法保存函數(shù)體內的狀態(tài)。如果你覺得魚和熊掌不可兼得,那你可錯了。Lambda函數(shù)結合了兩者的優(yōu)點,讓你寫出優(yōu)雅簡潔的代碼。
1.3語法格式
Lambda 表達式就是一個可調用的代碼單元,我們可以將其理解為一個未命名的內聯(lián)函數(shù)。與任何函數(shù)類似,一個Lambda具有一個返回類型、一個參數(shù)列表和一個函數(shù)體。但與函數(shù)不同,Lambda可以定義在函數(shù)內部,其語法格式如下:
[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ù)列表不能有默認參數(shù),且必須使用尾置返回類型。 mutable表示Lambda能夠修改捕獲的變量,省略了mutable,則不能修改。異常屬性則指定Lambda可能會拋出的異常類型。
其中Lambda表達式必須的部分只有capture list和function body。在Lambda忽略參數(shù)列表時表示指定一個空參數(shù)列表,忽略返回類型時,Lambda可根據(jù)函數(shù)體中的代碼推斷出返回類型。例如:
auto f=[]{return 42;}
我們定義了一個可調用對象f,它不接受任何參數(shù),返回42。auto關鍵字實際會將 Lambda 表達式轉換成一種類似于std::function的內部類型(但并不是std::function類型,雖然與std::function“兼容”)。所以,我們也可以這么寫:
std::function<int()> Lambda = [] () -> int { return val * 100;};
如果你對std::function<int()>這種寫法感到很神奇,可以查看 C++ 11 的有關std::function的用法。簡單來說,std::function<int()>是一個實例化后的模板類,代表一個可調用的對象,接受 0 個參數(shù),返回值是int。所以,當我們需要一個接受一個double作為參數(shù),返回int的對象時,就可以寫作:std::function<int(double)>[3]^{[3]}[3]。
1.4調用方式
Lambda表達式的調用方式與普通函數(shù)的調用方式相同,上面Lambda表達式的調用方式如下:
cout<<f()<<endl; //打印42
//或者直接調用
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 表達式的代碼內可使用的外部變量。比如雖然一個Lambda可以出現(xiàn)在一個函數(shù)中,使用其局部變量,但它只能使用那些在捕獲列表中明確指明的變量。Lambda在捕獲所需的外部變量有兩種方式:引用和值。我們可以在捕獲列表中設置各變量的捕獲方式。如果沒有設置捕獲列表,Lambda默認不能捕獲任何的變量。捕獲方式具體有如下幾種:
- [] 不截取任何變量
- [&} 截取外部作用域中所有變量,并作為引用在函數(shù)體中使用
- [=] 截取外部作用域中所有變量,并拷貝一份在函數(shù)體中使用
- [=,&valist] 截取外部作用域中所有變量,并拷貝一份在函數(shù)體中使用,但是對以逗號分隔valist使用引用
- [&,valist] 以引用的方式捕獲外部作用域中所有變量,對以逗號分隔的變量列表valist使用值的方式捕獲
- [valist] 對以逗號分隔的變量列表valist使用值的方式捕獲
- [&valist] 對以逗號分隔的變量列表valist使用引用的方式捕獲
- [this] 截取當前類中的this指針。如果已經(jīng)使用了&或者=就默認添加此選項。
在[]中設置捕獲列表,就可以在Lambda中使用變量a了,這里使用按值(=, by value)捕獲。
#include <iostream>
int main()
{
int a = 123;
auto lambda = [=]()->void
{
std::cout << "In Lambda: " << a << std::endl;
};
lambda();
return 0;
}
編譯運行結果如下:
In Lambda: 123
按值傳遞到Lambda中的變量,默認是不可變的(immutable),如果需要在Lambda中進行修改的話,需要在形參列表后添加mutable關鍵字(按值傳遞無法改變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;
}
編譯運行結果為:
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ù)是該類的一個實例。當該類被構造時,周圍的變量就傳遞給構造函數(shù)并以成員變量保存起來,看起來跟函數(shù)對象(仿函數(shù))很相似,但是C++11標準建議使用Lambda表達式,而不是函數(shù)對象,Lambda表達式更加輕量高效,易于使用和理解[4]^{[4]}[4]。
3.Lambda的類型
lambda函數(shù)的類型看起來和函數(shù)指針很像,都是把函數(shù)賦值給了一個變量。實際上,lambda函數(shù)是用仿函數(shù)實現(xiàn)的,它看起來又像是一種自定義的類。而事實上,lambda類型并不是簡單的函數(shù)指針類型或者自定義類型,lambda函數(shù)是一個閉包(closure)的類,C++11標準規(guī)定,closure類型是特有的、匿名且非聯(lián)合體的class類型。每個lambda表達式都會產(chǎn)生一個閉包類型的臨時對象(右值)。因此,嚴格來說,lambda函數(shù)并非函數(shù)指針,但是C++11允許lambda表達式向函數(shù)指針轉換,前提是沒有捕捉任何變量且函數(shù)指針所指向的函數(shù)必須跟lambda函數(shù)有相同的調用方式。
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表達式向函數(shù)指針轉換
std::cout << p1(1, 2) << std::endl;
return 0;
}
4.lambda的常量性和mutable關鍵字
C++11中,默認情況下lambda函數(shù)是一個const函數(shù),按照規(guī)則,一個const成員函數(shù)是不能在函數(shù)體內改變非靜態(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;
}
閱讀代碼,注意以下幾點:
(1)可以看到在const的lambda函數(shù)中無法修改按值捕捉到的變量。lambda函數(shù)是通過仿函數(shù)來實現(xiàn)的,捕捉到的變量相當于是仿函數(shù)類中的成員變量,而lambda函數(shù)相當于是成員函數(shù),const成員函數(shù)自然不能修改普通成員變量;
(2)使用引用的方式捕獲的變量在常量成員函數(shù)中值被更改則不會導致錯誤,其原因簡單地說,由于const_ref_lambda 不會改變引用本身,而只會改變引用的值,所以編譯通過;
(3)使用mutable修飾的mutable_val_lambda,去除了const屬性,所以可以修改按值方式捕獲到的變量;
(4)按值傳遞參數(shù)的const_param_lambda修改的是傳入lambda函數(shù)的實參,當然不會有問題。
5.Lambda的常見用法
(1)Lambda函數(shù)和STL
Lambda函數(shù)的引入為STL的使用提供了極大的方便。比如下面這個例子,當你想遍歷一個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è)務代碼更加復雜,那么更能凸顯Lambda函數(shù)的便捷。而且這么寫之后執(zhí)行效率反而會提高,因為編譯器有可能使用循環(huán)展開來加速執(zhí)行過程。
以上就是一文讀懂c++11 Lambda表達式的詳細內容,更多關于c++11 Lambda表達式的資料請關注腳本之家其它相關文章!
相關文章
C語言之棧和堆(Stack && Heap)的優(yōu)缺點及其使用區(qū)別
本篇文章主要介紹了什么是棧(Stack) 、什么是堆( Heap),以及棧和堆的優(yōu)缺點,同時介紹了應該什么時候使用堆和棧,有需要的朋友可以參考下2015-07-07

