1. 程式人生 > >Cocos2d-x v3.0 新的事件排程方法 lambda表示式的使用

Cocos2d-x v3.0 新的事件排程方法 lambda表示式的使用

歡迎加入 Cocos2d-x 交流群: 193411763


轉載請註明原文出處:http://blog.csdn.net/u012945598/article/details/24603251


Cocos 2d-x 3.0 版本中引入了C++ 11的特性。其中就包含了回撥函中使用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<int> 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<<v+a<<endl;a++)); //將容器中元素作為引數傳到lambda表示式 輸出a+v結果為 11 13

       cout<<a<<endl;   //輸出a 結果為12

}

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中的觸控事件,我們就可以明白其中的道理了。