生成隨機數字ide
假設咱們有一個vector<int>容器,想用100之內的隨機數初始化它,其中一個辦法是經過generate函數生成,如代碼1所示。generate函數接受三個參數,前兩個參數指定容器的起止位置,後一個參數指定生成邏輯,這個邏輯正是經過Lambda來表達的。函數
代碼 1測試
咱們如今看到Lambda是最簡形式,只包含捕獲子句和函數體兩個必要部分,其餘部分都省略了。[]是Lambda的捕獲子句,也是引出Lambda的語法,當編譯器看到這個符號時,就知道咱們在寫一個Lambda了。函數體經過{} 包圍起來,裏面的代碼和一個普通函數的函數體沒有什麼不一樣,www.189works.com。spa
那麼,代碼1生成的隨機數字裏有多少個奇數呢,咱們能夠經過for_each函數數一下,如代碼3所示。和generate函數不一樣的是,for_each函數要求咱們提供的Lambda接受一個參數。通常狀況下,若是Lambda的參數列表不包含任何參數,咱們能夠把它省略,就像代碼1所示的那樣;若是包含多個參數,能夠經過逗號分隔,如(int index, std::string item)。對象
代碼 2編譯器
看到這裏,細心的讀者可能已經發現代碼2的捕獲子句裏面多了一個"&odd_count",這是用來幹嗎的呢?咱們知道,這個代碼的關鍵部分是在Lambda的函數體裏修改一個外部的計數變量,常見的語言(如C#)會自動爲Lambda捕獲當前上下文的全部變量,但C++要求咱們在Lambda的捕獲子句裏顯式指定想要捕獲的變量,不然沒法在函數體裏使用這些變量。若是捕獲子句裏面什麼都不寫,像代碼1所示的那樣,編譯器會認爲咱們不須要捕獲任何變量。string
除了顯式指定想要捕獲的變量,C++還要求咱們指定這些變量的傳遞方式,能夠選擇的傳遞方式有兩種:按值傳遞和按引用傳遞。像[&odd_count] 這種寫法是按引用傳遞,這種傳遞方式使得你能夠在Lambda的函數體裏對odd_count變量進行修改。相對的,若是變量名字前面沒有加上"&"就是按值傳遞,這些變量在Lambda的函數體裏是隻讀的,www.189works.com。it
若是你但願按引用傳遞捕獲當前上下文的全部變量,能夠把捕獲子句寫成[&];若是你但願按值傳遞捕獲當前上下文的全部變量,能夠把捕獲子句寫成[=]。若是你但願把按引用傳遞設爲默認的傳遞方式,同時指定個別變量按值傳遞,能夠把捕獲子句寫成[&, a, b];同理;若是默認的傳遞方式是按值傳遞,個別變量按引用傳遞,能夠把捕獲子句寫成[=, &a, &b]。值得提醒的是,像[&, a, &b]和[=, &a, b]這些寫法是無效的,由於默認的傳遞方式均已覆蓋b變量,無需單獨指定,有效的寫法應該是[&, a]和[=, &a]。io
生成等差數列編譯
如今咱們把一開始的問題改一下,經過generate函數生成一個首項爲0,公差爲2的等差數列。有了前面關於捕獲子句的知識,咱們很容易想到代碼3這個方案,首先按引用傳遞捕獲i變量,而後在Lambda的函數體裏修改它的值,並返回給generate函數。
代碼 3
若是咱們把i變量的傳遞方式改爲按值傳遞,而後在捕獲子句後面加上mutable聲明,如代碼4所示,咱們能夠獲得相同的效果,我指的是輸出結果。那麼,這兩個方案有什麼不同呢?調用generate函數以後檢查一下i變量的值就會找到答案了。須要說明的是,若是咱們加上mutable聲明,參數列表就不能省略了,即便裏面沒有包含任何參數。
代碼 4
使用代碼3這個方案,i變量的值在調用generate函數以後是18,而使用代碼4這個方案,i變量的值是-2。這個意味着mutable聲明使得咱們能夠在Lambda的函數體修改按值傳遞的變量,但這些修改對Lambda之外的世界是不可見的,有趣的是,這些修改在Lambda的屢次調用之間是共享的。換句話說,代碼4的generate函數調用了10次Lambda,前一次調用時對i變量的修改結果能夠在後一次調用時訪問獲得,www.189works.com。
這聽起來就像有個對象,i變量是它的成員字段,而Lambda則是它的成員函數,事實上,Lambda是函數對象(Function Object)的語法糖,代碼4的Lambda最終會被轉換成代碼5所示的Functor類。
代碼 5
你也能夠把代碼4的Lambda替換成Functor類,如代碼6所示。
代碼 6
如何聲明Lambda的類型?
到目前爲止,咱們都是把Lambda做爲參數直接傳給函數的,若是咱們想把一個Lambda傳給多個函數,或者把它看成一個函數屢次調用,那麼就得考慮把它存到一個變量裏了,問題是這個變量應該如何聲明呢?若是你確實不知道,也不想知道,那麼最簡單的辦法就是交給編譯器處理,如代碼7所示,這裏的auto關鍵字至關於C#的var,編譯器會根據咱們用來初始化f1變量的值推斷它的實際類型,這個過程是靜態的,在編譯時完成。
代碼 7
若是咱們想定義一個接受代碼7的Lambda做爲參數的函數,那麼這個參數的類型又該如何寫呢?咱們能夠把它聲明爲function模板類型,如代碼8所示,裏面的類型參數反映了Lambda的簽名——兩個int參數,一個int返回值。
代碼 8
此外,你也能夠把這個函數聲明爲模板函數,如代碼9所示。
代碼 9
不管你如何聲明這個函數,調用的時候都是同樣的,並且它們都能接受Lambda或者函數對象做爲參數,如代碼10所示。
代碼 10
捕獲變量的值何時肯定?
如今,我要把代碼7的Lambda調整成代碼11所示的那樣,經過捕獲子句而不是參數列表提供輸入,這兩個參數分別使用不一樣的傳遞方式,那麼,我在第三行修改這兩個參數的值會否對第四行的調用產生影響?
代碼 11
若是你運行代碼11,你將會看到輸出結果是5。爲何?這是由於按值傳遞在聲明Lambda的那一刻就已經肯定變量的值了,不管以後外面怎麼修改,裏面只能訪問到聲明時傳過來的版本;而按引用傳遞則恰好相反,裏面和外面看到的是同一個東西,所以在調用Lambda以前外面的任何修改對裏面都是可見的。這種問題在C#裏是沒有的,由於C#只有按引用傳遞這種方式。
返回值的類型何時能夠省略?
最後,咱們一直沒有提到返回值的類型,編譯器會一直幫咱們自動推斷嗎?不會,只有兩種狀況能夠在聲明Lambda時省略返回值類型,而前面的例子恰好都知足這兩種狀況,所以推到如今才說:
當你須要加上返回值的類型時,必須把它放在參數列表後面,而且在返回值類型前面加上"->"符號,如代碼12所示。
代碼 12
*以上代碼均在Visual Studio 2010和Visual Studio 2012 RC上測試經過。