第一版是為 Lua 5.0 編寫的。儘管在很大程度上仍然適用於後續版本,但還是有一些差異。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買本書,您還可以 支持 Lua 專案


28.3 – 物件導向存取

我們的下一步是將新類型轉換為物件,以便可以使用一般的物件導向語法對其執行個體運算,例如

    a = array.new(1000)
    print(a:size())     --> 1000
    a:set(10, 3.4)
    print(a:get(10))    --> 3.4

請記住,a:size() 等於 a.size(a)。因此,我們必須安排表達式 a.size 傳回我們的 getsize 函式。此處的關鍵機制是 __index 元方法。對於表格,每當 Lua 找不到特定鍵的值時,就會呼叫此元方法。對於使用者資料,每次存取都會呼叫它,因為使用者資料根本沒有鍵。

假設我們執行下列程式碼

    local metaarray = getmetatable(array.new(1))
    metaarray.__index = metaarray
    metaarray.set = array.set
    metaarray.get = array.get
    metaarray.size = array.size
在第一行,我們建立一個陣列,只為了取得其元表格,然後將其指定給 metaarray。(我們無法從 Lua 設定使用者資料的元表格,但我們可以毫無限制地取得其元表格。)然後,我們將 metaarray.__index 設定為 metaarray。當我們評估 a.size 時,Lua 無法在物件 a 中找到鍵 "size",因為該物件是使用者資料。因此,Lua 會嘗試從 a 的元表格的 __index 欄位取得此值,而該欄位碰巧就是 metaarray 本身。但是 metaarray.sizearray.size,因此 a.size(a) 會產生 array.size(a),正如我們所要的。

當然,我們可以用 C 編寫相同內容。我們甚至可以做得更好:現在陣列是物件,具有自己的運算,我們不再需要在表格 array 中有這些運算了。我們的函式庫仍然必須匯出的唯一函式是 new,用於建立新陣列。所有其他運算都只作為方法提供。C 程式碼可以直接將它們註冊為此類方法。

運算 getsizegetarraysetarray 沒有從我們先前的做法中變更。將會變更的是我們如何註冊它們。也就是說,我們必須變更開啟函式庫的函式。首先,我們需要兩個獨立的函式清單,一個是常規函式,一個是方法

    static const struct luaL_reg arraylib_f [] = {
      {"new", newarray},
      {NULL, NULL}
    };
    
    static const struct luaL_reg arraylib_m [] = {
      {"set", setarray},
      {"get", getarray},
      {"size", getsize},
      {NULL, NULL}
    };
開啟函式庫的函式 luaopen_array 的新版本必須建立元表,將它指定給它自己的 __index 欄位,註冊所有方法在那裡,並建立和填入 array 表格
    int luaopen_array (lua_State *L) {
      luaL_newmetatable(L, "LuaBook.array");
    
      lua_pushstring(L, "__index");
      lua_pushvalue(L, -2);  /* pushes the metatable */
      lua_settable(L, -3);  /* metatable.__index = metatable */
    
      luaL_openlib(L, NULL, arraylib_m, 0);
    
      luaL_openlib(L, "array", arraylib_f, 0);
      return 1;
    }
在此我們使用 luaL_openlib 的另一個功能。在第一次呼叫時,當我們傳遞 NULL 作為函式庫名稱時,luaL_openlib 沒有建立任何表格來封裝函式;相反地,它假設封裝表格在堆疊上,低於任何偶爾的向上值。在此範例中,封裝表格是元表本身,這是 luaL_openlib 將會放置方法的地方。下一次呼叫 luaL_openlib 會正常運作:它建立一個具有給定名稱 (array) 的新表格,並在那裡註冊給定的函式 (在此情況下只有 new)。

作為最後的修飾,我們將新增一個 __tostring 方法到我們的類型中,這樣 print(a) 會列印 array 加上括號中的陣列大小 (例如,array(1000))。函式本身在此

    int array2string (lua_State *L) {
      NumArray *a = checkarray(L);
      lua_pushfstring(L, "array(%d)", a->size);
      return 1;
    }
lua_pushfstring 函式格式化字串並將它留在堆疊頂端。我們還必須將 array2string 新增到清單 arraylib_m 中,以將它包含在陣列物件的元表中
    static const struct luaL_reg arraylib_m [] = {
      {"__tostring", array2string},
      {"set", setarray},
      ...
    };