cocos 2d-x 3.0 版本中引入了c++ 11的特性。其中就包含了回調函中使用lambda對象。
lambda表達式是一個匿名函數,lambda表達式基于數學中的λ演算得名,直接對應于其中的lambda抽象,是一個匿名函數,即沒有函數名的函數。
下面我們來看一段testcpp中的代碼:
在上圖的觸摸事件的回調函數中,共使用了三次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中的觸摸事件,我們就可以明白其中的道理了。