Lua元表和元方法(轉載)

前言

元表對應的英文是metatable,元方法是metamethod。我們都知道,在C++中,兩個類是無法直接相加的,但是,如果你重載了“+”符號,就可以進行類的加法運算。在Lua中也有這個道理,兩個table類型的變量,你是無法直接進行“+”操作的,如果你定義了一個指定的函數,就可以進行了。那這篇博文就是主要講的如何定義這個指定的函數,這個指定的函數是什么?希望對學習Lua的朋友有幫助。

Lua是怎么做的

通常,Lua中的每個值都有一套預定義的操作集合,比如數字是可以相加的,字符串是可以連接的,但是對于兩個table類型,則不能直接進行“+”操作。這需要我們進行一些操作。在Lua中有一個元表,也就是上面說的metatable,我們可以通過元表來修改一個值得行為,使其在面對一個非預定義的操作時執行一個指定的操作。比如,現在有兩個table類型的變量a和b,我們可以通過metatable定義如何計算表達式a+b,具體的在Lua中是按照以下步驟進行的:
先判斷a和b兩者之一是否有元表;
檢查該元表中是否有一個叫__add的字段;
如果找到了該字段,就調用該字段對應的值,這個值對應的是一個metamethod;
調用__add對應的metamethod計算a和b的值。

上述四個步驟就是計算table類型變量a+b的過程。在Lua中,每個值都有一個元表,table和userdata類型的每個變量都可以有各自獨立的元表,而其他類型的值則共享其類型所屬的單一元表。

告別metatable小白

現在就說說最基本的metatable內容。Lua在創建新的table時不會創建元表,比如以下代碼就可以演示:

Paste_Image.png

我們是使用getmetatable來獲取一個table或userdata類型變量的元表,當創建新的table變量時,使用getmetatable去獲得元表,將返回nil;同理,我們也可以使用setmetatable去設置一個table或userdata類型變量的元表,例如以下代碼:

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1928510-d20d137178bff315.png?imageMogr2/auto-orient/strip%7
CimageView2/2/w/1240)

任何table都可以作為任何值得元表,而一組相關的table有可以共享一個通用的元表,此元表描述了它們共同的行為。一個table甚至可以作為它自己的元表,用于描述其特有的行為。總之,任何搭配形式都是合法的。

在Lua代碼中,只能設置table的元表。若要設置其它類型的值得元表,則必須通過C代碼來完成。還存在一個特例,對于字符串,標準的字符串程序庫為所有的字符串都設置了一個元表,而其它類型在默認情況下都沒有元表。查看兩句代碼的打印值,就可以看出來:


Paste_Image.png

在table中,我可以重新定義的元方法有以下幾個:

Paste_Image.png

接下來就介紹介紹如果去重新定義這些方法。

算術類的元方法
現在我使用完整的實例代碼來詳細的說明算術類元方法的使用。我準備定義一些對集合的操作方法,所有的方法都放入Set這個table中,下面的代碼是我模擬的一個集合的操作:

Paste_Image.png

現在,我定義“+”來計算兩個集合的并集,那么就需要讓所有用于表示集合的table共享一個元表,并且在該元表中定義如何執行一個加法操作。首先創建一個常規的table,準備用作集合的元表,然后修改Set.new函數,在每次創建集合的時候,都為新的集合設置一個元表。代碼如下:

Paste_Image.png

在此之后,所有由Set.new創建的集合都具有一個相同的元表,例如:

Paste_Image.png

最后,我們需要把元方法加入元表中,代碼如下:

Paste_Image.png

這以后,只要我們使用“+”符號求兩個集合的并集,它就會自動的調用Set.union函數,并將兩個操作數作為參數傳入。比如以下代碼:

Paste_Image.png

在上面列舉的那些可以重定義的元方法都可以使用上面的方法進行重定義。現在就出現了一個新的問題,set1和set2都有元表,那我們要用誰的元表啊?雖然我們這里的示例代碼使用的都是一個元表,但是實際coding中,會遇到我這里說的問題,對于這種問題,Lua是按照以下步驟進行解決的:

對于二元操作符,如果第一個操作數有元表,并且元表中有所需要的字段定義,比如我們這里的__add元方法定義,那么Lua就以這個字段為元方法,而與第二個值無關;
對于二元操作符,如果第一個操作數有元表,但是元表中沒有所需要的字段定義,比如我們這里的__add元方法定義,那么Lua就去查找第二個操作數的元表;
如果兩個操作數都沒有元表,或者都沒有對應的元方法定義,Lua就引發一個錯誤。
以上就是Lua處理這個問題的規則,那么我們在實際編程中該如何做呢?比如set3 = set1 + 8這樣的代碼,就會打印出以下的錯誤提示:

Paste_Image.png

但是,我們在實際編碼中,可以按照以下方法,彈出我們定義的錯誤消息,代碼如下:

Paste_Image.png

當兩個操作數的元表不是同一個元表時,就表示二者進行并集操作時就會出現問題,那么我們就可以打印出我們需要的錯誤消息。

上面總結了算術類的元方法的定義,關系類的元方法和算術類的元方法的定義是類似的,這里不做累述。

__tostring元方法

寫過Java或者C#的人都知道,Object類中都有一個tostring的方法,程序員可以重寫該方法,以實現自己的需求。在Lua中,也是這樣的,當我們直接print(a)(a是一個table)時,是不可以的。那怎么辦,這個時候,我們就需要自己重新定義__tostring元方法,讓print可以格式化打印出table類型的數據。

函數print總是調用tostring來進行格式化輸出,當格式化任意值時,tostring會檢查該值是否有一個__tostring的元方法,如果有這個元方法,tostring就用該值作為參數來調用這個元方法,剩下實際的格式化操作就由__tostring元方法引用的函數去完成,該函數最終返回一個格式化完成的字符串。例如以下代碼:

Paste_Image.png

如何保護我們的“奶酪”——元表

我們會發現,使用getmetatable就可以很輕易的得到元表,使用setmetatable就可以很容易的修改元表,那這樣做的風險是不是太大了,那么如何保護我們的元表不被篡改呢?

在Lua中,函數setmetatable和getmetatable函數會用到元表中的一個字段,用于保護元表,該字段是__metatable。當我們想要保護集合的元表,是用戶既不能看也不能修改集合的元表,那么就需要使用__metatable字段了;當設置了該字段時,getmetatable就會返回這個字段的值,而setmetatable則會引發一個錯誤;如以下演示代碼:


Paste_Image.png

上述代碼就會打印以下內容:


Paste_Image.png

__index元方法

是否還記得當我們訪問一個table中不存在的字段時,會返回什么值?默認情況下,當我們訪問一個table中不存在的字段時,得到的結果是nil。但是這種狀況很容易被改變;Lua是按照以下的步驟決定是返回nil還是其它值得:

當訪問一個table的字段時,如果table有這個字段,則直接返回對應的值;
當table沒有這個字段,則會促使解釋器去查找一個叫__index的元方法,接下來就就會調用對應的元方法,返回元方法返回的值;
如果沒有這個元方法,那么就返回nil結果。
下面通過一個實際的例子來說明__index的使用。假設要創建一些描述窗口,每個table中都必須描述一些窗口參數,例如顏色,位置和大小等,這些參數都是有默認值得,因此,我們在創建窗口對象時可以指定那些不同于默認值得參數。

Paste_Image.png

根據上面代碼的輸出,結合上面說的那三步,我們再來看看,print(win.x)時,由于win變量本身就擁有x字段,所以就直接打印了其自身擁有的字段的值;print(win.width),由于win變量本身沒有width字段,那么就去查找是否擁有元表,元表中是否有__index對應的元方法,由于存在__index元方法,返回了default表中的width字段的值,print(win.color.r)也是同樣的道理。

在實際編程中,__index元方法不必一定是一個函數,它還可以是一個table。當它是一個函數時,Lua以table和不存在key作為參數來調用該函數,這就和上面的代碼一樣;當它是一個table時,Lua就以相同的方式來重新訪問這個table,所以上面的代碼也可以是這樣的:


Paste_Image.png

__newindex元方法

__newindex元方法與__index類似,__newindex用于更新table中的數據,而__index用于查詢table中的數據。當對一個table中不存在的索引賦值時,在Lua中是按照以下步驟進行的:

Lua解釋器先判斷這個table是否有元表;
如果有了元表,就查找元表中是否有__newindex元方法;如果沒有元表,就直接添加這個索引,然后對應的賦值;
如果有這個__newindex元方法,Lua解釋器就執行它,而不是執行賦值;
如果這個__newindex對應的不是一個函數,而是一個table時,Lua解釋器就在這個table中執行賦值,而不是對原來的table。
那么這里就出現了一個問題,看以下代碼:


Paste_Image.png

發現什么問題了么?是不是循環了,在Lua解釋器中,對這個問題,就會彈出錯誤消息,錯誤消息如下:


Paste_Image.png

丟掉那該死的元表

有的時候,我們就不想從__index對應的元方法中查詢值,我們也不想更新table時,也不想執行__newindex對應的方法,或者__newindex對應的table。那怎么辦?在Lua中,當我們查詢table中的值,或者更新table中的值時,不想理那該死的元表,我們可以使用rawget函數,調用rawget(tb, i)就是對table tb進行了一次“原始的(raw)”訪問,也就是一次不考慮元表的簡單訪問;你可能會想,一次原始的訪問,沒有訪問__index對應的元方法,可能有性能的提升,其實一次原始訪問并不會加速代碼執行的速度。對于__newindex元方法,可以調用rawset(t, k, v)函數,它可以不涉及任何元方法而直接設置table t中與key k相關聯的value v。

參考文章:http://www.jellythink.com/archives/511

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容