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

智能推荐

C语言 openssl库 AES对称加解密 CBC模式 PKCS7Padding填充 256秘钥 带16偏移 base64编解码_openssl pkcs7padding_一个菜鸡的学习记录的博客-程序员秘密

最近学习对接平台 需要用到 AES BASE64 加密对接接口非常少的资料而且满足不了需要或者有bug,譬如无偏移,base解码超位数输出不正常等最后整理出如标题所示的结果希望能帮助到有需要的朋友 也给自己往后回顾用使用前需要安装openssl库使用方法:&lt;./aes 数据&gt; &lt;例子:./aes test&gt; &lt;注意:有空格会被当成多个参数&gt;功能介...

iOS 动画篇 - CAAnimation初识_LOLITA0164的博客-程序员秘密

CAAnimation 是一个抽象动画类,无法直接使用,需要使用它的子类,动画分为两大类:属性动画和转场动画。属性动画顾名思义,针对图层可动画属性进行动画,转场动画通常用于视图切换,例如A视图切换为B视图。CAAnimation 针对的是图层而非视图,因此默认情况下,它在完成动画后恢复到最初状态,且不会改变视图的相关属性。

SQL语言和T-SQL语言简介_life is wonderful的博客-程序员秘密

SQL语言简介       结构化查询语言(SQL)是一个非过程化的语言,它一次处理一个记录,对数据提供自动导航。SQL允许用户在高层的数据结构上工作,而不对单个记录进行操作,可操作记录集。所有SQL语句接受集合作为输入,返回集合作为输出。SQL的集合特性允许一条SQL语句的结果作为另一条SQL语句的输入。SQL不要求用户指定对数据的存放方法。这种特性使用户更容易集中精力于要得到的结果。所有的

Mockito怎么样Mock返回值为空的方法_chenhailong118的博客-程序员秘密

Mockito怎么样Mock返回值为空的方法? Mockito这个类是很轻松mock带有返回值的方法。 但是当遇到没有返回值的方法时,就非常的麻烦了。下面提出解决方法:  public class People{ public void sayHello(String str){ System.out.println(st...

python pct_change_Python Pandas,使用pct_change函数重新采样数据_weixin_39578899的博客-程序员秘密

在Pandas 0.18或更高版本中,可以使用^{}:def percent_change(x):if len(x):return (x[-1]-x[0])/x[0]ser.resample('60T', base=30).apply(percent_change)它产生了^{pr2}$如果没有base=30,ser.resample('60T')会将序列重新采样为60分钟的间隔(分钟和秒等于0)...

AT+CNMI之自动接收短信_mchp的博客-程序员秘密

AT+CNMI的使用 短消息类(class)的概念:根据指定储存的位置,短消息分为class 0 – 3四个类。也可以不指定类(no class),由ME按默认设置进行处理,存储到内存或者SIM卡中。在TPDU的TP-DCS字节中,当bit7-bit4为00x1, 01x1, 1111时,bit1-bit0指出消息所属类: 00 – class 0:只显示,不储存 01 – class 1:储存在

随便推点

浅谈算法和数据结构: 七 二叉查找树_Hunter_first的博客-程序员秘密

浅谈算法和数据结构: 七 二叉查找树前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的时候具有较高的灵活性,而有序数组在查找时具有较高的效率,本文介绍的二叉查找树(Binary Search Tree,BST)这一数据结构综合了以上两种数据结构的优点。二叉查找树具有很高的灵活性,对其优化可以生成平衡二叉树,红黑树等高效的查找和插入数据结构,后文会一一介绍。一 定义

HTML: button和input button的区别_taizhoufox的博客-程序员秘密

感谢原文作者:http://www.cnblogs.com/youjiangnan/archive/2009/07/08/button-input-button.html 一句话概括主题:具有相同的作用但是在可操控性方面更加强大。HTML 4.01规范的Forms部分指名表单有以下几种控制类型:buttons, checkboxes, radio buttons

10分钟理解ES6箭头函数_箭头函数可以实例化吗_°PJ想做前端攻城狮的博客-程序员秘密

前言面试中,ES6 是一大考点,当被问到箭头函数时,我们都会说:箭头函数很好用,而且再也不用操心 this 的指向了。面试官:箭头函数是挺好用的,那有哪些不适合使用箭头函数的场景呢?箭头函数在大多数情况下,是很好用的,但是为什么在有些场景,使用箭头函数后会产生问题?是不是箭头函数还不够完善?又有哪些场景会发生问题?该如何解决呢?为了防止血案的产生,重新吧这一块拎出来整理巩固一下。概念ES6允许使用箭头(=&gt;)定义函数,箭头函数提供了一种更加简洁的函数书写方式,箭头函数多用于匿名函数的定

编程基本功:代码即注释,文字类注释要谨慎_柳鲲鹏的博客-程序员秘密

  关于注释,吾有几个言论:注释无用。 注释有害。因为对错不知。写的人意思很难表达清楚,表达清楚了阅读者很难正确理解。 前一文,吾提出一个惊天神论:注释是程序员赠送给公司的,不写也没错。这比注释无用论、有害论更进了一步。  有人就奇怪了,大哥你说注释无用、注释有害,那么什么有用?当然是代码啊。代码是程序员的工作,做好工作才是关键。实际上现在要求写好代码,代码就是注释。包括:命名清楚、准确。文件名、类名、函数名、字段名、参数名、变量名,没有歧义。比如说,在视图中不要用left表示剩下的意思,因为

openWRT各功能软件包及其依赖库配置选项_libip4tc2_yhcs1213的博客-程序员秘密

1.访问路由器的网页:除了需要uhttpd Web服务器外,还需luci这一网页GUI(feeds/luci,一般也都在该目录下开发,最终编译的内容在rootfs的/usr/lib/lua目录下)。安装luci网页GUI:./scripts/feeds update packages luci./scripts/feeds install -a -p luciLUCI --

[算法题解详细]DFS解力扣463岛屿的周长_程序员云锦的博客-程序员秘密

题目给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。示例1输入:grid = [[0,1,

推荐文章

热门文章

相关标签