1. 程式人生 > >lua學習筆記之詳解lua堆疊

lua學習筆記之詳解lua堆疊

1. Lua的堆疊和全域性表

我們來簡單解釋一下Lua的堆疊和全域性表,堆疊大家應該會比較熟悉,它主要是用來讓C++Lua通訊的,是的,它們並不認識對方,只能通過堆疊來溝通,就像寫信一樣。

(旁白:它們不會用微信嗎?!微信~!不知道?)

Lua的全域性表又是什麼呢?可以想象成是一個map雜湊表結構,比如Lua有一個變數:

name = “hello”

那麼,全域性表就存放了”name”和”hello”的對應關係,Lua可以通過name在全域性表中查詢到hello。應該是這樣的~

(旁白:應該= =!)

2. LuaC++的第一次通訊

現在來設計一個場景,C++在一次JavaScript

開發者大會上看到Lua在演講,於是C++Lua深深吸引了。

(旁白:JavaScript大會...那為毛是Lua在演講~!)

正文:

在這裡我僅簡單解釋一下Lua堆疊的索引,因為我們在很多操作裡都涉及到堆疊的索引,比如上一章中我們要從堆疊中取得一個字串,就必須給出堆疊索引:

[cpp] view plaincopyprint?
  1. /* 獲取棧頂的值 */
  2. constchar* str = lua_tostring(pL, 1);  
/* 獲取棧頂的值 */
    const char* str = lua_tostring(pL, 1);

如果對堆疊索引不清晰的話,將會很糾結。

《遊戲人工智慧程式設計案例精粹》一書的200

,有一張圖,很好地表達了Lua的堆疊索引是如何定義的,我照著畫了一張:

(旁白:好醜~!而且還打了一個廣告,別以為我不知道~!)

我們很明顯的看到堆疊的索引方式有兩種,一種是正數索引,一種是負數索引。

並且咋一看,好像兩種索引方式的規則是相反的,其實不然,我們來認真數數:

1. 正數索引,棧底是1,然後一直到棧頂是逐漸+1,最後變成99大於1

2. 負數索引,棧底是-9,然後一直到棧頂是逐漸+1,最後變成-1-1大於-9

(旁白:這,這還真的是一樣的~!好神奇!)

對吧,一般像旁白那種人才會認為是相反的規則。

(旁白:吐槽是我的專利= =!)

大家不覺得奇怪嗎?為什麼要用兩種方式?好混亂~

我也覺得,但是有一點好處,看看它們各自的好處:

1. 正數索引,不需要知道棧的大小,我們就能知道棧底在哪,棧底的索引永遠是1

2. 負數索引,不需要知道棧的大小,我們就能知道棧頂在哪,棧頂的索引永遠是-1

(旁白:又好像有那麼一點道理。。。)

1. 什麼是table

tableLua裡最強大的資料型別,我們可以當成是陣列,但是它又和陣列有點不一樣,建議大家看看Lua的語法教程,因為我對table也沒有熟悉到可以給大家解釋的程度。

(旁白:那你還寫什麼教程。。。)

2. 獲取table變數

現在,我們給helloLua.lua檔案新增一個table全域性變數:

[cpp] view plaincopyprint?
  1. -- helloLua.lua檔案  
  2. myName = "beauty girl"
  3. helloTable = {name = "mutou", IQ = 125}  
-- helloLua.lua檔案
myName = "beauty girl"

helloTable = {name = "mutou", IQ = 125}

我們看到,多了一個helloTable的變數,它和陣列十分相似,又和HashMap有點類似,總之它很強大。

(旁白:我覺得亮點是,你的IQ125?我覺得乘以2的話,還有點可能~!)

話說,125乘以2等於多少?...250 ....O O

獲取helloTable變數的方式和以前是一樣的:

[cpp] view plaincopyprint?
  1. /* 取得table變數,在棧頂 */
  2.     lua_getglobal(pL, "helloTable");  
/* 取得table變數,在棧頂 */
    lua_getglobal(pL, "helloTable");

這樣,helloTable變數就被存放到棧頂。

可我們並不是要取table變數,因為C++中是無法識別Luatable型別的,所以我們要取得table中具體的值,也就是nameIQ的值。

3. lua_gettable函式

有一個和lua_getglobal類似的函式,叫做lua_gettable,顧名思義,它是用來取得table相關的資料的。

(旁白:廢話少點好吧= =

lua_gettable函式會從棧頂取得一個值,然後根據這個值去table中尋找對應的值,最後把找到的值放到棧頂。

lua_pushstring()函式可以把C++中的字串存放到Lua的棧裡;

然後再用lua_gettable()取執行前面所說的步驟,lua_gettable的第二個引數是指定的table變數在棧中的索引。

(旁白:小笨木,我被你繞暈了。。。)

為了照顧旁白這個笨蛋,我們畫個圖來理解:

這是初始狀態,堆疊裡還沒有任何東西,那麼,現在要先把helloTable變數放到棧頂:

[cpp] view plaincopyprint?
  1. /* 取得table變數,在棧頂 */
  2.     lua_getglobal(pL, "helloTable");  
/* 取得table變數,在棧頂 */
    lua_getglobal(pL, "helloTable");

然後就變成了這樣:

接著,我們要取得tablename對應的值,那麼,先要做的就是把”name”字串入棧:

[cpp] view plaincopyprint?
  1. /* 將C++的字串放到Lua的棧中,此時,棧頂變為“name”, helloTable物件變為棧底 */
  2.     lua_pushstring(pL, "name");  
/* 將C++的字串放到Lua的棧中,此時,棧頂變為“name”, helloTable物件變為棧底 */
    lua_pushstring(pL, "name");

然後變成這樣:

(旁白:不帶這樣啊,你偷偷加上了棧的索引~!)

注意了,我把棧的索引也加上了,因為我們即將要使用,這次我們用負數索引(不瞭解負數的索引的朋友請閱讀第03章的教程哈~)。

由於”name”的入棧,現在helloTable變數已經不在棧頂了。

接著,我們呼叫要做最重要的一步了,取得nametable中對應的值:

[cpp] view plaincopyprint?
  1. /*  
  2.         從table物件尋找“name”對應的值(table物件現在在索引為-2的棧中,也就是當前的棧底), 
  3.         取得對應值之後,將值放回棧頂 
  4.     */
  5.     lua_gettable(pL, -2);  
/* 
        從table物件尋找“name”對應的值(table物件現在在索引為-2的棧中,也就是當前的棧底),
        取得對應值之後,將值放回棧頂
    */
    lua_gettable(pL, -2);

此時,棧變成這樣:

(旁白:發生什麼事?為什麼“mutou”突然出現在棧頂?!為毛!是你自己畫上去的吧!)

lua_gettable倒底做了什麼事情?

首先,我們來解釋一下lua_gettable的第二個引數,-2是什麼意思,-2就是剛剛helloTable變數在棧中的索引。

然後,Lua會去取得棧頂的值(之前的棧頂是”name”),然後拿著這個值去helloTable變數中尋找對應的值,當然就找到”mutou”了。

最後,Lua會把找到的值入棧,於是”mutou”就到了棧頂了。

(旁白:你妹紙的。。。沒事,我就罵罵人)

最後我們只需要取出棧頂的資料就可以了,完整程式碼如下:

[cpp] view plaincopyprint?
  1. /* 初始化 */
  2.     lua_State* pL = lua_open();  
  3.     luaopen_base(pL);  
  4. /* 執行指令碼 */
  5.     luaL_dofile(pL, "helloLua.lua");  
  6. /* 取得table變數,在棧頂 */
  7.     lua_getglobal(pL, "helloTable");  
  8. /* 將C++的字串放到Lua的棧中,此時,棧頂變為“name”, helloTable物件變為棧底 */
  9.     lua_pushstring(pL, "name");  
  10. /*  
  11.         從table物件尋找“name”對應的值(table物件現在在索引為-2的棧中,也就是當前的棧底), 
  12.         取得對應值之後,將值放回棧頂 
  13.     */
  14.     lua_gettable(pL, -2);  
  15. /* 現在表的name對應的值已經在棧頂了,直接取出即可 */
  16. constchar* sName = lua_tostring(pL, -1);  
  17.     CCLOG("name = %s", sName);  
/* 初始化 */
    lua_State* pL = lua_open();
    luaopen_base(pL);

    /* 執行指令碼 */
    luaL_dofile(pL, "helloLua.lua");

    /* 取得table變數,在棧頂 */
    lua_getglobal(pL, "helloTable");

    /* 將C++的字串放到Lua的棧中,此時,棧頂變為“name”, helloTable物件變為棧底 */
    lua_pushstring(pL, "name");

    /* 
        從table物件尋找“name”對應的值(table物件現在在索引為-2的棧中,也就是當前的棧底),
        取得對應值之後,將值放回棧頂
    */
    lua_gettable(pL, -2);

    /* 現在表的name對應的值已經在棧頂了,直接取出即可 */
    const char* sName = lua_tostring(pL, -1);
    CCLOG("name = %s", sName);

好了,本章到此結束。

我們來看看這位美麗的Lua小姐長什麼樣:

[plain] view plaincopyprint?
  1. -- hello.lua 檔案  
  2. myName = "beauty girl"  
-- hello.lua 檔案
myName = "beauty girl"

OK,一位簡單又美麗Lua小姐。

然後,C++想知道Lua叫什麼名字,所以,它們必須要通訊了。來看看通訊流程:

請注意紅色數字,代表通訊順序:

1) C++想獲取LuamyName字串的值,所以它把myName放到Lua堆疊(棧頂),以便Lua能看到

2) Lua從堆疊(棧頂)中獲取myName,此時棧頂再次變為空

3) Lua拿著這個myNameLua全域性表查詢myName對應的字串

4) 全域性表返回一個字串”beauty girl”

5) Lua把取得的“beauty girl”字串放到堆疊(棧頂)

6) C++可以從Lua堆疊中取得“beauty girl”,也就是這位美麗的Lua小姐的名字了~

世界如此美妙,這是如此的簡單。

(旁白:好吧,這次不吐槽,確實簡單...

不過,(旁白:我就知道~!我就知道事情沒有那麼簡單!)這只是最簡單的情況,實際上各種C++Lua的操作比這要複雜多了,但基本原理是一樣的。

好的,趁著旁白還沒有吐槽,我們要結束第一章了,希望能幫到大家。

(旁白:說得好像我好喜歡搶戲似的,我像這樣的人嗎?= = 等等~!例子呢?Demo?

噢,Demo將在下一章介紹。

(旁白:吊胃口。。。絕對是在吊胃口...你以為我會期待嗎,魂淡...心好癢~!)

1. 引入標頭檔案

我們來看看要在C++中使用Lua,需要些什麼東西

[cpp] view plaincopyprint?
  1. /*  
  2.    檔名:    HelloLua.h  
  3.    描 述:    Lua Demo 
  4.    建立人:    笨木頭 (CSDN部落格:http://blog.csdn.net/musicvs)  
  5.    建立日期:   2012.12.24  
  6. */
  7. #ifndef __HELLO_LUA_H_
  8. #define __HELLO_LUA_H_
  9. #include "cocos2d.h"
  10. extern"C" {  
  11. #include <lua.h>
  12. #include <lualib.h>
  13. #include <lauxlib.h>
  14. };  
  15. usingnamespace cocos2d;  
  16. class HelloLua : public CCLayer {  
  17. public:  
  18.     CREATE_FUNC(HelloLua);  
  19. virtualbool init();  
  20. static CCScene* scene();  
  21. };  
  22. #endif
/* 
   檔名:    HelloLua.h 
   描 述:    Lua Demo
   建立人:    笨木頭 (CSDN部落格:http://blog.csdn.net/musicvs) 

   建立日期:   2012.12.24 
*/  

#ifndef __HELLO_LUA_H_
#define __HELLO_LUA_H_

#include "cocos2d.h"

extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
};

using namespace cocos2d;

class HelloLua : public CCLayer {
public:
    CREATE_FUNC(HelloLua);
    virtual bool init();

    static CCScene* scene();
};

#endif

看到紅色粗體的程式碼了嗎?(旁白:在哪呢?在哪啊?)

在這:

extern "C" {

#include <lua.h>

#include <lualib.h>

#include <lauxlib.h>

};

(旁白:你妹紙的...你不能先貼出來再問嗎?~!)

記住了,LuaC語言庫,所以在C++中使用必須用extern “C”宣告,讓編譯器知道。

有了這些,我們就能開始使用Lua了。

(旁白:等等,總感覺有點不對勁= =

啊,對了,還少一樣東西,不過這個不需要我們做了,那就是引入Lua的庫,沒有庫,我們怎麼包含標頭檔案都沒用。

不過沒關係,Cocos2d-x本來就支援Lua,所以這一步我們省下了,為了保險起見,我在新建Demo專案的時候勾選了支援Lua

建議大家首先能建立一個支援LuaCocos2d-x專案,並且能編譯執行,然後再繼續往下看~

(旁白:你就不能教我們引入Lua庫麼?= =

我教?我不懂~

2. 開始使用

來看看我們的cpp檔案,我們要開始使用Lua~

[cpp] view plaincopyprint?
  1. #include "HelloLua.h"
  2. CCScene* HelloLua::scene() {  
  3.     CCScene* scene = CCScene::create();  
  4.     CCLayer* layer = HelloLua::create();  
  5.     scene->addChild(layer);  
  6. return scene;  
  7. }  
  8. bool HelloLua::init() {  
  9.     lua_State* pL = lua_open();  
  10.     luaopen_base(pL);  
  11.     luaopen_math(pL);  
  12.     luaopen_string(pL);  
  13. /* 1.執行Lua指令碼,返回0代表成功 */
  14. /* 2.重置棧頂索引 */
  15. /* 3.判斷棧頂的值的型別是否為String, 返回非0值代表成功 */
  16. /* 4.獲取棧頂的值 */
  17.     lua_close(pL);  
  18. returntrue;  
  19. }  
#include "HelloLua.h"

CCScene* HelloLua::scene() {
    CCScene* scene = CCScene::create();
    CCLayer* layer = HelloLua::create();
    scene->addChild(layer);

    return scene;
}

bool HelloLua::init() {
    lua_State* pL = lua_open();
    luaopen_base(pL);
    luaopen_math(pL);
    luaopen_string(pL);

    /* 1.執行Lua指令碼,返回0代表成功 */
    /* 2.重置棧頂索引 */
    /* 3.判斷棧頂的值的型別是否為String, 返回非0值代表成功 */
  /* 4.獲取棧頂的值 */
  
    lua_close(pL);
    return true;
}

為了不一下子就一大堆程式碼嚇壞大家,我把部分程式碼先刪了,我們來看看現在這個程式碼的情況:

1) HelloLua是一個場景(旁白:廢話...

2) HelloLua有一個init函式(旁白:你妹紙的,進入正題好不?)

3) 我就喜歡旁白吐槽~(旁白:....

4) 要使用Lua,首先要有一個lua_State,這是什麼呢?我引用《遊戲人工智慧程式設計案例精粹》一書的一句話(191頁):“每一個執行的指令碼檔案都在一個動態分配的叫做lua_State的資料結構中執行”。不明白的話,也沒有關係,我們就把lua_State當成是一個Lua的身體,Lua在做任何事情的時候都不能沒有身體。

5) 接下來看到幾句話:luaopen_base(pL);luaopen_math(pL);luaopen_string(pL);

Lua有一些標準庫,要使用這些庫,就要用luaopen_**去載入這些庫

6) 然後最後還有一句話:lua_close(pL),一看就知道了,用來釋放記憶體的。

7) 旁白呢?(旁白:心情不好...不想吐槽)

3. 執行Lua指令碼

現在我們來一步步完善我們的程式碼,執行Lua指令碼很簡單,看看:

[cpp] view plaincopyprint?
  1. bool HelloLua::init() {  
  2.     lua_State* pL = lua_open();  
  3.     luaopen_base(pL);  
  4.     luaopen_math(pL);  
  5.     luaopen_string(pL);  
  6. /* 1.執行Lua指令碼,返回0代表成功 */
  7. int err = luaL_dofile(pL, "helloLua.lua");  
  8.     CCLOG("open : %d", err);  
  9. /* 2.重置棧頂索引 */
  10.     lua_settop(pL, 0);  
  11.     lua_getglobal(pL, "myName");  
  12. /* 3.判斷棧頂的值的型別是否為String, 返回非0值代表成功 */
  13. /* 4.獲取棧頂的值 */
  14.     lua_close(pL);  
  15. returntrue;  
  16. }  
bool HelloLua::init() {
    lua_State* pL = lua_open();
    luaopen_base(pL);
    luaopen_math(pL);
    luaopen_string(pL);

    /* 1.執行Lua指令碼,返回0代表成功 */
    int err = luaL_dofile(pL, "helloLua.lua");
    CCLOG("open : %d", err);

    /* 2.重置棧頂索引 */
    lua_settop(pL, 0);
    lua_getglobal(pL, "myName");

    /* 3.判斷棧頂的值的型別是否為String, 返回非0值代表成功 */
  /* 4.獲取棧頂的值 */
  
    lua_close(pL);
    return true;
}

(旁白:不吐槽都不行了。。。你是不是把第2步也不小心放出來了?= =

我們還要新建一個lua檔案,很簡單,新建一個文字檔案,把字尾名改為lua就行了。現在我們來建立一個helloLua.lua檔案:

[plain] view plaincopyprint?
  1. -- helloLua.lua檔案  
  2. myName = "beauty girl"  
-- helloLua.lua檔案
myName = "beauty girl"

(旁白:別老是忽略我好吧。。。第2步是怎麼回事?!)

好,lua檔案也有了,在C++中只要呼叫luaL_dofile就能執行lua指令碼了,注意了,必須把lua_State也作為引數傳給luaL_dofile,前面已經說了,身體不能少。

4. 重置棧頂索引, 將全域性變數放到堆疊中

大家沒有發現嗎?我把第2步也放出來了~

(旁白:啊喂~!我說了好多次了,我發現了啊~!)

lua_settop(pL, 0);是為了確認讓棧頂的索引置為0,因為我們操作棧的時候是根據索引來操作的。置0之後,我們入棧的第一個元素的索引就是1

那,lua_getglobal(pL, “myName”);又是什麼呢?咋一看好像是從lua中取得myName這個全域性變數的值,但並不是這樣的,雖然最終也是這樣。

(旁白:你妹紙的,說清楚點)

我們之前說過了,LuaC++是不能直接通訊的,要通過堆疊來通訊。

因此,lua_getglobal(pL, “myName”);只是把myName放到了棧中,然後lua就會通過myName去全域性表尋找,找到myName對應的字串“beauty girl”,再放到棧中。(第01章的時候介紹過的步驟,還記得嗎?不記得的建議大家去看看~

(旁白:停!讓我緩衝一下...

(旁白:

1.C++myName放到堆疊

2.lua從堆疊取得myName

3.luamyNamelua全域性表查詢獲取myName對應的字串,得到“beauty girl”字串,然後再放回堆疊

4.最後C++就可以從堆疊中取得“beauty girl”字串? 

~!明白了~

5. 最後一步,C++取得字串

我們來看看完整的程式碼:

[cpp] view plaincopyprint?
  1. bool HelloLua::init() {  
  2.     lua_State* pL = lua_open();  
  3.     luaopen_base(pL);  
  4.     luaopen_math(pL);  
  5.     luaopen_string(pL);  
  6. /* 1.執行Lua指令碼,返回0代表成功 */
  7. int err = luaL_dofile(pL, "helloLua.lua");  
  8.     CCLOG("open : %d", err);  
  9. /* 2.重置棧頂索引 */
  10.     lua_settop(pL, 0);  
  11.     lua_getglobal(pL, "myName");  
  12. /* 3.判斷棧頂的值的型別是否為String, 返回非0值代表成功 */
  13. int isstr = lua_isstring(pL, 1);  
  14.     CCLOG("isstr = %d", isstr);  
  15. /* 4.獲取棧頂的值 */
  16. constchar* str = lua_tostring(pL, 1);  
  17.     CCLOG("getStr = %s", str);  
  18.     lua_close(pL);  
  19. returntrue;  
  20. }  
bool HelloLua::init() {
    lua_State* pL = lua_open();
    luaopen_base(pL);
    luaopen_math(pL);
    luaopen_string(pL);

    /* 1.執行Lua指令碼,返回0代表成功 */
    int err = luaL_dofile(pL, "helloLua.lua");
    CCLOG("open : %d", err);

    /* 2.重置棧頂索引 */
    lua_settop(pL, 0);
    lua_getglobal(pL, "myName");

    /* 3.判斷棧頂的值的型別是否為String, 返回非0值代表成功 */
    int isstr = lua_isstring(pL, 1);
    CCLOG("isstr = %d", isstr);

    /* 4.獲取棧頂的值 */
    const char* str = lua_tostring(pL, 1);
    CCLOG("getStr = %s", str);

    lua_close(pL);
    return true;
}

lua_getglobal已經完成了很多工作了,現在堆疊上就放著“beauty girl”字串,我們只要去取就可以了。

獲取堆疊的值有很多種方法,分別對應不同的變數型別:

lua_toboolean

lua_toNumber

lua_tocfunction

lua_tostring

我就不全部舉例了,現在我們要用lua_tostring來獲取棧頂的值。

最後,在AppDelegate.cpp中把預設啟動場景設為我們的HelloLua場景,用除錯模式執行專案,將看到以下日誌:

open : 0

isstr = 1

getStr = beauty girl

好,本章到此結...(旁白:等等!第3步是什麼?你還沒有解釋啊,魂淡~!)

對了對了,Lua還提供了很多函式供我們判斷堆疊中的變數型別,比如lua_isstringlua_isnumber等等,和lua_tostring等函式是對應的。返回非0值表示型別正確。

一般在取值之前都要判斷一下,不能程式很可能意外崩潰~

(旁白:嚇 = =!)

~本章到此結束~

6. 贈送的

最後再告訴大家一個笑眯眯~

那就是用lua_pop(pL, 1); 可以清除指定堆疊上的資料~

噗,閃人~

(旁白:我幫他解釋一下。。。是小祕密。。。不是笑眯眯= =