根據(jù)算法接受一元謂詞還是二元謂詞宪巨,我們傳遞給算法的謂詞必須嚴(yán)格接受一個(gè)或兩個(gè)參數(shù)。但是议谷,有時(shí)我們希望進(jìn)行的操作需要更多參數(shù)愈腾,超出了算法對(duì)謂詞的限制憋活。這就需要用到lambda表達(dá)式。
我們可以向一個(gè)算法傳遞任何類別的可調(diào)用對(duì)象(callable object)虱黄。對(duì)于一個(gè)對(duì)象或者表達(dá)式悦即,如果可以對(duì)其使用調(diào)用運(yùn)算符,則稱它為可調(diào)用的。即辜梳,如果e是一個(gè)可調(diào)用的表達(dá)式粱甫,則我們可以編寫代碼e(args),其中args是一個(gè)逗號(hào)分隔的一個(gè)或多個(gè)參數(shù)的列表作瞄。
四種可調(diào)用對(duì)象:函數(shù)茶宵,函數(shù)指針,重載了函數(shù)調(diào)用運(yùn)算符的類和lambda表達(dá)式宗挥。
一個(gè)lambda表達(dá)式表示一個(gè)可調(diào)用的代碼單元乌庶。我們可以將其理解為一個(gè)未命名的內(nèi)聯(lián)函數(shù)。與任何函數(shù)類似契耿,一個(gè)lambda具有一個(gè)返回類型瞒大,一個(gè)參數(shù)列表和一個(gè)函數(shù)體。但與函數(shù)不同搪桂,lambda可能定義在函數(shù)內(nèi)部糠赦,一個(gè)lambda表達(dá)式具有如下形式:
[capture list](parameter list)->return type{function body}
其中capture list是一個(gè)lambda所在函數(shù)中定義的局部變量的列表(通常為空);return type和function body與任何普通函數(shù)一樣分別表示返回類型锅棕,參數(shù)列表和函數(shù)體。但是淌山,與普通函數(shù)不同裸燎,lambda必須使用尾置返回來指定返回類型。我們可以忽略參數(shù)列表和返回類型泼疑,但必須永遠(yuǎn)包含捕獲列表和函數(shù)體德绿。
auto f = []{return 42;}
cout << f() << endl;
在lambda中忽略括號(hào)和參數(shù)列表等價(jià)于指定一個(gè)空參數(shù)列表。如果忽略返回類型退渗,lambda根據(jù)函數(shù)體中的代碼推斷出返回類型移稳。如果函數(shù)體只是一個(gè)return語句,則返回類型從返回的表達(dá)式的類型推斷而來会油。如果lambda的函數(shù)體包含任何單一return語句之外的內(nèi)容个粱,且未指定返回類型,則返回void.
與一個(gè)普通函數(shù)調(diào)用類似翻翩,調(diào)用一個(gè)lambda時(shí)給定的實(shí)參被用來初始化lambda的形參都许。通常,實(shí)參和形參的類型必須匹配嫂冻。但與普通函數(shù)不同胶征,lambda不能有默認(rèn)參數(shù)。因此桨仿,一個(gè)lambda調(diào)用的實(shí)參數(shù)目永遠(yuǎn)與形參數(shù)目相等睛低。一旦形參初始化完畢,就可以執(zhí)行函數(shù)體了。下面是一個(gè)與isShorter函數(shù)完成相同功能的lambda:
[](const string &a, const string &b) {return a.size() < b.size(); }
stable_sort(words.begin(), words.end(),
[](const string &a, const string &b) {return a.size() < b.size();});
一個(gè)lambda只有在其捕獲列表中捕獲一個(gè)它所在函數(shù)中的局部變量钱雷,才能在函數(shù)體中使用該變量骂铁。捕獲列表只用于局部非static變量,lambda可以直接使用局部static變量和在它所在函數(shù)之外聲明的名字急波。
其實(shí)从铲,當(dāng)定義一個(gè)lambda時(shí),編譯器生成一個(gè)與lambda相對(duì)應(yīng)的未命名類的未命名對(duì)象澄暮,其實(shí)這是一個(gè)函數(shù)對(duì)象名段。捕獲列表中獲得的局部變量成為了這個(gè)函數(shù)對(duì)象的數(shù)據(jù)成呀un。默認(rèn)情況下泣懊,從lambda生成的類都包含一個(gè)對(duì)應(yīng)該lambda所捕獲的變量的數(shù)據(jù)成員伸辟,類似任何類的數(shù)據(jù)成員,lambda的數(shù)據(jù)成員也在lambda對(duì)象創(chuàng)建時(shí)被初始化馍刮。
類似參數(shù)傳遞信夫,變量的捕獲方式也可以是值或引用。首先來說值捕獲卡啰,與傳值參數(shù)類似静稻,采用值捕獲的前提是變量可以拷貝。與參數(shù)不同匈辱,被捕獲的變量的值是在lambda創(chuàng)建時(shí)拷貝振湾,而不是調(diào)用時(shí)拷貝。隨后對(duì)其修改不會(huì)影響到lambda內(nèi)對(duì)應(yīng)的值亡脸。接著來說引用捕獲押搪,引用捕獲與引用返回有著相同的問題和限制,如果我們采用引用方式捕獲一個(gè)變量浅碾,就必須確保被引用的對(duì)象在lambda執(zhí)行的時(shí)候是存在的大州。lambda捕獲的是局部變量,這些變量在函數(shù)結(jié)束后就不復(fù)存在了垂谢。如果lambda可能在函數(shù)結(jié)束后執(zhí)行厦画,捕獲的引用指向的局部變量已經(jīng)消失。所以建議是盡量保持lambda的變量捕獲簡(jiǎn)單化滥朱。
除了顯示列出我們希望使用的來自所在函數(shù)的變量之外苛白,還可以讓編譯器根據(jù)lambda體中的代碼來推斷我們要使用哪些變量。為了指示編譯器推斷捕獲列表焚虱,應(yīng)在捕獲列表中寫一個(gè)&或=购裙。&告訴編譯器采用捕獲引用方式,=則表示采用值捕獲方式鹃栽。如果我們希望對(duì)一部分變量采用值捕獲躏率,對(duì)其他變量采用引用捕獲躯畴,可以混合使用隱式捕獲和顯式捕獲:
[&, c]... [=, &os]...
當(dāng)我們混合使用隱式捕獲和顯式捕獲時(shí),捕獲列表中的第一個(gè)元素必須是一個(gè)&或=薇芝。此符號(hào)指定了默認(rèn)捕獲方式為引用或值蓬抄。當(dāng)混合使用隱式捕獲和顯式捕獲時(shí),顯式捕獲的變量必須使用與隱式捕獲不同的方式夯到。
關(guān)于可變lambda,默認(rèn)情況下嚷缭,對(duì)于一個(gè)值被拷貝的變量,lambda不會(huì)改變其值耍贾。如果我們希望能改變一個(gè)被捕獲的變量的值阅爽,就必須在參數(shù)列表后面加上關(guān)鍵字mutable。因此荐开,可變lambda不能省略參數(shù)列表:
void fcn3()
{
size_t v1 = 42; // local variable
// f can change the value of the variables it captures.
auto f = [v1] () mutable {return ++v1;}
v1 = 0;
auto j = f(); // j is 43
}
上例中如果不加() mutable就會(huì)出現(xiàn)錯(cuò)誤信息error: increment of read-only variable ‘v1’
一個(gè)引用捕獲變量是否可以修改依賴于此引用指向的是一個(gè)const類型還是一個(gè)非const類型:
void fcn4()
{
size_t v1 = 42; // local variable
// v1 is a reference to a nonconst variable
// we can change that variable through the reference inside f2
auto f2 = [&v1] () {return ++v1;}
v1 = 0;
auto j = f2(); // j is 1
}
關(guān)于lambda表達(dá)式的返回類型付翁,先看一個(gè)例子:
transform(vi.begin(), vi.end(), vi.begin(), [](int i) {return i < 0 ? -i : i; });
只有一個(gè)return語句,返回一個(gè)條件表達(dá)式的結(jié)果晃听。我們無須指定返回類型百侧,因?yàn)榭梢愿鶕?jù)條件運(yùn)算符的類型推斷出來。但是能扒,如果我們將程序改寫為看起來是等價(jià)的if語句佣渴,就會(huì)產(chǎn)生編譯錯(cuò)誤:
//錯(cuò)誤:不能推斷l(xiāng)ambda的返回類型
transform(vi.begin(), vi.end(), vi.begin(), [](int i){ if(i < 0) return -i; else return i;});
編譯器推斷這個(gè)版本的lambda返回類型為void,但它返回了一個(gè)int值初斑。
當(dāng)我們需要為一個(gè)lambda定義返回類型時(shí)观话,必須使用尾置返回類型:
transform(vi.begin(), vi.end(), vi.begin(),
[](int i) -> int { if(i < 0) return -i; else return i;});