1. 數組操作:
在Lua中,“數組”只是table的一個別名,是指以一種特殊的方法來使用table。出於性能原因,Lua的C API為數組操作提供了專門的函數,如:
void lua_rawgeti(lua_State* L, int index, int key);
void lua_rawseti(lua_State* L, int index, int key);
以上兩個函數分別用於讀取和設置數組中的元素值。其中index參數表示待操作的table在棧中的位置,key表示元素在table中的索引值。由於這兩個函數均為原始操作,比涉及元表的table訪問更快。通常而言,作為數組使用的table很少會用到元表。
見如下代碼示例和關鍵性注釋:
1 #include <stdio.h> 2 #include <string.h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 extern "C" int mapFunc(lua_State* L) 8 { 9 //檢查Lua調用代碼中傳遞的第一個參數必須是table。否則將引發錯誤。 10 luaL_checktype(L,1,LUA_TTABLE); 11 luaL_checktype(L,2,LUA_TFUNCTION); 12 //獲取table中的字段數量,即數組的元素數量。 13 int n = lua_objlen(L,1); 14 //Lua中的數組起始索引習慣為1,而不是C中的0。 15 for (int i = 1; i <= n; ++i) { 16 lua_pushvalue(L,2); //將Lua參數中的function(第二個參數)的副本壓入棧中。 17 lua_rawgeti(L,1,i); //壓入table[i] 18 lua_call(L,1,1); //調用function(table[i]),並將函數結果壓入棧中。 19 lua_rawseti(L,1,i); //table[i] = 函數返回值,同時將返回值彈出棧。 20 } 21 22 //無結果返回給Lua代碼。 23 return 0; 24 }
2. 字符串操作:
當一個C函數從Lua收到一個字符串參數時,必須遵守兩條規則:不要在訪問字符串時從棧中將其彈出,不要修改字符串。在Lua的C API中主要提供了兩個操作Lua字符串的函數,即:
void lua_pushlstring(lua_State *L, const char *s, size_t l);
const char* lua_pushfstring(lua_State* L, const char* fmt, ...);
第一個API用於截取指定長度的子字符串,同時將其壓入棧中。而第二個API則類似於C庫中的sprintf函數,並將格式化后的字符串壓入棧中。和sprintf的格式說明符不同的是,該函數只支持%%(表示字符%)、%s(表示字符串)、%d(表示整數)、%f(表示Lua中的number)及%c(表示字符)。除此之外,不支持任何例如寬度和精度的選項。
1 #include <stdio.h> 2 #include <string.h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 extern "C" int splitFunc(lua_State* L) 8 { 9 const char* s = luaL_checkstring(L,1); 10 const char* sep = luaL_checkstring(L,2); //分隔符 11 const char* e; 12 int i = 1; 13 lua_newtable(L); //結果table 14 while ((e = strchr(s,*sep)) != NULL) { 15 lua_pushlstring(L,s,e - s); //壓入子字符串。 16 //將剛剛壓入的子字符串設置給table,同時賦值指定的索引值。 17 lua_rawseti(L,-2,i++); 18 s = e + 1; 19 } 20 //壓入最后一個子串 21 lua_pushstring(L,s); 22 lua_rawseti(L,-2,i); 23 return 1; //返回table。 24 }
Lua API中提供了lua_concat函數,其功能類似於Lua中的".."操作符,用於連接(並彈出)棧頂的n個值,然后壓入連接后的結果。其原型為:
void lua_concat(lua_State *L, int n);
參數n表示棧中待連接的字符串數量。該函數會調用元方法。然而需要說明的是,如果連接的字符串數量較少,該函數可以很好的工作,反之,則會帶來性能問題。為此,Lua API提供了另外一組函數專門解決由此而帶來的性能問題,見如下代碼示例:
1 #include <stdio.h> 2 #include <string.h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 extern "C" int strUpperFunc(lua_State* L) 8 { 9 size_t len; 10 luaL_Buffer b; 11 //檢查參數第一個參數是否為字符串,同時返回字符串的指針及長度。 12 const char* s = luaL_checklstring(L,1,&len); 13 //初始化Lua的內部Buffer。 14 luaL_buffinit(L,&b); 15 //將處理后的字符依次(luaL_addchar)追加到Lua的內部Buffer中。 16 for (int i = 0; i < len; ++i) 17 luaL_addchar(&b,toupper(s[i])); 18 //將該Buffer及其內容壓入棧中。 19 luaL_pushresult(&b); 20 return 1; 21 }
使用緩沖機制的第一步是聲明一個luaL_Buffer變量,並用luaL_buffinit來初始化它。初始化后,就可通過luaL_addchar將一個字符放入緩沖。除該函數之外,Lua的輔助庫還提供了直接添加字符串的函數,如:
void luaL_addlstring(luaL_Buffer* b, const char* s, size_t len);
void luaL_addstring(luaL_Buffer* b, const char* s);
最后luaL_pushresult會更新緩沖,並將最終的字符串留在棧頂。通過這些函數,就無須再關心緩沖的分配了。但是在追加的過程中,緩沖會將一些中間結果放到棧中。因此,在使用時要留意此細節,只要保證壓入和彈出的次數相等既可。Lua API還提供一個比較常用的函數,用於將棧頂的字符串或數字也追加到緩沖區中,函數原型為:
void luaL_addvalue(luaL_Buffer* b);
3. 在C函數中保存狀態:
Lua API提供了三種方式來保存非局部變量,即注冊表、環境和upvalue。
1). 注冊表:
注冊表是一個全局的table,只能被C代碼訪問。通常用於保存多個模塊間的共享數據。我們可以通過LUA_REGISTRYINDEX索引值來訪問注冊表。
1 #include <stdio.h> 2 #include <string.h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 void registryTestFunc(lua_State* L) 8 { 9 lua_pushstring(L,"Hello"); 10 lua_setfield(L,LUA_REGISTRYINDEX,"key1"); 11 lua_getfield(L,LUA_REGISTRYINDEX,"key1"); 12 printf("%s\n",lua_tostring(L,-1)); 13 } 14 15 int main() 16 { 17 lua_State* L = luaL_newstate(); 18 registryTestFunc(L); 19 lua_close(L); 20 return 0; 21 }
2). 環境:
如果需要保存一個模塊的私有數據,即模塊內各函數需要共享的數據,應該使用環境。我們可以通過LUA_ENVIRONINDEX索引值來訪問環境。
1 #include <lua.hpp> 2 #include <lauxlib.h> 3 #include <lualib.h> 4 5 //模塊內設置環境數據的函數 6 extern "C" int setValue(lua_State* L) 7 { 8 lua_pushstring(L,"Hello"); 9 lua_setfield(L,LUA_ENVIRONINDEX,"key1"); 10 return 0; 11 } 12 13 //模塊內獲取環境數據的函數 14 extern "C" int getValue(lua_State* L) 15 { 16 lua_getfield(L,LUA_ENVIRONINDEX,"key1"); 17 printf("%s\n",lua_tostring(L,-1)); 18 return 0; 19 } 20 21 static luaL_Reg myfuncs[] = { 22 {"setValue", setValue}, 23 {"getValue", getValue}, 24 {NULL, NULL} 25 }; 26 27 28 extern "C" __declspec(dllexport) 29 int luaopen_testenv(lua_State* L) 30 { 31 lua_newtable(L); //創建一個新的表用於環境 32 lua_replace(L,LUA_ENVIRONINDEX); //將剛剛創建並壓入棧的新表替換為當前模塊的環境表。 33 luaL_register(L,"testenv",myfuncs); 34 return 1; 35 }
Lua測試代碼如下。
1 require "testenv" 2 3 print(testenv.setValue()) 4 print(testenv.getValue()) 5 --輸出為:Hello
3). upvalue:
upvalue是和特定函數關聯的,我們可以將其簡單的理解為函數內的靜態變量。
1 #include <lua.hpp> 2 #include <lauxlib.h> 3 #include <lualib.h> 4 5 extern "C" int counter(lua_State* L) 6 { 7 //獲取第一個upvalue的值。 8 int val = lua_tointeger(L,lua_upvalueindex(1)); 9 //將得到的結果壓入棧中。 10 lua_pushinteger(L,++val); 11 //賦值一份棧頂的數據,以便於后面的替換操作。 12 lua_pushvalue(L,-1); 13 //該函數將棧頂的數據替換到upvalue(1)中的值。同時將棧頂數據彈出。 14 lua_replace(L,lua_upvalueindex(1)); 15 //lua_pushinteger(L,++value)中壓入的數據仍然保留在棧中並返回給Lua。 16 return 1; 17 } 18 19 extern "C" int newCounter(lua_State* L) 20 { 21 //壓入一個upvalue的初始值0,該函數必須先於lua_pushcclosure之前調用。 22 lua_pushinteger(L,0); 23 //壓入閉包函數,參數1表示該閉包函數的upvalue數量。該函數返回值,閉包函數始終位於棧頂。 24 lua_pushcclosure(L,counter,1); 25 return 1; 26 } 27 28 static luaL_Reg myfuncs[] = { 29 {"counter", counter}, 30 {"newCounter", newCounter}, 31 {NULL, NULL} 32 }; 33 34 35 extern "C" __declspec(dllexport) 36 int luaopen_testupvalue(lua_State* L) 37 { 38 luaL_register(L,"testupvalue",myfuncs); 39 return 1; 40 }
Lua測試代碼如下。
1 require "testupvalue" 2 3 func = testupvalue.newCounter(); 4 print(func()); 5 print(func()); 6 print(func()); 7 8 func = testupvalue.newCounter(); 9 print(func()); 10 print(func()); 11 print(func()); 12 13 --[[ 輸出結果為: 14 1 15 2 16 3 17 1 18 2 19 3 20 --]]
