lua對象調用—用 "." 與 ":" 調用表中函數時的區別
寫這篇帖子之前,我看過許多關於綁定C++對象到Lua中的文章。總結一下他們的做法,用到元表、注冊表、以及一些表中函數操作的一些基礎知識以及相關的細節。
相信大家對Lua的表一點也不陌生,Lua表是個神奇的東西(本質上就是hash表),可以當做數組,可以當做map,還可用來模擬面向對象,這很Lua。
我們在Lua中模擬面向對象的步驟中大家有沒有仔細去研究過用 "." 與 ":" 調用表中函數時的區別呢,下面帶大家一起來研究一下。
例1
1 --code 1: 2 --定義一張表obj 3 obj = {} 4 --下面的這行代碼等價於obj["func"]=function(a,b,c)... 5 obj.func = function(a, b, c) 6 print(a, b, c) 7 end 8 9 print("obj:", obj) 10 obj.func(1,2) 11 obj:func(1,2)
輸出:
[Shell]
obj: obj表地址 1 2 nil obj表地址 1 2
從上面的輸出可以看出,當我們可以發現,當用"."來調用表內的函數時,與普通的函數調用沒啥區別。
但是當我們用":"來調用時,會把調用這個函數的那張表的傳進去第一個參數中,后面的參數與實參一樣。
有了這個發現我們就可以繼續往下探究。
但是有一種情況是不會傳入表到第一個參數的。看如下代碼
例2
1 --下面這種定義表中函數的當大相信大家都不陌生 2 obj = {} 3 function obj:func(a, b, c) 4 print(a, b, c) 5 end 6 7 print("obj:", obj) 8 obj.func(1, 2) 9 obj:func(1, 2)
輸出:
obj:obj表地址 2 nil nil 1 2 nil
發現了吧,當我們用如"function 表:函數名(......) .... end"的形式去定義一個表中的函數時,如果我們用"."來調用這個函數,就會得到莫名其妙的東西,這個現象不再我們的討論之內;對於上面的定義方式,如果是用":"來調用那么跟調用普通的函數沒啥區別。
這個了解一下就好,因為我們用lua c api 操作表中函數時用的是上上面的那種定義形式。
那么如果把這個obj表作為一個元表__index鍵的值再把這個元表與其他的表進行綁定,在觸發__index時傳的是哪張表進第一的參數呢
有如下代碼:
例3
1 obj = {} 2 obj.func = function(a, b, c) 3 print(a, b, c) 4 end 5 6 mtable = {__index = obj} 7 otherobj = {} 8 9 setmetatable(otherobj, mtable) 10 11 print("obj:", obj) 12 print("mtable", mtable) 13 print("otherobj:", otherobj) 14 15 otherobj.func(1, 2) 16 otherobj:func(1, 2)
輸出:
obj: obj表地址 mtable: mtable表地址 otherobj: otherobj表地址 1 2 nil otherobj表地址 1 2
從上面的結果可以知道,就算是在觸發__index元方法調用的函數,如果是用":"來調用,那么一樣會傳入調用這個函數的那張表進第一個參數,就算是嵌套多層元表也是如此,大家感興趣可以試一試嵌套多層元表,結果是一樣的。
元表還有一些小知識,就是__index鍵對應的是一個函數的時候也會把調用這個函數的表傳進去,然后第二個參數是要調用的那個函數的名字,這時你就得手動處理是返回一個函數還是返回一個其他的量了。這個大家感興趣也可以做做實驗,這也是元表的基礎知識。
那么,如果這個函數時我們自己寫的CFunction呢,還能這么如願么,看下面的代碼:
例4
1 //filename:funct.c 2 3 #define LUA_LIB 4 #include <lua.h> 5 #include <lualib.h> 6 #include <lauxlib.h> 7 8 static int _c_l_testfunc(lua_State* L) 9 { 10 unsigned char argc, index; 11 const char *typename; 12 if ((argc = lua_gettop(L)) != 0) { 13 printf("共傳入 %d 個參數\n", argc); 14 for (index = 1; index <= argc; index++) { 15 printf( 16 "第 %d 個參數類型為: %s\n", 17 index, lua_typename(L, lua_type(L, index)) 18 ); 19 } 20 } else { 21 puts("0 個參數傳入"); 22 } 23 24 //清空棧 25 lua_settop(L, 0); 26 27 //把參數個數壓入棧作為返回值 28 lua_pushinteger(L, argc); 29 30 return 1; 31 } 32 33 #if define(__cplusplus) 34 #define EXP_FUNC __declspec(dllexport) 35 #else 36 #define EXP_FUNC 37 #endif 38 39 40 LUA_API EXP_FUNC int luaopen_funct(lua_State* L) 41 { 42 lua_pushcfunction(L, _c_l_testfunc); 43 return 1; 44 }
上面的這個函數又來輸出調用這個函數時的參數以及參數的類型。
把上面的代碼編譯成動態庫,我們來在lua中調用這個函數
例5
1 funct = require "funct" 2 3 --建一個表來存這個函數 4 obj = {} 5 obj.func = funct 6 7 print("\".\"調用:") 8 obj.func(1, "str") 9 print("\":\"調用:") 10 obj:func(1, "str")
輸出:
"."調用: 共傳入 2 個參數 第 1 個參數類型為: number 第 2 個參數類型為: string ":"調用 共傳入 3 個參數 第 1 個參數類型為: table 第 2 個參數類型為: number 第 3 個參數類型為: string
發現了吧,與lua內函數定義的函數傳參沒區別。如果大家半信半疑的話再把剛才那個C代碼改一下,再添加一個把某個參數位置的值輸出出來。這跑一下這個例子就明白了。現在告訴大家的是就算是CFunction,我們使用":"調用時也會把調用這個函數的表傳進第一個參數,這使得我么把C++綁進lua中有方法可尋。
還有一點,就是我們的userdata可以與元表綁定,lightuserdata不可以與元表綁定。如果我們在Lua中是對一個userdata進行":"調用的話,那么就會觸發與之綁定的元表的__index方法,而且這回傳進去的不是表了,是這個userdata,我們用lua_touserdata轉換得到相應的對象指針。
在Lua中,我們不能直接對userdata進行操作,但是我們可以間接的通過與之綁定的元表的__index進行操作。
有一個點我們得注意的是,CFunction是普通成員函數指針類型,與類成員函數指針不一樣,我們得再封裝一層函數,這樣子才能把函數綁定在我們的函數表中。
大致思路:
1.與類成員函數同名的CFunction,並且在其中完成對相應類成員函數的調用。
2.把定義的CFunction打包到一張函數表methods中
3.創建元表,設置鍵值對:__index = methods
4.把這張元表綁定到userdata上(我們的對象指針)
5.垃圾回收(主動回收和__gc被動回收)
參考:https://www.52pojie.cn/thread-833988-1-1.html
參考:解決C++成員變量在lua中直接使用的問題https://www.cnblogs.com/liao0001/p/9791557.html