C++ Lambda表達(dá)式

Lambda表達(dá)式,也稱為匿名函數(shù)样眠、閉包函數(shù)友瘤,在別的編程語言很早就有了。

C++ 11開始檐束,也支持了這個(gè)功能辫秧。而后續(xù)的C++ 版本又陸陸續(xù)續(xù)做了些改進(jìn)。

整理了編筆記被丧,把lambda表達(dá)式的用法試驗(yàn)盟戏、記錄一下。

lambda表達(dá)式的語法如下:

[ captures ] ( params ) specs -> return-type { body }
  • captures:捕獲列表甥桂,用來捕獲當(dāng)前作用域的變量柿究,然后可以在lambda表達(dá)式內(nèi)部使用。支持按值捕獲黄选、按引用捕獲蝇摸。用法和行為,跟普通的C++函數(shù)調(diào)用很像办陷。

  • params:參數(shù)列表探入,可選。在調(diào)用lambda表達(dá)式時(shí)懂诗,額外傳遞的參數(shù)。

  • specs:限定符苗膝,可選殃恒。比如說mutable,后面的試驗(yàn)會(huì)用到它辱揭。在后續(xù)的C++ 17离唐、20、23等版本问窃,還添加了constexpr亥鬓、consteval、static這些域庇。

  • return-type:返回類型嵌戈,可選覆积。

  • body:函數(shù)體

整個(gè)看起來,跟普通的C++函數(shù)很像:

  • lambda的捕獲列表 + 參數(shù)列表 <--> 函數(shù)的參數(shù)列表

  • lambda的限定符 <--> 函數(shù)的限定符

  • lambda的返回類型 <--> 函數(shù)的返回類型

  • lambda的函數(shù)體 <--> 函數(shù)的函數(shù)體

就是函數(shù)的函數(shù)名熟呛,在lambda里面不需要定義宽档。所以也把lambda表達(dá)式稱為匿名函數(shù)。

這是一個(gè)簡單的lambda表達(dá)式例子:

#include <iostream>

int main()
{
    int x = 1;
    // Define a simple lambda
    auto add_func = [x] (int y) {
        return x + y; 
    }; 

    // Call lambda
    int ans = add_func(10);
    std::cout << ans << std::endl; 
    return 0; 
}

運(yùn)行結(jié)果很淺白庵朝,會(huì)輸出:11

add_func表達(dá)式在定義時(shí)吗冤,把作用域的x變量捕獲,然后其函數(shù)體內(nèi)使用x九府、和額外傳遞的參數(shù)y進(jìn)行運(yùn)算椎瘟,最后返回計(jì)算結(jié)果。

那么我們把代碼稍作修改:

#include <iostream>

int main()
{
    int x = 1;
    // Define a simple lambda
    auto add_func = [x] (int y) {
        return x + y; 
    }; 

    // Call lambda
    int ans = add_func(10);
    std::cout << ans << std::endl; 

    // Modify x outside
    x = 2;
    // Call lambda again
    ans = add_func(10);
    std::cout << ans << std::endl; 

    return 0; 
}

在第一次調(diào)用add_func之后侄旬,我們把外部的x變量修改了肺蔚,然后,再次調(diào)用add_func勾怒。

運(yùn)行這個(gè)代碼婆排,會(huì)發(fā)現(xiàn)輸出:

11

11

外部x變量的修改,并沒有傳遞到lambda內(nèi)部笔链。

這是因?yàn)椋篊++在編譯期間段只,編譯器自動(dòng)為lambda表達(dá)式生成一個(gè)閉包ClosureType類。在lambda表達(dá)式被定義的地方鉴扫,實(shí)例化該類赞枕,生成實(shí)例add_func,并對(duì)被其捕獲的成員變量進(jìn)行賦值:

add_func.__x = x

所以坪创,按值捕獲的變量炕婶,在lambda定義時(shí),它在lambda內(nèi)部的值已經(jīng)被確定下來莱预。后續(xù)外部對(duì)變量x的修改柠掂,不會(huì)再影響到lambda內(nèi)部的__x。

那么依沮,如果需要修改按值捕獲的變量涯贞,應(yīng)該怎么做呢?修改完以后危喉,lambda內(nèi)外的變量會(huì)發(fā)生什么變化呢宋渔?

#include <iostream>

int main()
{
    int x = 1;    
    // Define lambda to modify value captured
    auto modify_func = [x] () {
        x++; 
    };

    return 0; 
}

像這樣,直接對(duì)按值捕獲的變量進(jìn)行修改辜限,編譯器會(huì)報(bào)錯(cuò):

error: increment of read-only variable 'x'
         x++;

需要用到一開始說的mutable限定符皇拣,改為這樣就可以了:

auto modify_func = [x] () mutable {

加上一些輸出信息之后,代碼變成了這樣:

#include <iostream>

int main()
{
    int x = 1;    
    // Define lambda to modify value captured
    auto modify_func = [x] () mutable {
        std::cout << "x inside lambda is: " << x << std::endl;
        x++; 
    };

    std::cout << "Before calling lambda, x out of lambda is: " << x << std::endl;
    modify_func();
    std::cout << "After calling lambda, x out of lambda is: " << x << std::endl;    
    modify_func();
    std::cout << "After calling lambda again, x out of lambda is: " << x << std::endl;

    return 0; 
}

運(yùn)行這段代碼薄嫡,可以得到這些輸出:

Before calling lambda, x out of lambda is: 1
x inside lambda is: 1
After calling lambda, x out of lambda is: 1 
x inside lambda is: 2
After calling lambda again, x out of lambda is: 1

這里可以看出兩個(gè)信息:

  1. 按值捕獲之后氧急,lambda內(nèi)外的變量已經(jīng)沒有關(guān)系颗胡,各自有各自的數(shù)值。

  2. 修改lambda實(shí)例的成員變量之后态蒂,該修改會(huì)一直生效杭措,直到lambda實(shí)例的生命周期結(jié)束。

第一點(diǎn)信息钾恢,前面已經(jīng)解釋過手素。第二點(diǎn)信息,跟第一點(diǎn)信息的原理也密切相關(guān)瘩蚪。

可以這么理解泉懦,閉包ClosureType類的實(shí)例modify_func,根據(jù)捕獲的變量疹瘦,內(nèi)部相應(yīng)創(chuàng)建了成員變量__x崩哩。Lambda內(nèi)部的x++,其實(shí)是modify_func.__x++言沐。所以邓嘹,下次再次調(diào)用modify_func時(shí),其成員變量__x保留了上次調(diào)用的數(shù)值险胰。

以上兩點(diǎn)信息汹押,只要理解了lambda表達(dá)式其實(shí)是個(gè)ClosureType類,由編譯器根據(jù)捕獲的變量起便,自動(dòng)生成對(duì)應(yīng)的成員變量棚贾。然后在lambda表達(dá)式定義的地方被實(shí)例化、初始化成員變量榆综。而后的lambda表達(dá)式調(diào)用妙痹,本質(zhì)上是調(diào)用了該實(shí)例的成員函數(shù)。那么這些行為就很自然而然了鼻疮。

接下來怯伊,按引用捕獲變量。

按引用捕獲判沟,用法震贵、行為跟普通函數(shù)的按引用傳遞沒什么區(qū)別。只需要在捕獲的變量前水评,加上&符號(hào)即可。

#include <iostream>

int main()
{
    int x = 1;
    // Define lambda to capture by reference
    auto ref_func = [&x] () {
        std::cout << "x inside lambda is: " << x << std::endl;
        x++; 
    };

    std::cout << "Before calling lambda, x out of lambda is: " << x << std::endl;
    ref_func();
    std::cout << "After calling lambda, x out of lambda is: " << x << std::endl;  

    x = 5; 
    std::cout << "Now change x out of lambda to: " << x << std::endl; 
    ref_func(); 
    std::cout << "After calling lambda again, x out of lambda is: " << x << std::endl;

    return 0; 
}

x變成&x媚送,變成了按引用捕獲變量中燥,之后叛本,lambda內(nèi)部和外部首尼,共享同一個(gè)變量雁佳。一方的修改缸废,將反應(yīng)到另一方上面。

所以咱扣,上面的代碼將輸出:

Before calling lambda, x out of lambda is: 1
x inside lambda is: 1
After calling lambda, x out of lambda is: 2
Now change x out of lambda to: 5
x inside lambda is: 5
After calling lambda again, x out of lambda is: 6

另外绽淘,如果需要按值捕獲外部的所有變量,通過[=]即可闹伪。

而通過[&]沪铭,可以按引用捕獲外部的所有變量。

最后一點(diǎn)偏瓤,針對(duì)外部的全局變量或者局部static變量杀怠,可以在lambda表達(dá)式內(nèi)部直接使用、修改厅克;內(nèi)外共享一個(gè)變量赔退。比如下面的代碼:

#include <iostream>

int global_val = 1;

int main()
{
    // Define lambda to use global param
    auto global_func = [] () {
        std::cout << "global_val inside lambda is: " << global_val << std::endl;
        global_val++; 
    };

    std::cout << "Before calling lambda, global_val out of lambda is: " << global_val << std::endl;
    global_func();
    std::cout << "After calling lambda, global_val out of lambda is: " << global_val << std::endl;  

    global_val = 5; 
    std::cout << "Now change global_val out of lambda to: " << global_val << std::endl; 
    global_func(); 
    std::cout << "After calling lambda again, global_val out of lambda is: " << global_val << std::endl;

    return 0; 
}

這段代碼將輸出:

Before calling lambda, global_val out of lambda is: 1
global_val inside lambda is: 1
After calling lambda, global_val out of lambda is: 2
Now change global_val out of lambda to: 5
global_val inside lambda is: 5
After calling lambda again, global_val out of lambda is: 6

可以看到,不用顯式捕獲全局變量证舟,lambda表達(dá)式內(nèi)部可以直接使用硕旗;lambda內(nèi)部和外部,共享同一個(gè)全局變量女责。一方的修改漆枚,將反應(yīng)到另一方上面。

綜上所述鲤竹,lambda表達(dá)式有這些特點(diǎn):

  1. 按值捕獲的變量浪读,在lambda定義時(shí),它在lambda內(nèi)部的值已經(jīng)被確定下來辛藻。之后碘橘,外部對(duì)該變量的修改,不會(huì)再影響到lambda內(nèi)部的那一份吱肌。

  2. 在lambda內(nèi)部痘拆,修改捕獲的變量之后,該修改會(huì)一直生效氮墨,直到lambda實(shí)例的生命周期結(jié)束纺蛆。

  3. 按引用捕獲的變量,lambda內(nèi)部和外部规揪,共享同一份桥氏。一方的修改,將反應(yīng)到另一方猛铅。

  4. 不用顯式捕獲全局變量字支,lambda表達(dá)式內(nèi)部可以直接使用;lambda內(nèi)部和外部,共享同一份全局變量堕伪。一方的修改揖庄,將反應(yīng)到另一方。

掌握了這些知識(shí)欠雌,就足以滿足常見的lambda表達(dá)式應(yīng)用了蹄梢。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市富俄,隨后出現(xiàn)的幾起案子禁炒,更是在濱河造成了極大的恐慌,老刑警劉巖蛙酪,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件齐苛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡桂塞,警方通過查閱死者的電腦和手機(jī)凹蜂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阁危,“玉大人玛痊,你說我怎么就攤上這事】翊颍” “怎么了擂煞?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長趴乡。 經(jīng)常有香客問我对省,道長,這世上最難降的妖魔是什么晾捏? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任蒿涎,我火速辦了婚禮,結(jié)果婚禮上惦辛,老公的妹妹穿的比我還像新娘劳秋。我一直安慰自己,他們只是感情好胖齐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布玻淑。 她就那樣靜靜地躺著,像睡著了一般呀伙。 火紅的嫁衣襯著肌膚如雪补履。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天剿另,我揣著相機(jī)與錄音箫锤,去河邊找鬼帅腌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛麻汰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播戚篙,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼五鲫,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了岔擂?” 一聲冷哼從身側(cè)響起位喂,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎乱灵,沒想到半個(gè)月后塑崖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痛倚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年规婆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝉稳。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抒蚜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耘戚,到底是詐尸還是另有隱情嗡髓,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布收津,位于F島的核電站饿这,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏撞秋。R本人自食惡果不足惜长捧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望部服。 院中可真熱鬧唆姐,春花似錦、人聲如沸廓八。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剧蹂。三九已至声功,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宠叼,已是汗流浹背先巴。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來泰國打工其爵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伸蚯。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓摩渺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親剂邮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子摇幻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 什么是lambda表達(dá)式 lambda表達(dá)式是一個(gè)可調(diào)用的代碼單元,我們可以理解為一個(gè)未命名的內(nèi)聯(lián)函數(shù)挥萌,當(dāng)定義一個(gè)...
    土豆吞噬者閱讀 863評(píng)論 0 0
  • lambda表達(dá)式 目錄 一绰姻、開篇 二、lambda初識(shí) 三引瀑、lambda基本用法 四狂芋、lambda表達(dá)式捕獲列表...
    開源519閱讀 207評(píng)論 0 0
  • Lambda 表達(dá)式(Lambda Expression)是 C++11 引入的一個(gè)“語法糖”,可以方便快捷地創(chuàng)建...
    linjinhe閱讀 742評(píng)論 0 0
  • C++ lambda表達(dá)式與函數(shù)對(duì)象 lambda表達(dá)式是C++11中引入的一項(xiàng)新技術(shù)憨栽,利用lambda表達(dá)式可以...
    小白將閱讀 85,245評(píng)論 15 118
  • lambda其實(shí)就是匿名函數(shù)帜矾,有時(shí)候我們創(chuàng)建一個(gè)函數(shù),只有一個(gè)地方使用這個(gè)函數(shù)徒像∈蛱兀或者某類函數(shù)的函數(shù)體經(jīng)常變化,需要...
    小阿牛的爸爸閱讀 434評(píng)論 0 3