lua學習筆記之詳解lua堆疊
1. Lua的堆疊和全域性表
我們來簡單解釋一下Lua的堆疊和全域性表,堆疊大家應該會比較熟悉,它主要是用來讓C++和Lua通訊的,是的,它們並不認識對方,只能通過堆疊來溝通,就像寫信一樣。
(旁白:它們不會用微信嗎?!微信~!不知道?)
Lua的全域性表又是什麼呢?可以想象成是一個map雜湊表結構,比如Lua有一個變數:
name = “hello”
那麼,全域性表就存放了”name”和”hello”的對應關係,Lua可以通過name在全域性表中查詢到hello。應該是這樣的~
(旁白:應該= =!)
2. Lua和C++的第一次通訊
現在來設計一個場景,C++在一次JavaScript
(旁白:JavaScript大會...那為毛是Lua在演講~!)
正文:
在這裡我僅簡單解釋一下Lua堆疊的索引,因為我們在很多操作裡都涉及到堆疊的索引,比如上一章中我們要從堆疊中取得一個字串,就必須給出堆疊索引:
[cpp] view plaincopyprint?- /* 獲取棧頂的值 */
- constchar* str = lua_tostring(pL, 1);
/* 獲取棧頂的值 */
const char* str = lua_tostring(pL, 1);
如果對堆疊索引不清晰的話,將會很糾結。
《遊戲人工智慧程式設計案例精粹》一書的200
(旁白:好醜~!而且還打了一個廣告,別以為我不知道~!)
我們很明顯的看到堆疊的索引方式有兩種,一種是正數索引,一種是負數索引。
並且咋一看,好像兩種索引方式的規則是相反的,其實不然,我們來認真數數:
1. 正數索引,棧底是1,然後一直到棧頂是逐漸+1,最後變成9(9大於1)
2. 負數索引,棧底是-9,然後一直到棧頂是逐漸+1,最後變成-1(-1大於-9)
(旁白:這,這還真的是一樣的~!好神奇!)
對吧,一般像旁白那種人才會認為是相反的規則。
(旁白:吐槽是我的專利= =!)
大家不覺得奇怪嗎?為什麼要用兩種方式?好混亂~
我也覺得,但是有一點好處,看看它們各自的好處:
1. 正數索引,不需要知道棧的大小,我們就能知道棧底在哪,棧底的索引永遠是1
2. 負數索引,不需要知道棧的大小,我們就能知道棧頂在哪,棧頂的索引永遠是-1
(旁白:又好像有那麼一點道理。。。)
1. 什麼是table
table是Lua裡最強大的資料型別,我們可以當成是陣列,但是它又和陣列有點不一樣,建議大家看看Lua的語法教程,因為我對table也沒有熟悉到可以給大家解釋的程度。
(旁白:那你還寫什麼教程。。。)
2. 獲取table變數
現在,我們給helloLua.lua檔案新增一個table全域性變數:
[cpp] view plaincopyprint?- -- helloLua.lua檔案
- myName = "beauty girl"
- helloTable = {name = "mutou", IQ = 125}
-- helloLua.lua檔案
myName = "beauty girl"
helloTable = {name = "mutou", IQ = 125}
我們看到,多了一個helloTable的變數,它和陣列十分相似,又和HashMap有點類似,總之它很強大。
(旁白:我覺得亮點是,你的IQ有125?我覺得乘以2的話,還有點可能~!)
話說,125乘以2等於多少?...250 ....O O!
獲取helloTable變數的方式和以前是一樣的:
[cpp] view plaincopyprint?- /* 取得table變數,在棧頂 */
- lua_getglobal(pL, "helloTable");
/* 取得table變數,在棧頂 */
lua_getglobal(pL, "helloTable");
這樣,helloTable變數就被存放到棧頂。
可我們並不是要取table變數,因為C++中是無法識別Lua的table型別的,所以我們要取得table中具體的值,也就是name和IQ的值。
3. lua_gettable函式
有一個和lua_getglobal類似的函式,叫做lua_gettable,顧名思義,它是用來取得table相關的資料的。
(旁白:廢話少點好吧= =)
lua_gettable函式會從棧頂取得一個值,然後根據這個值去table中尋找對應的值,最後把找到的值放到棧頂。
lua_pushstring()函式可以把C++中的字串存放到Lua的棧裡;
然後再用lua_gettable()取執行前面所說的步驟,lua_gettable的第二個引數是指定的table變數在棧中的索引。
(旁白:小笨木,我被你繞暈了。。。)
為了照顧旁白這個笨蛋,我們畫個圖來理解:
這是初始狀態,堆疊裡還沒有任何東西,那麼,現在要先把helloTable變數放到棧頂:
[cpp] view plaincopyprint?- /* 取得table變數,在棧頂 */
- lua_getglobal(pL, "helloTable");
/* 取得table變數,在棧頂 */
lua_getglobal(pL, "helloTable");
然後就變成了這樣:
接著,我們要取得table的name對應的值,那麼,先要做的就是把”name”字串入棧:
[cpp] view plaincopyprint?- /* 將C++的字串放到Lua的棧中,此時,棧頂變為“name”, helloTable物件變為棧底 */
- lua_pushstring(pL, "name");
/* 將C++的字串放到Lua的棧中,此時,棧頂變為“name”, helloTable物件變為棧底 */
lua_pushstring(pL, "name");
然後變成這樣:
(旁白:不帶這樣啊,你偷偷加上了棧的索引~!)
注意了,我把棧的索引也加上了,因為我們即將要使用,這次我們用負數索引(不瞭解負數的索引的朋友請閱讀第03章的教程哈~)。
由於”name”的入棧,現在helloTable變數已經不在棧頂了。
接著,我們呼叫要做最重要的一步了,取得name在table中對應的值:
[cpp] view plaincopyprint?- /*
- 從table物件尋找“name”對應的值(table物件現在在索引為-2的棧中,也就是當前的棧底),
- 取得對應值之後,將值放回棧頂
- */
- 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?- /* 初始化 */
- 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對應的值已經在棧頂了,直接取出即可 */
- constchar* sName = lua_tostring(pL, -1);
- 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?- -- hello.lua 檔案
- myName = "beauty girl"
-- hello.lua 檔案
myName = "beauty girl"
OK,一位簡單又美麗Lua小姐。
然後,C++想知道Lua叫什麼名字,所以,它們必須要通訊了。來看看通訊流程:
請注意紅色數字,代表通訊順序:
1) C++想獲取Lua的myName字串的值,所以它把myName放到Lua堆疊(棧頂),以便Lua能看到
2) Lua從堆疊(棧頂)中獲取myName,此時棧頂再次變為空
3) Lua拿著這個myName去Lua全域性表查詢myName對應的字串
4) 全域性表返回一個字串”beauty girl”
5) Lua把取得的“beauty girl”字串放到堆疊(棧頂)
6) C++可以從Lua堆疊中取得“beauty girl”,也就是這位美麗的Lua小姐的名字了~
世界如此美妙,這是如此的簡單。
(旁白:好吧,這次不吐槽,確實簡單...)
不過,(旁白:我就知道~!我就知道事情沒有那麼簡單!)這只是最簡單的情況,實際上各種C++和Lua的操作比這要複雜多了,但基本原理是一樣的。
好的,趁著旁白還沒有吐槽,我們要結束第一章了,希望能幫到大家。
(旁白:說得好像我好喜歡搶戲似的,我像這樣的人嗎?= = 等等~!例子呢?Demo呢?)
噢,Demo將在下一章介紹。
(旁白:吊胃口。。。絕對是在吊胃口...你以為我會期待嗎,魂淡...心好癢~!)
1. 引入標頭檔案
我們來看看要在C++中使用Lua,需要些什麼東西
[cpp] view plaincopyprint?- /*
- 檔名: 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>
- };
- usingnamespace cocos2d;
- class HelloLua : public CCLayer {
- public:
- CREATE_FUNC(HelloLua);
- virtualbool init();
- static CCScene* scene();
- };
- #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>
};
(旁白:你妹紙的...你不能先貼出來再問嗎?~!)
記住了,Lua是C語言庫,所以在C++中使用必須用extern “C”宣告,讓編譯器知道。
有了這些,我們就能開始使用Lua了。
(旁白:等等,總感覺有點不對勁= =)
啊,對了,還少一樣東西,不過這個不需要我們做了,那就是引入Lua的庫,沒有庫,我們怎麼包含標頭檔案都沒用。
不過沒關係,Cocos2d-x本來就支援Lua,所以這一步我們省下了,為了保險起見,我在新建Demo專案的時候勾選了支援Lua。
建議大家首先能建立一個支援Lua的Cocos2d-x專案,並且能編譯執行,然後再繼續往下看~
(旁白:你就不能教我們引入Lua庫麼?= =)
我教?我不懂~
2. 開始使用
來看看我們的cpp檔案,我們要開始使用Lua了~!
[cpp] view plaincopyprint?- #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);
- returntrue;
- }
#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?- 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);
- returntrue;
- }
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?- -- helloLua.lua檔案
- 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這個全域性變數的值,但並不是這樣的,雖然最終也是這樣。
(旁白:你妹紙的,說清楚點)
我們之前說過了,Lua和C++是不能直接通訊的,要通過堆疊來通訊。
因此,lua_getglobal(pL, “myName”);只是把myName放到了棧中,然後lua就會通過myName去全域性表尋找,找到myName對應的字串“beauty girl”,再放到棧中。(第01章的時候介紹過的步驟,還記得嗎?不記得的建議大家去看看~)
(旁白:停!讓我緩衝一下...)
(旁白:
1.C++把myName放到堆疊
2.lua從堆疊取得myName
3.lua用myName去lua全域性表查詢獲取myName對應的字串,得到“beauty girl”字串,然後再放回堆疊
4.最後C++就可以從堆疊中取得“beauty girl”字串?
好~!明白了~)
5. 最後一步,C++取得字串
我們來看看完整的程式碼:
[cpp] view plaincopyprint?- 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.獲取棧頂的值 */
- constchar* str = lua_tostring(pL, 1);
- CCLOG("getStr = %s", str);
- lua_close(pL);
- returntrue;
- }
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_isstring、lua_isnumber等等,和lua_tostring等函式是對應的。返回非0值表示型別正確。
一般在取值之前都要判斷一下,不能程式很可能意外崩潰~!
(旁白:嚇 = =!)
好~本章到此結束~
6. 贈送的
最後再告訴大家一個笑眯眯~
那就是用lua_pop(pL, 1); 可以清除指定堆疊上的資料~
噗,閃人~
(旁白:我幫他解釋一下。。。是小祕密。。。不是笑眯眯= =)