元表

[TOC]

@date: 2018-3-18


在Lua5.1语言中,元表 (metatable) 的表现行为类似于 C++ 语言中的操作符重载,类似PHP的魔术方法。Python里也有元类(metaclass)一说。

通过元表,Lua有了更多的扩展特性。Lua的面向对象特性就是基于元表实现的。

Lua 提供了两个十分重要的用来处理元表的方法,如下:

设置元表的方法很简单,如下:

local mytable = {}
local mymetatable = {}
setmetatable(mytable, mymetatable)

上面的代码可以简写成如下的一行代码:

local mytable = setmetatable({}, {})

例如我们可以重载 __add 元方法 (metamethod),实现重载+操作符,来计算两个 Lua 数组的并集:

-- 计算集合的并集实例
set1 = {10,40}
set2 = {10,20,30}

setmetatable(set1, {
	__add = function(self, another)
		local res = {}
		local set = {}
		
		for k,v in pairs(self) do set[v] = true end -- 防止集合元素重复
		for k,v in pairs(another) do set[v] = true end -- 防止集合元素重复
		
		for k,v in pairs(set) do table.insert(res, k) end
		
		return res
	end
})

local set3 = set1 + set2
for k,v in pairs(set3) do print(v) end 

输出:

30
20
10
40

类似的元方法还有:

__index 元方法

该方法实现了在表中查找键不存在时转而在元表中查找该键的功能。有两种写法:

第一种是给 __index 元方法一个函数:

local mytable = setmetatable({}, {
	__index = function(self, key)
		return "__index"
	end
})
print(mytable.key1) -- __index 

另一种方法是给 __index 元方法一个表:

local _M = {
	add = function(x,y) return x+y end,
	mul = function(x,y) return x*y end,
	ver = "1.0",
}
local mytable = setmetatable({}, {
	__index = _M
})
print(mytable.ver) -- 1.0 
print(mytable.add(1,3)) -- 4 

Lua查找一个表元素时的规则,其实就是如下3个步骤:

  1. 在表中查找,如果找到,返回该元素,找不到则继续
  2. 判断该表是否有元表,如果没有元表,返回nil,有元表则继续。
  3. 判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复1、2、3;如果__index方法是一个函数,则返回该函数的返回值。

通过__index这个方法,我们可以实现继承的特性。下节再详细讲述。

__newindex 元方法

如果说__index具有PHP里__get的一些特性,那么__newindex则类似__set

以下实例使用了 rawset 函数来更新表:

mytable = setmetatable({key1 = "value1"}, {
  __newindex = function(self, key, value)
        rawset(self, key, "\""..value.."\"")

  end
})

mytable.key1 = "new value"
mytable.key2 = 4

print(mytable.key1,mytable.key2)

以上实例执行输出结果为:

new value    "4"

__tostring 元方法

如果设置了__tostring 元方法,当直接输出表时会自动调用该方法。示例:

local mytable = setmetatable({ 10, 20, 30 }, {
  __tostring = function(mytable)
    sum = 0
    for k, v in pairs(mytable) do
        sum = sum + v
    end
    return sum
  end
})
print(mytable) -- 60

__call 元方法

__call 元方法的功能类似于 C++ 中的仿函数,使得普通的表也可以被调用。

local mytable = setmetatable({}, {
  __call = function(self, arg)
	local sum = 0
	for _,v in pairs(arg) do
		sum = sum + v
	end
    print(sum)
  end
})
mytable({10,20,30}) -- 60

示例里我们调用自定义的表,并给该表传了参数,最终算出了参数的和。

__metatable 元方法

如果给表设置了 __metatable 元方法的值,getmetatable 将返回这个域的值,而调用 setmetatable将会被禁止,会直接报错。

local mytable = setmetatable({}, {
  __metatable = "no access"
})
print(getmetatable(mytable)) -- no access
setmetatable(mytable, {}) -- 引发编译器报错