lua面向对象学习
元表和元方法
在Lua中,每个值都有一个元表,table和userdata类型的每个变量都可以有各自独立的元表,
其他类型的值则共享其类型所属的单一元表。
基本的metatable
- 创建新的table时不会创建元表
- getmetatable(table) 获取table或者userdata类型变量的元表
setmetatable(t,ot) 设置table或者userdata类型变量的元表
123456local t = {1,1}print getmetatable(t) -- nillocal t1 = {}setmetatable(t,t1)assert(getmetatable(t) == t1)Lua代码中,只能设置table的元表,若要设置其他类型的值的元表,必须通过C代码来完成。
- 标准的字符串程序库为所有的字符串都设置了一个元表,其他类型在默认情况下没有元表。
table中可以重新定义的元方法:
1234567891011121314151617__add(a,b) --加法__sub(a,b) --减法__mul(a,b) --乘法__div(a,b) --除法__mod(a,b) --取模__pow(a,b) --乘幂__unm(a) --相反数__concat(a,b) -- 连接__len(a) --长度__eq(a,b) --相等__lt(a,b) --小于__le(a,b) --小于等于__index(a,b) --索引查询__newindex(a,b,c) --索引更新__call(a,...) --执行方法调用__tostring(a) --字符串输出__metatable --保护元表当操作符的两个操作数都有元表时的函数获取规则:
- 对于二元操作符,如果第一个操作数有元表,并且元表中有所需要的字段定义,那么lua就以这个字段为元方法。
- 对于二元操作符,如果第一个操作数有元表,但是元表中没有所需要的字段定义,那么lua就去查找第二个操作数的元表。
- 如果两个操作数都没有元表,或者都没有对应的元方法定义,lua就会引发一个错误。
保护元表:
12345678910111213function Set.new(l)local set = {}setmetatable(set,mt)for _, v in pairs(l) do set[v] = true endmt.__metatable = "You cannot get the metatable"return setendlocal tb = Set.new({1,2})print(tb) -- 输出:{1, 2}print(getmetatable(tb)) -- 输出__metatable的内容:You cannot get the metatablesetmetatable(tb,{}) -- 输出错误信息:lua: test.lua:56: cannot change a protected metatable
当访问一个table中不存在的字段时,lua的处理规则:
- 当table有这个字段时,直接返回对应的值;
- 当table没有这个字段时,解释器会去查找一个叫__index的元方法,并调用对应的元方法,返回结果
- 如果没有这个元方法,返回nil
__index使用的例子:123456789101112131415161718192021222324-- 假设要创建一些描述窗口,每个table中都必须描述一些窗口参数,例如颜色,位置和大小等Windows = {} -- 创建一个命名空间-- 创建默认值表Window.default = {x = 0, y = 0, width = 100, height = 100, color = {r = 255, g = 255, b = 255}}Window.mt = {} --创建元表-- 声明构造函数function Windows.new(0)setmetatable(o, Window.mt)end-- 定义__lindex元方法Window.mt.__index = function (table, key)return Windows.default[key]endlocal win = Window.new({x = 10, y = 10})print(win.x) -- >10 访问自身已经拥有的值print(win.width) -- >100 访问default表中的值print(win.color.r) -- >255 访问default表中的值-- __index元方法不一定必须是个函数,它还可以是一个table。
newindex元方法,与index类似,newindex用于更新table中的数据,index用于查询table中的数据。当对table中不存在的索引赋值时,执行步骤如下:
- Lua解释器先判断这个table是否有元表,如果没有元表,就直接添加这个索引,然后对应赋值
- 如果有了元表,就查找元表中是否有__newindex元方法,如果有这个方法,lua解释器就执行这个元方法,而不是赋值
- 如果这个__newindex对应的不是一个函数,而是一个table时,lua解释器就在这个table中执行赋值,而不是在原来的table中
示例代码:12345678910local tb1 = {}local tb2 = {}tb1.__newindex = tb2tb2.__newindex = tb1setmetatable(tb1,tb2)setmetatable(tb2,tb1)tb1.x = 10
上面的代码存在循环引用的问题,lua解释器会抛出如下错误:
1loop in settable
- rawget(tb,i); 直接访问tb属性,不使元表,即忽略__index的定义
- rawset(tb,k,v); 直接设置tb属性,不使用元表,即忽略__newindex的定义
代码示例
记录table的访问
12345678910111213141516171819202122232425
local t = {} --原来的table-- 保持对原table的一个引用local _t = t-- 创建代理t = {}-- 创建元表local mt = {__index = function(t, k) print("access to element " .. tostring(k)) return _t[k]end,__newindex = function(t, k, v) print("update of element " .. tostring(k)) _t[k] = vend}setmetatable(t, mt)t.x = 10 -- update of element xprint(t.x) -- access to element x
多个table访问日志,使用代理模式
12345678910111213141516171819202122232425262728
-- 创建唯一索引local index = {}-- 创建元表local mt = { __index = function(t, k) print("access to element " .. tostring(k)) return t[index][k] end, __newindex = function(t, k, v) print("update of element " .. tostring(k)) t[index][k] = v end}function track(t) local proxy = {} proxy[index] = t setmetatable(proxy, mt) return proxyendlocal t = {}local proxy = track(t)proxy.x = 10print(proxy.x)
只读的table(代理模式)
123456789101112131415161718
function readOnly(t) local proxy = {} -- 创建元表 local mt = { __index = t, __newindex = function(t, k, v) error("Attempt to update a read-only table", 2) end } setmetatable(proxy, mt) return proxyendlocal tb = readOnly{1, 2, 3, 4, 5}print(tb[1])tb[1] = 20 -- Attempt to update a read-only table
面向对象
lua中,table就是对象,对象就是表,lua中的类本质上也是对象(表),不过是用来做模版的对象。
lua中的类和javascript中的非常类似,也是采用原型(prototype)的概念。lua通过元表实现原型。
像下面这样:
|
|
lua中方法的调用:
|
|