日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入Lua:元表

發布時間:2024/1/23 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入Lua:元表 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

元表

我覺得Lua最強大的地方在于對象可以設置元表,而元表會影響對象的訪問行為。

Table的結構有一個metatable成員,userdata類型的結構也有一個metatable成員,這表明Table和userdata對象可以單獨設置元表,其他每種類型的元素是共享的。通過情況下,我們只會對Table或Userdata設置元表,其他類型沒有辦法通過Lua代碼設置元表。

字符串庫默認給string類型設置了一個元表。使得它可以像對象一樣調用字符串庫的函數,比如"hello":len()

設置元表的API為lua_setmetatable:

LUA_API int lua_setmetatable(lua_State *L, int objindex) {TValue *obj;Table *mt;lua_lock(L);// 從棧中取對象obj = index2addr(L, objindex);//從棧中取元表if (ttisnil(L->top - 1))mt = NULL;else {api_check(L, ttistable(L->top - 1), "table expected");mt = hvalue(L->top - 1);}//根據不同類型設置元表switch (ttnov(obj)) {case LUA_TTABLE: { //tablehvalue(obj)->metatable = mt;if (mt) {luaC_objbarrier(L, gcvalue(obj), mt);luaC_checkfinalizer(L, gcvalue(obj), mt);}break;}case LUA_TUSERDATA: { //userdatauvalue(obj)->metatable = mt;if (mt) {luaC_objbarrier(L, uvalue(obj), mt);luaC_checkfinalizer(L, gcvalue(obj), mt);}break;}default: { // 其他類型G(L)->mt[ttnov(obj)] = mt;break;}}L->top--;lua_unlock(L);return 1; }

元方法

元表是通過定義在其上的字段來影響對象的行為的,這些字段也稱為元方法,但其實不是每個字段都是函數,有些是字符串,有些甚至可以是另外一個表,大概列舉如下:

__tostring, __pairs, __name, __index, __newindex, __call, __add, __sub, __mul, __div, __mod, __pow, __unm, __idiv, __band, __bor, __bxor, __bnot, __shl, __shr, __concat, __len, __eq, __lt, __le, __gc, __mode,

關于元方法的具體含義,具體請看文檔,這里不再描述。

在ltm.h|.c中實現了元方法的一些輔助函數,其中有一個枚舉:

typedef enum {TM_INDEX,TM_NEWINDEX,TM_GC,TM_MODE,TM_LEN,TM_EQ, /* 在這之前的元方法可以快速訪問 */...TM_N /* 枚舉數量 */ } TMS;

枚舉了元方法的類型,在global_state中有一個tmname字段,為TMS到元方法名字的映射,在下面代碼初始化:

void luaT_init (lua_State *L) {static const char *const luaT_eventname[] = { /* ORDER TM */"__index", "__newindex","__gc", "__mode", "__len", "__eq","__add", "__sub", "__mul", "__mod", "__pow","__div", "__idiv","__band", "__bor", "__bxor", "__shl", "__shr","__unm", "__bnot", "__lt", "__le","__concat", "__call"};int i;for (i=0; i<TM_N; i++) {G(L)->tmname[i] = luaS_new(L, luaT_eventname[i]);luaC_fix(L, obj2gco(G(L)->tmname[i])); /* never collect these names */} }

?

由上面代碼可知tmname的字段為不會被GC回收的短字符串對象。這樣的話使用TMS宏即可快速從tmname找到相應的元方法名。再由元方法名找元表的字段值。

快速訪問元方法

正常取元方法值是下面這個函數:

const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event) {// 主體邏輯很簡單,根據類型取出ua尿素,然后調用luaH_getshortstr取元方法Table *mt;switch (ttnov(o)) {case LUA_TTABLE:mt = hvalue(o)->metatable;break;case LUA_TUSERDATA:mt = uvalue(o)->metatable;break;default:mt = G(L)->mt[ttnov(o)];}return (mt ? luaH_getshortstr(mt, G(L)->tmname[event]) : luaO_nilobject); }

ltm.h中有一個fasttm宏,用于加速取表中某些元方法的值,看最上面的枚舉,在TM_EQ之上那些元方法會用加速的方式,怎么加速呢:

// L為lua_State, et為元素, e為TMS枚舉 #define fasttm(l,et,e) gfasttm(G(l), et, e) // 先通過Table的flag標記位判斷,如果該位存在,表示沒有該元方法,直接返回NULL // 否則才通過luaT_gettm去取 #define gfasttm(g,et,e) ((et) == NULL ? NULL : \((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e]))

luaT_gettm代碼如下:

const TValue *luaT_gettm(Table *events, TMS event, TString *ename) {// 先嘗試從Table中取元方法值const TValue *tm = luaH_getshortstr(events, ename);lua_assert(event <= TM_EQ);// 如果值不存在,設置表中的flags相應位為1// 下次再調用fasttm, 就不會調到這個函數了。if (ttisnil(tm)) { /* no tag method ? */events->flags |= cast_byte(1u<<event); /* cache this fact */return NULL;}else return tm; }

我們前面看Table結構時,注意到這個flags成員,現在終于知道它是用在這里的。flags的位為1表示該位的元方法不存在,這樣就避免每次查詢元方法都要從元表去取,某些元方法的查詢是很頻繁的。

但既然有了狀態,就得維護這個狀態,ltable.h有一個宏invalidateTMcache,作用是把flags清0:

#define invalidateTMcache(t)? ?((t)->flags = 0)

注意fasttm只能用于表,用戶數據只能通過luaT_gettmbyobj查詢元方法。

列舉元方法的調用

不同的元方法會在不同的代碼出現,下面只能列舉一些。

__index

索引表,流程大概是這樣的:

  • 調用luaV_gettable取表的字段,其中:
  • 調用luaV_fastget取字段,如果失敗則調用luaV_finishget, 這里面就會使用元方法
  • 通過fasttm或luaT_gettmbyobj得到元方法后,判斷它是否為函數,如果為函數則調用luaT_callTM,否則它應該是一個表,則繼續這個過程。
  • 假如一直這個循環,直到MAXTAGLOOP次,則Lua直接報錯,說明這個__index鏈太長了。

具體邏輯請看上面這幾個函數。

__newindex

設置表,流程大概是這樣的:

  • 調用luaV_settable設置表字段,其中:
  • 調用luaV_fastset設置字段,如果失敗則調用luaV_finishset,這里面就會使用元方法。
  • 通過fasttm或luaT_gettmbyobj得到元方法后,判斷他是否為函數,如果為函數則調用luaT_callTM,否則它應該是一個表,則繼續這個過程。
  • 假如一直這個循環,直到MAXTAGLOOP次,則Lua直接報錯,說明這個__newindex鏈太長了。

具體邏輯請看上面這幾個函數。

__eq

比較兩個對象是否相等,在luaV_equalobj中如果對象是表或用戶數據,則嘗試通過fasttm取得它們的元方法,如果取得到,就調用luaT_callTM。

__len

取對象長度,在luaV_objlen函數中,中如果對象是表或用戶數據,則嘗試通過fasttm取得它們的元方法,如果取得到,就調用luaT_callTM。

__name

自定義對象的名字,luaT_objtypename函數返回一個對象的名字,比如boolean, nil, table等等,但對象的名字可以自定義,只要這個對象有一個元表,指定元表的__name元方法即可,對象名字一般用于錯誤提示上,比如luaG_typeerror等函數。

__tostring

返回對象的字符串表現,看一下Lua的tostring函數的調用鏈:

tostring -> luaB_tostring -> luaL_tolstring

luaL_tolstring嘗試調用對象元表的tostring,如果沒有tostring就根據類型來,最后默認就是typename : 對象地址

__pairs

遍歷表:luaB_pairs->pairsmeta,先判斷有沒有元表,元表有沒有存在__pairs,有就調用它,沒有就調用luaB_next。

總結

以上是生活随笔為你收集整理的深入Lua:元表的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。