天天看點

轉 Lambda表達式解析

cocos 2d-x 3.0 版本中引入了c++ 11的特性。其中就包含了回調函中使用lambda對象。

lambda表達式是一個匿名函數,lambda表達式基于數學中的λ演算得名,直接對應于其中的lambda抽象,是一個匿名函數,即沒有函數名的函數。

下面我們來看一段testcpp中的代碼:

轉 Lambda表達式解析

在上圖的觸摸事件的回調函數中,共使用了三次lambda表達式:

[ ](touch * touch,event * event){ };

下面我們就來介紹一下lambda表達式的使用方法。

正常情況下,如果我們需要在很多地方使用相同的操作,通常應該定義一個函數來實作這個功能。

而有些時候,我們隻需要在一兩個地方使用到一些簡單的操作,而又不想去定義這個函數名,那麼此時便可以lambda表達式來實作我們的功能。

一個完整的lambda表達式的表達形式如下:

[capture list](parameter list)->return type (function body)

[捕獲清單] (參數清單) ->傳回類型 (函數體)

那麼為什麼圖中的lambda表達式的形式與上述的形式不一樣呢?

原因是 lambda表達式的參數清單和傳回類型是和可以忽略的,但是捕獲清單和函數體一定要包含。

也就是說,lambda表達式實際上就是一個匿名函數,它的優點與内聯函數(又稱内嵌函數、内置函數)類似,但lambda表達式可能會定義在函數内部。

那麼内聯函數的優點是什麼呢?我們舉個例子來說明,比如我們的程式中有這樣一段代碼:

void a(){

....//函數體略

}

void main(){

.... //省略

a( );//調用a函數

上述代碼執行的主要過程如下:

1.主調函數main執行完調用a函數前的語句後,在轉去調用a函數前,首先需要記錄目前執行的指令位址,也就是做一個“保護現場”的操作,用于執行完a函數後繼續執行後續代碼。

2.然後流程的控制會被轉移到a函數的入口,并且執行a函數中的函數體内的語句.

3.執行完成後,流程才會傳回到之前記錄的位址處,并且根據之前所記錄的資訊做恢複現場操作,保證程式正常執行。

上述過程的每一個操作都需要花費一定的時間,如果a函數需要被頻繁的使用,那麼我們花費的時間就會很長,進而造成效率降低。

為了解決這個問題,c++為我們提供的了内聯函數,所謂内聯函數,就是通過将一個函數聲明為inline function,進而達到在編譯的過程中,直接将所調用的函數的函數體部分直接拷貝到主調函數,而不需要将流程轉到這個函數中去,以此來減少程式的運作時間。

這是因為當一個函數的函數體規模很小的時候,函數調用過程中的時間開銷會超過執行函數所需要的時間。

這就是使用内聯函數的好處,而對于lambda表達式,我們可以将它了解為一個未命名的内聯函數。

下面我們對lambda表達式的形式進行逐一分析:

1.“ [捕獲清單] ”

首先我們觀察一下上圖中的第一個lambda表達式與第三個lambda表達式的捕獲清單部分的差別。

可以看到,上圖的第一個表達式中捕獲清單為空 [ ],而第三個表達式中的捕獲清單中包含了一個等号 [=]。

下面我們再觀察一下上圖中第一個與第三個lambda表達式的函數體内都使用到了哪些變量。

可以看到,第一個表達式中所有的變量,均是在lambda表達式中定義的(log除外,因為log函數包含在頭檔案中),

而在第三個表達式中所使用到的sprite1,sprite2等變量,并不是在lambda表達式中定義的,而是目前函數中或是目前類中的變量。

那麼我們就可以總結出,在lambda表達式的函數體内,是不能夠通路到外部的變量的,如果想要使用函數體外定義的變量,就需要将它們進行捕獲,上圖第三個lambda表達式采用的正是“值捕獲”,與它對應的另外一種為“引用捕獲”。

[ ]:空捕獲清單,即lambda表達式不能夠使用所在函數中的變量

[=]:值捕獲,即lambda表達式可以以拷貝的方式通路到函數中變量的值

[&]:引用捕獲,即lambda表達式中所使用的其所在函數中的變量均是引用方式

當我們不希望在捕獲的時候将所有的變量都捕獲的時候,我們可以使用如下的方式進行捕獲,例如:

[=sprite1,&sprite2]

這裡我們僅僅捕獲了兩個變量,第一個變量是以值拷貝的方式捕獲,第二個是以引用方式捕獲,變量與變量之間用逗号分隔。

正常情況下,如果一個變量是值拷貝,lambda不能改變它的值,如果我們希望改變一個值拷貝的變量的值,就需要在參數清單前加上關鍵字mutable

例如:

auto s1=10;

auto s2=[=s1](){return ++s1};//錯誤,因為s1是值拷貝,不能改變s1的值

auto s2=[=s1]() mutable {return ++s1};//正确

2.(參數清單)

lambda表達式傳遞參數時需要注意的是,lambda表達式不能有預設參數,也就是說lambda表達式的實參數與形參數必須相等。

其他情況lambda表達式的參數部分與普通函數并無差別,一般會結合stl使用。

例如:

void test(){

vector myvec; //建立一個int 類型容器

myvec.push_back(1); //插入資料 1

myvec.push_back(2);//插入資料 2

int a=10; //建立局部變量 a

for_each(myvec.begin(),myvec.end(),[&](int v)mutable(cout<

cout<

3.->return type

之間我們已經提到,lambda的傳回值是可以省略的。

原因是編譯器會根據return的類型來推導傳回值,但是如果需要return後再做一個類型轉換,我們就可以通過寫一個傳回類型來完成。

例如:cout<<[](float f){return f}(1.5); //這裡我們将1.5作為參數傳入并列印,傳回結果就是實參的值1.5

cout<<[](float f)->int{return f}(1.5); //我們将傳回值強制轉換為int 輸出結果為1

4.函數體

函數體部分與普通函數并無差別,我們隻需要注意以上幾點,在函數體部分就不會出現問題。

現在再回頭看看testcpp中的觸摸事件,我們就可以明白其中的道理了。

繼續閱讀