lua的元表_linuxheik的博客-程序员秘密

技术标签: lua  

lua的元表

 

metatable是Lua中的重要概念,每一个table都可以加上metatable,以改变相应的table的行为。让我们看一个例子:

t = {} -- 普通的table
mt = {} -- metatable
setmetatable(t, mt) -- 设定mt为t的metatable
getmetatable(t) -- 返回mt

使用getmetatablesetmetatable来查看和设定metatable。当然,上面的代码也可以压缩成一行:

t = setmetatable({}, {})

这是因为setmetatable会返回它的第一个参数。

metatable可以包括任何东西,metatable特有的键一般以__开头,例如__index__newindex,它们的值一般是函数或其他table。

t = setmetatable({}, {
  __index = function(t, key)
    if key == "foo" then
      return 0
    else
      return table[key]
    end
  end
})

__index

这是metatable最常用的键了。

当你通过键来访问table的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index键。如果__index包含一个表格,Lua会在表格中查找相应的键。

other = { foo = 3 }
t = setmetatable({}, { __index = other })
t.foo -- 3
t.bar -- nil

如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。

__newindex

类似__index__newindex的值为函数或table,用于按键赋值的情况。

other = {}
t = setmetatable({}, { __newindex = other })
t.foo = 3
other.foo -- 3
t.foo -- nil

t = setmetatable({}, {
  __newindex = function(t, key, value)
    if type(value) == "number" then
      rawset(t, key, value * value)
    else
      rawset(t, key, value)
    end
  end
})

t.foo = "foo"
t.bar = 4
t.la = 10
t.foo -- "foo"
t.bar -- 16
t.la -- 100

上面的代码中使用了rawgetrawset以避免死循环。使用这两个函数,可以避免Lua使用__index__newindex

运算符

利用metatable可以定义运算符,例如*

t = setmetatable({ 1, 2, 3 }, {
  __mul = function(t, other)
    new = {}

    for i = 1, other do
      for _, v in ipairs(t) do table.insert(new, v) end
    end

    return new
  end
})

t = t * 2 -- { 1, 2, 3, 1, 2, 3 }

__index__newindex不同,__mul的值只能是函数。与__mul类似的键有:

  • __add (+)
  • __sub (-)
  • __div (/)
  • __mod (%)
  • __unm 取负
  • __concat (..)
  • __eq (==)
  • __lt (<)
  • __le (<=)

__call

__call使得你可以像调用函数一样调用table:

t = setmetatable({}, {
  __call = function(t, a, b, c, whatever)
    return (a + b + c) * whatever
  end
})

t(1, 2, 3, 4) -- 24

这是很有用的特性。需要以直接调用table的形式调用table中的某个(默认)函数的时候,使用__call设定很方便。例如,kikitotween.lua,就用了这个技巧,这样直接调用tween就可以调用tween.start。再如MiddleClass中,类的new方法可以通过直接调用类的方式调用。

__tostring

最后讲下__tostring,它可以定义如何将一个table转换成字符串,经常和print配合使用,因为默认情况下,你打印table的时候会显示table: 0x<16进制数字>

t = setmetatable({ 1, 2, 3 }, {
  __tostring = function(t)
    sum = 0
    for _, v in pairs(t) do sum = sum + v end
    return "Sum: " .. sum
  end
})

print(t) -- prints out "Sum: 6"

例子

综合运用以上知识,我们编写一个2D矢量类:

Vector = {}
Vector.__index = Vector

function Vector.__add(a, b)
  if type(a) == "number" then
    return Vector.new(b.x + a, b.y + a)
  elseif type(b) == "number" then
    return Vector.new(a.x + b, a.y + b)
  else
    return Vector.new(a.x + b.x, a.y + b.y)
  end
end

function Vector.__sub(a, b)
  if type(a) == "number" then
    return Vector.new(b.x - a, b.y - a)
  elseif type(b) == "number" then
    return Vector.new(a.x - b, a.y - b)
  else
    return Vector.new(a.x - b.x, a.y - b.y)
  end
end

function Vector.__mul(a, b)
  if type(a) == "number" then
    return Vector.new(b.x * a, b.y * a)
  elseif type(b) == "number" then
    return Vector.new(a.x * b, a.y * b)
  else
    return Vector.new(a.x * b.x, a.y * b.y)
  end
end

function Vector.__div(a, b)
  if type(a) == "number" then
    return Vector.new(b.x / a, b.y / a)
  elseif type(b) == "number" then
    return Vector.new(a.x / b, a.y / b)
  else
    return Vector.new(a.x / b.x, a.y / b.y)
  end
end

function Vector.__eq(a, b)
  return a.x == b.x and a.y == b.y
end

function Vector.__lt(a, b)
  return a.x < b.x or (a.x == b.x and a.y < b.y)
end

function Vector.__le(a, b)
  return a.x <= b.x and a.y <= b.y
end

function Vector.__tostring(a)
  return "(" .. a.x .. ", " .. a.y .. ")"
end

function Vector.new(x, y)
  return setmetatable({ x = x or 0, y = y or 0 }, Vector)
end

function Vector.distance(a, b)
  return (b - a):len()
end

function Vector:clone()
  return Vector.new(self.x, self.y)
end

function Vector:unpack()
  return self.x, self.y
end

function Vector:len()
  return math.sqrt(self.x * self.x + self.y * self.y)
end

function Vector:lenSq()
  return self.x * self.x + self.y * self.y
end

function Vector:normalize()
  local len = self:len()
  self.x = self.x / len
  self.y = self.y / len
  return self
end

function Vector:normalized()
  return self / self:len()
end

function Vector:rotate(phi)
  local c = math.cos(phi)
  local s = math.sin(phi)
  self.x = c * self.x - s * self.y
  self.y = s * self.x + c * self.y
  return self
end

function Vector:rotated(phi)
  return self:clone():rotate(phi)
end

function Vector:perpendicular()
  return Vector.new(-self.y, self.x)
end

function Vector:projectOn(other)
  return (self * other) * other / other:lenSq()
end

function Vector:cross(other)
  return self.x * other.y - self.y * other.x
end

setmetatable(Vector, { __call = function(_, ...) return Vector.new(...) end })

原文 Lua Metatables Tutorial
翻译 SegmentFault

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/linuxheik/article/details/70155586

智能推荐

POJ 1365 Prime Land 素数_求100以内的素数调用prime(m)_Katapeltes的博客-程序员秘密

Prime Land Time Limit: 1000MS Memory Limit: 10000KB 64bit IO Format: %lld & %llu SubmitStatusDescription Everybody in the Prime Land is using a prime base number system. In this system, e

有关STM32外设配置的几个常见问题_luck_horse的博客-程序员秘密

作者:MilerShao 在做STM32开发应用的过程中,常常会遇到这样那样的问题,其中相当部分问题是与各外设及相关GPIO的配置有关的。就这方面的问题,这里一起总结交流下。目前的STM32芯片是基于ARM内核的可编程微处理器,我们可以简单地把内核以外的东西统称为外设,比方 TIMER、UART、SPI、USB、I2C、存储器等功能模块。以32F427芯片功能框图为例,那些红圈上的都是...

978. 最长湍流子数组--leetcode --c#_2笨鸟先飞2的博客-程序员秘密

题目描述:当 A 的子数组 A[i], A[i+1], ..., A[j] 满足下列条件时,我们称其为湍流子数组: 若 i &lt;= k &lt; j,当 k 为奇数时, A[k] &gt; A[k+1],且当 k 为偶数时,A[k] &lt; A[k+1]; 或 若 i &lt;= k &lt; j,当 k 为偶数时,A[k] &gt; A[k+1] ,且当 k 为奇数时,...

计算机视觉在农业领域中的应用_计算机视觉技术在农业中的应用案例_MrPhD的博客-程序员秘密

摘要:随着计算机等技术的不断发展,计算机视觉技术被广泛运用到各个领域中。与此同时,随着人口数量的增长、城市化进程导致耕地面积的减少,农业向着高质量、高产量方向的发展成为关键。将计算机视觉技术应用在农业领域能够使在一定程度上降低虫害等对农业的影响,推进农业向着高质量、高产量的方向不断发展。本文简要回顾计算机视觉领域的几个重要任务和方法,介绍当前计算机视觉技术在农业领域中的应用。关键词:计算机视觉;农...

Android studio安装Database Navigator插件出现错误Plugin DBN failed to initialize and will be disabled_database navigator安装失败_sunshine~~~的博客-程序员秘密

Plugin 'DBN' failed to initialize and will be disabled. Please restart Android Studio.com.intellij.openapi.extensions.impl.PicoPluginExtensionInitializationException: Duplicate registration for EP: com.intellij.externalSystemManager: original plugin com

Silverlight实例教程 - Navigation导航框架URI映射机制_导航映射原理_jv9的博客-程序员秘密

在上几篇Silverlight Navigation导航框架教程中,主要介绍了Silverlight Navigation导航框架基础,本篇开始将结合实例介绍Silverlight Navigation导航框架的应用。按照个人经验来讲,学习Silverlight Navigati

随便推点

桥接dns服务器未响应,子路由器IP地址怎么修改?_Lee的呼吸教室的博客-程序员秘密

问:子路由器IP地址怎么修改?很多时候一个路由器不能够满足上网需求,需要增加一个子路由器。那么子路由器的IP地址应该怎么修改,才能让子路由器上网呢?答:子路由器IP地址的修改有2种方式:1、子路由器IP修改为与主路由器IP不在同一个网段;2、子路由器IP修改为与主路由器IP在同一个网段。 不同的修改方式,会造成子路由器的上网设置也不相同。下面分别对这2种方式进行详细介绍。方法一、子路由器IP修改为...

Swiper 异步渲染 swiper-slide滑动失效_swiper的slide滑动有bug_Im灬大神的博客-程序员秘密

1.在Swiper初始化的时候设置两个属性就行,这样在你自己append元素的时候滑动就不会出现问题var galleryTop = new Swiper('.gallery-top', { observer: true,//修改swiper自己或子元素时,自动初始化swiper observeParents: true,//修改swiper的父元素时,自动初始化swiper });2.异步append HTML元素可能会出现,append出来的元素触发事件无效

设置GPS模块ublox 的波特率和数据输出格式_木木哈20171026的博客-程序员秘密

序:以下介绍的是通过u-blox公司的UBX协议来配合u-center软件来对u-blox公司的GPS模块进行模块的设置参数更改,UBX格式是u-blox公司独家开发且应用于所有自产的模块中的可支持的通信协议,UBX格式具体说明资料可以在本站内进行下载.​前言:设置前准备:安装u-center8.1x软件:一块GPS模块一个usb转串口线以及一台电脑以下都是经过亲手确认过。​1. 利用u-ce

摆脱无效报警?十年运维监控报警优化经验总结_焦振清的博客-程序员秘密

作者:焦振清运维工程师面试者第一个问题是:需要值班吗?笔者自己也曾经历过月入十万的时期,在那个时候,数个系统同时发布下一代版本,而老系统还需要过渡很长时间,工作量直接翻倍,大家只能勉强应付一线运维工作,团队成员开始陆续离职,而新人又无法在短时间内上手,整体情况不断恶化,持续半年左右才缓过劲来。下面两张截图是我挑选的两个团队一周报警数的对比图,前者的单日报警量最高是 55348 条,后者单日...

小菜编程成长记(十四 设计模式不能戏说!设计模式怎就不能戏说?)_weixin_34221276的博客-程序员秘密

(续上篇)         次日,小菜来到大鸟处。       “大鸟,你在写什么东西?”小菜看到大鸟的电脑上开着记事本。       “哦,我打算写篇博客,名字就叫《设计模式不能戏说?》”大鸟解释道。       “嘻嘻,废话,这又不是电视剧《戏说XX》,可以乱讲不负责任,设计模式戏说了如何讲得清楚。怎么突然会想起来写这样的文章?”       “你知道为什么《Head First Design...

推荐文章

热门文章

相关标签