讲到元表,先看一段table的合并动作.

t1 = {1,2}
t2 = {3,4}
t3 = t1 + t2
attempt to perform arithmetic on a table value (global 't1')

程序会报错,因为不知道如何对两个table执行+运算,这个时候就需要通过元表来定义,有点类似c中的运算符加载。我们看一下如何通过元表实现合并操作。


local mt = {}
--定义mt.__add元方法(其实就是元表中一个特殊的索引值)为将两个表的元素合并后返回一个新表
mt.__add = function(t1,t2)
local temp = {}
for _,v in pairs(t1) do
table.insert(temp,v)
end
for _,v in pairs(t2) do
table.insert(temp,v)
end
return temp
end
local t1 = {1,2,3}
local t2 = {2}
--设置t1的元表为mt
setmetatable(t1,mt) local t3 = t1 + t2
--输出t3
local st = "{"
for _,v in pairs(t3) do
st = st..v..", "
end
st = st.."}"
print(st)
{1, 2, 3, 2, }

可以看到, 程序在执行的时候,调用了mt._add元方法计算。

具体的过程是:

1.查看t1是否有元表,若有,则查看t1的元表是否有__add元方法,若有则调用。

2.查看t2是否有元表,若有,则查看t2的元表是否有__add元方法,若有则调用。

3.若都没有则会报错。

所以说,我们通过定义了t1元表的__add元方法,达到了让两个表通过+号来相加的效果

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

1.在表中查找,如果找到,返回该元素,找不到则继续

2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。

3.判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。

Lua提供两个用来处理元表的方法

  • setmetatable(table, metatable)为表设置元表metatable,不能从Lua中改变其它任何类型的值的元表metatable(使用debug库例外),要这样做的话必须使用C语言的API。

  • getmetatable(table)获取表的元表metatable对象

元表的元方法有:(下标是__双底线喔)

函数 描述
__add 运算符 +
__sub 运算符 -
__mul 运算符 *
__ div 运算符 /
__mod 运算符 %
__unm 运算符 -(取反)
__concat 运算符 ..
__eq 运算符 ==
__lt 运算符 <
__le 运算符 <=
__call 当函数调用
__tostring 转化为字符串
__index 调用一个索引
__newindex 给一个索引赋值

__index取下标操作用于访问`table[key]

__newindex赋值给指定下标`table[key]=value

__tostring转换成字符串

__call当Lua调用一个值时调用

__mode用于弱表`week table

__metatable用于保护metatable不被访问

  • __add

当Lua试图将两个表相加时,会首先检查两个表之一是否有元素,然后检查该元表中是否具有一个叫做__add的字段。如果Lua找到了该字段,则会调用该字段对应的值。这个值就是所谓的“元方法”。


local tbl1 = {1,2,3}
local tbl2 = {5,1,1}
-- print(#tbl1, #tbl2) -- 无法直接相加两个表
-- printf(tbl1 + tbl2) -- 实现表的相加操作
mt = {}
mt.__add = function(x, y)
local result = {}
local length = #x
for i=1,length do
result[i] = x[i] + y[i]
end
return result
end
-- test
-- 设置表的元表
setmetatable(tbl1, mt)
setmetatable(tbl2, mt)
-- 执行表的相加操作
local result = tbl1 + tbl2
-- 循环输出
for k,v in ipairs(result) do
print(k, v)
end
1	6
2 3
3 4
  • __concat 表连接
-- 表的连接
local tbl1 = {1,2,3}
local tbl2 = {2,3,4} -- 实现表的相加操作
mt = {}
mt.__add = function(x, y)
local result = {}
local length = #x
for i=1,length do
result[i] = x[i] + y[i]
end
return result
end
-- 实现表的连接操作
mt.__concat = function(x, y)
local length = #x
local result = {}
for i=1,length do
result[i] = x[i].."**"..y[i]
end
return result
end
-- 设置表的元表
setmetatable(tbl1, mt)
setmetatable(tbl2, mt)
-- 执行表的连接操作
local result = tbl1..tbl2
-- 循环输出
for k,v in ipairs(result) do
print(k, v)
end
1	1**2
2 2**3
3 3**4

下面举一个例子,集合运算

使用表table表示集合,实现集合计算中的并集、交集等。大家看一下写法,体会一下元表的运用


-- 定义集合
Set = {}
-- 定义集合的元表
local mt = {} -- 创建集合,根据参数列表值创建新集合
function Set.new(arr)
local set = {}
setmetatable(set, mt) --创建集合均具有一个相同的元表
for i,v in ipairs(arr) do
set[v] = true
end
return set
end
-- 求集合并集
function Set.union(x, y)
local result = Set.new{}
for k,v in pairs(x) do
result[k] = true
end
for k,v in pairs(y) do
result[k] = true
end
return result
end
-- 求集合交集
function Set.intersection(x, y)
-- 创建空集合
local result = Set.new{}
for k in pairs(x) do
result[k] = y[k]
end
return result
end
-- 将集合转换为字符串
function Set.tostring(set)
-- 定义存放集合中所有元素的列表
local result = {}
for k in pairs(set) do
result[#result + 1] = k
end
return "{"..table.concat(result, ", ").."}"
end
-- 打印输出集合元素
function Set.print(set)
print(Set.tostring(set))
end -- 设置集合元方法
mt.__add = Set.union -- 测试
local set1 = Set.new{10,20,30,40}
local set2 = Set.new{30, 1,50} Set.print(set1 + set2) -- {1, 40, 10, 20, 30, 50}
Set.print(Set.intersection(set1, set2)) -- {30}
  • __call

__call可以让table当做一个函数来使用。

local mt = {}
--__call的第一参数是表自己
mt.__call = function(mytable,...)
--输出所有参数
for _,v in ipairs{...} do
print(v)
end
end t = {}
setmetatable(t,mt)
--将t当作一个函数调用
t(1,2,3)
1
2
3

再举一个例子,注意call 里面的参数调用

local mt = {}
sum = 0
--__call的第一参数是表自己
mt.__call = function(mytable,val)
--输出所有参数
for i = 1,#mytable do
sum = sum +mytable[i]*val
end
return sum
end t = {1,2,3}
setmetatable(t,mt)
--将t当作一个函数调用
print(t(5))
--30
  • __tostring

__tostring可以修改table转化为字符串的行为

local mt = {}
--参数是表自己
mt.__tostring = function(t)
local s = "{"
for i,v in ipairs(t) do
if i > 1 then
s = s..", "
end
s = s..v
end
s = s .."}"
return s
end t = {1,2,3}
--直接输出t
print(t)
--将t的元表设为mt
setmetatable(t,mt)
--输出t
print(t)
table: 0x7fcfe7c06a80
{1, 2, 3}
  • __index

调用table的一个不存在的索引时,会使用到元表的__index元方法,和前几个元方法不同,__index可以是一个函数也可是一个table。

作为函数:

将表和索引作为参数传入__index元方法,return一个返回值

local mt = {}
--第一个参数是表自己,第二个参数是调用的索引
mt.__index = function(t,key)
return "it is "..key
end t = {1,2,3}
--输出未定义的key索引,输出为nil
print(t.key)
setmetatable(t,mt)
--设置元表后输出未定义的key索引,调用元表的__index函数,返回"it is key"输出
print(t.key)
local tbl = {x=1, y=2}
-- table中字段默认值为nil
print(tbl.x, tbl.y, tbl.z) -- 1 2 nil
-- 通过metatable修改table的默认值
function setTableDefault(tbl, default)
local mt = {
__index = function()
return default
end
}
setmetatable(tbl, mt)
end
-- 调用setTableDefault后,任何对tbl中存在的字段的访问都回调用它的__index
setTableDefault(tbl, 1)
print(tbl.x, tbl.y, tbl.z) -- 1 2 1

作为table:

查找__index元方法表,若有该索引,则返回该索引对应的值,否则返回nil

local mt = {}
mt.__index = {key = "it is key"} t = {1,2,3}
--输出未定义的key索引,输出为nil
print(t.key)
setmetatable(t,mt)
--输出表中未定义,但元表的__index中定义的key索引时,输出__index中的key索引值"it is key"
print(t.key)
--输出表中未定义,但元表的__index中也未定义的值时,输出为nil
print(t.key2)
nil
it is key
nil
  • __newindex

__newindex__index类似,不同之处在于__newindex用于table的更新,__index用于table的查询;当为table中一个不存在的索引赋值时,会去调用元表中的__newindex元方法

1.作为函数

__newindex是一个函数时会将赋值语句中的表、索引、赋的值当作参数去调用。不对表进行改变

local mt = {}
--第一个参数时表自己,第二个参数是索引,第三个参数是赋的值
mt.__newindex = function(t,index,value)
print("index is "..index)
print("value is "..value)
end t = {key = "it is key"}
setmetatable(t,mt)
--输出表中已有索引key的值
print(t.key)
--为表中不存在的newKey索引赋值,调用了元表的__newIndex元方法,输出了参数信息
t.newKey = 10
--表中的newKey索引值还是空,上面看着是一个赋值操作,其实只是调用了__newIndex元方法,并没有对t中的元素进行改动
print(t.newKey)
it is key
index is newKey
value is 10
nil

-- 定义原表
local mt = {}
mt.__index = function(tbl, key)
return mt[key]
end
mt.__newindex = function(tbl, key, value)
mt[key] = value
print(string.format("modify: key=%s value=%s", key, value))
end local window = {x=1}
setmetatable(window, mt) print(window.x) -- 1
print(rawget(window, x)) -- nil -- 添加属性
print ("-------------")
window.y = 2
print ("-------------")
for k,v in pairs(mt) do
print (k,v)
end
print ("-------------")
for k in pairs(mt) do
print (k)
end
print ("-------------")
print(window.y) -- 2
print(rawget(window, y)) -- nil
1
nil
-------------
modify: key=y value=2
-------------
__index function: 0x7fde254066f0
y 2
__newindex function: 0x7fde25406b00
-------------
__index
y
__newindex
-------------
2
nil

2.作为table

__newindex是一个table时,为t中不存在的索引赋值会将该索引和值赋到__newindex所指向的表中,不对原来的表进行改变。

local mt = {}
--将__newindex元方法设置为一个空表newTable
local newTable = {}
mt.__newindex = newTable
t = {}
setmetatable(t,mt)
print(t.newKey,newTable.newKey) --对t中不存在的索引进行负值时,由于t的元表中的__newindex元方法指向了一个表,所以并没有对t中的索引进行赋值操作将,而是将__newindex所指向的newTable的newKey索引赋值为了"it is newKey"
t.newKey = "it is newKey" print(t.newKey,newTable.newKey)
nil	nil
nil it is newKey

当然如果主表中存在该索引,自然会直接赋值,不会传递元表中赋值。我们也可以直接改写newindex,用rawset直接赋值


Window = {}
Window.mt = {} function Window.new(o)
setmetatable(o ,Window.mt)
return o
end
Window.mt.__index = function (t ,key)
return 1000
end
Window.mt.__newindex = function (table ,key ,value)
if key == "wangbin" then
rawset(table ,"wangbin" ,"yes,i am")
end
end
w = Window.new{x = 10 ,y = 20}
w.wangbin = "55"
print(w.wangbin)
yes,i am

rawget 和 rawset

有时候我们希望直接改动或获取表中的值时,就需要rawget和rawset方法了。

rawget可以让你直接获取到表中索引的实际值,而不通过元表的__index元方法。

rawget是为了绕过__index而出现的,直接点,就是让__index方法的重写无效

local mt = {}
mt.__index = {key = "it is key"}
t = {}
setmetatable(t,mt)
print(t.key)
--通过rawget直接获取t中的key索引
print(rawget(t,"key"))
it is key
nil

rawset可以让你直接为表中索引的赋值,而不通过元表的__newindex元方法。

local mt = {}
local newTable = {}
mt.__newindex = newTable
t = {}
setmetatable(t,mt)
print(t.newKey,newTable.newKey)
--通过rawset直接向t的newKey索引赋值
rawset(t,"newKey","it is newKey")
print(t.newKey,newTable.newKey)
nil	nil
it is newKey nil
local mt = {}
t = {}
setmetatable(t,mt)
rawset(t,"newKey","it is newKey")
for k ,v in pairs (t) do
print (k,v)
end print(t.newKey)
newKey	it is newKey
it is newKey

下面举几个例子,讲述一下各个方法之间的关系。

local tb = {}
setmetatable(tb, { __index = function()
return "not find"
end })
setmetatable(tb, { __newindex = function(table, key, value)
local patchKey = "version"
if key == patchKey then
rawset(table, patchKey, "补丁值")
else
rawset(table, key, value)
end
end })
-- setmetatable(tb, { __index = function()
-- return "not find"
-- end })
tb.version = "正常版本"
tb.date = "2018"
print(tb.version) --打印 补丁值
print(tb.server) --打印nil,不会调用__index方法了?
print(tb.date) --打印2018

经过测试发现:

如果__index在__newindex之前,则不会调用__index

如果把_index放在__newindex之后,调用不存在值,才会调用__index方法

--谁在后面就会调用谁,前一个会失效。但是这个取决于你定于元方法的方式(我们一般定义元方法方式如下),看下面的写法没问题;


local tb = {}
local mt ={}
mt.__newindex = function(table, key, value)
local patchKey = "version"
if key == patchKey then
rawset(table, patchKey, "补丁值")
else
rawset(table, key, value)
end
end mt.__index = function()
return "not find"
end
setmetatable(tb,mt) tb.version = "正常版本"
tb.date = "2018"
print(tb.version)
print(tb.server)
print(tb.date)
补丁值
not find
2018

rawget是为了绕过__index而出现的,直接点,就是让__index方法的重写无效

--- Gets the real value of `table[index]`, the `__index` metamethod. `table`
--- must be a table; `index` may be any value.
---@param table table
---@param index any
---@return any
function rawget(table, index) end
local tb = {}
local mt ={mm = "test"}
mt.__index = function()
return "not find"
end
setmetatable(tb,mt) tb.version = "正常版本"
print(tb.version)
print(tb.server) ---不存在的值,调用__index方法
--rawget是为了绕过__index而出现的,直接点,就是让__index方法的重写无效
print(rawget(tb, "version")) --打印 正常版本
print(rawget(tb, "server")) --打印nil

利用元表的特性实现对象继承


local function class( super )
local cls
if super then
cls = {}
cls.super = super
setmetatable(cls, {__index = super})
else
-- ctor是构造函数的命名
cls = {ctor = function () end}
end cls.__index = cls
function cls.new( ... )
local instance = setmetatable({}, cls)
instance:ctor()
return instance
end return cls
end
--测试实现部分
local Test = class()
function Test:doSomething()
print("test doSomething")
end
local test = Test.new()
test:doSomething() --测试继承部分
local Test = class()
function Test:doSomething()
print("test doSomething")
end
local Test2 = class(Test)
local test = Test2.new()
test:doSomething()

在new的时候,创建一个table并返回,即创建一个实例,实例可以有自己的字段,比如Test类的doSomething,该字段是个函数,可以调用执行。实例的元表是cls,如果调用实例没有的字段,会去cls里找

cls设置了元方法__index = cls

如果没有super,则只有一个构造函数方法

如果有super,cls的元表是super,元表的元方法也正确的设置了

所以,在Test2是继承自Test的,它的实例test调用doSomething,找不到,去元表里找,元表发现自己有父类,去父类里找,成功找到。

多继承

如果我想要继承多个父类,怎么办?

思路就是将元方法改成函数


local function search(key, tables)
for _, super in ipairs(tables) do
if super[key] then
return super[key]
end
end
return nil
end local function class(...)
local cls = { ctor = function () end}
local supers = {...}
setmetatable(cls, {__index = function (_, key)
-- 在查找table的时候,会把table的key传进来
return search(key, supers)
end}) function cls.new(...)
local instance = {}
setmetatable(instance, {__index = cls})
instance:ctor(...)
return instance
end
return cls
end local Human = class()
function Human:life()
print("almost 100 years.")
end
local Programmer = class()
function Programmer:coding()
print("sub 1 year.")
end
local My = class(Human, Programmer)
local You = My.new()
You:life()
You:coding()
almost 100 years.
sub 1 year.

解析:

在You里找不到life和coding字段,去找元表cls,调用元方法__index,__index调用函数search,把所有的父类都找一遍

成功找到

最新文章

  1. NetMQ(四): 推拉模式 Push-Pull
  2. Hololens 手势事件执行顺序
  3. java学习笔记(3)之面向对象(1)
  4. PHP 文件处理
  5. CST时间转换成 yyyy-MM-dd格式
  6. git--- 拉取代码
  7. Counterfeit Dollar -----判断12枚钱币中的一个假币
  8. thymeleaf 和其它标签组合 获取数据
  9. jquery图片3D旋绕效果 rotate3Di的操作
  10. docker进入容器
  11. 0.关于TCP协议的一些总结
  12. uboot的relocation原理具体分析
  13. 如何在分布式环境中同步solr索引库和缓存信息
  14. Input类型是checkbox时checked属性获取
  15. Gradle脚本打包so库
  16. ORA-12537: Network Session: End of file
  17. Linux系统安装Docker
  18. Duplicate entry &#39;xxx&#39; for key &#39;xxx&#39;
  19. C# 获取当前路径方法整理
  20. 快速入门 WePY 小程序【转】

热门文章

  1. RH124-3 目录结构_转
  2. Postman 下载和使用
  3. 结合 Nginx 谈谈 Http 状态码
  4. python 脚本定时删除 elk索引
  5. 4.JVM 实战操作
  6. 【坑】Mybatis 多次逆向工程生成mapper文件
  7. 开始使用 git(配置+常用命令)
  8. MySQL之创建用户和授权
  9. Eureka常见问题
  10. Foxmail7.2的账号密码的备份与恢复