这一部分应该挺重要的,Lua中唯一的数据结构便是table,几乎所有的的数据操作都是在table的基础上进行。而本文提到的元表和元方法,便是帮助table实现更强大的功能而设计的。
日期:2014.7.11

Part Ⅱ

Metatables and Metamethods
Lua中不能直接对table进行相加、比较等操作。除非使用元表(Metatables)。元表可以使得我们改变元素在处理未定义操作的应对行为,如定义两个table直接的相加操作。Lua在处理两个table的相加操作时会首先检查两个table是否有元表,且元表中是否有 __add 元方法字段,如果有这个字段则lua会遵循这个字段内定义的操作执行两个table的相加操作。
Lua中各个类似的变量有一个相关联的元表?(到底有没有?),而table与userdata有各自独立的元表。默认的,新创建的table是没有元表的:
e.g.
t = {}
print(getmetatable(t))          --nil
此时我们可以通过setmetatable方法来设置元表,元表其实也相当于一个table
e.g.
t1 = {}
setmetatable(t,t1)
print(getmetatable(t) == t1)          --ture
当然,在lua中我们只可以对table执行setmetatable操作,对其余类型的变量执行这个操作需要使用C 代码。书上string库涉及到了给string类型变量执行设置元表的操作。其余类型的变量默认是没有元表的?
print(getmetatable("ss"))               -- table
print(getmetatable(10))                  --nil
Arithmetic Metamethods
算术运算元方法
这里介绍元表的使用,在这里用一个table表示set,我们需要运算set的并集等操作
e.g.
Set = {}
local mt = {}          --metatable for setsfunction Set.new(l)     --新建一个set,初始化并且设置其元表local set = {}     setmetatable(set,mt)for _ v in ipairs(l) do set[v] = true endreturn set
end
这样每次我们新建一个set其都会有同样的元表:
s1 = Set.new{10,20,11,13}
s2 = Set.new{30,1}
print(getmetatable(s1))          --table: 0x7fa1eb4093a0
print(getmetatable(s2))          --table: 0x7fa1eb4093a0

给元表添加元方法:__add 字段表示table如果执行相加操作
mt.__add = Set.union     --注意此时的 __add 字段还是不能使用的,因为Set.union 还未定义,而且这段代码也需要放在定义Set.union之后,否则会报错。正确的用法是先定义再赋值
--假若mt.__add = Set.union
--定义Set.union
function Set.union( a,b )local res = Set.new{}for k in pairs(a) dores[k] = trueendfor k in pairs(b) dores[k] = trueendreturn res
end

--此时
s3 = s1 + s2 会报错     --attempt to perform arithmetic on global 's1' (a table value)
--需要将mt.__add = Set.union 放置在定义Set.union之后。
同样的,设置 __mul 元方法也是类似的要求
--定义 Set.intersection
function Set.intersection( a,b )local res = Set.new{}for k in pairs(a) dores[k] = b[k]endreturn res
end
mt.__mul = Set.intersection

所有的算术运算元方法:
__add(加)、__mul(乘)、__sub(减)、__div(除)、__unm(负)、__mod(取模)、__pow(取幂)、__concat(连接)
Lua在处理两个变量的算术运算时,针对不同类型的变量,如
e.g.
s = Set.new {1,2,3}
s = s + 8
此时运行的话会报错:
--bad argument #1 to 'pairs' (table expected, got number) 
其处理两个变量的算术运算遵循的步骤是:如果第一个变量定义了元方法则使用第一个变量的元方法,而不会再考虑第二个元素的元方法;第一个没有而第二个有,则使用第二个的;否则就会报错。
因此为了更好的控制程序运行,我们需要限制两个变量为同类型拥有同一个元表,以__add为例,可以如下操作:
function Set.union( a,b )if getmetatable(a) ~= mt or getmetatable(b) ~= mt thenerror("xxx",2)     --注意这里的参数是2,表示是提醒用户是传递的参数有问题local res = Set.new{}for k in pairs(a) dores[k] = trueendfor k in pairs(b) dores[k] = trueendreturn res
end

Relational Metamethods
关系运算元方法
Lua中的关系运算元方法主要有:
__eq(相等)、__lt(小于)、__le(小于或等于)。而对于其他的关系操作符,Lua直接做了转换:a ~= b 相当于 not (a == b) 、a > b 相当于 b < a、a >= b 相当于 b <= a;
关系运算元方法的具体使用类似于上文提到的算术运算元方法;
要注意的是,当两个变量的元方法不同的时候执行相等关系运算会返回false;
Library-Defined Metamethods
库定义的元方法
__tostring 元方法 
当调用tostring函数的时候,函数首先会寻找变量是否有__tostring 元方法:
同上文:
s = Set.new{1,2,2}
print(s)                                --table: 0x7f8349403d30
print(getmetatable(s))           --table: 0x7f8349409fd0

此时打印出来的并不是其值,但也不是其元表
--to string
function Set.tostring( set )local l = {}for e in pairs(set) dol[#l + 1] = eendreturn "{" .. table.concat(l," , ") .. "}"
end

mt.__tostring = Set.tostring
print(s)                                   --{1,2,2}

在设置了其元方法之后,才会正确的打印出其值
当然我们可以通过一定的方法来保护我们的元表,setmetatabe和getmetatable也是使用到了元方法,我们可以根据这一特性达到我们的目的:
e.g.
mt.__metatable = "cannot change"
s1 = Set.new{}
print(getmetatable(s1))          --cannot change

而当我们想改变其元表的时候
e.g.
setmetatable(s1,mt)               --error:cannot change a protected metatable 

会报错,不能修改其元表,这样就达到了我们要保护元表的目的
Table-Access Metamethods
Lua允许通过元表来控制修改和访问table中不存在的元素的行为
The __index metamehod
当我们试图访问一个table中不存在的元素时,我们得到的值将会是nil。这是一般意义上将的,事实上,我们访问table中不存在的元素的时候,会触发编译器寻找__index 元方法,当没有该方法的时候会返回nil;而当该元方法存在被定义了,将会返回该方法定义的操作。
这一个特性对我们使用继承机制,继承默认变量的时候有很大的帮助,书上也是以这个为例子做了讲解:
--有默认变量的table
prototype = {x = 0,y = 0,width = 100,height = 100}
--元表
mt = {}
--构造函数
function new(o)setmetatable(o,mt)return o
end

--定义元方法
mt.__index = function(_,key)return prototype[key]
end

--创建一个新的table,使用继承机制,需要技能默认变量的table
w = new{x = 10,y = 20}
print(w.width)           --100

此时w使用到了prototype里面的值
__index 元方法不一定需要是一个函数,也可以是一个table。当该方法是一个函数的是,Lua会执行函数里定义的操作,当是一个table的时候,Lua直接在table中执行访问操作。
函数 rawget(t,i) 可以使得我们访问table各个元素的时候不去调用 __index 操作.将会对t执行raw访问?啥意思
The __newindex metamethod
该元方法的作用表现在更新table中元素值的时候。当我们试图给table中不存在的键赋值的时候,编译器会寻找 __newindex 的元方法,如果有编译器将会执行该方法定义的操作,否则就会直接赋值。这里也有一个函数 rawset(t,k,v),该函数会绕开元方法,直接在t中对键k设置值v。
书中提到,有效的结合__index 和 __newindex 两个元方法的使用将会带来很强大的设计技巧,如创建只读table,带默认值的table等。
Tables with default values
table带有默认的值,其原理主要是在我们访问一个table中不存在或者没有赋值的键的时候,返回值是一个固定值,这里就涉及到了 __index 元方法
e.g.
function setDefault( t,d )local mt = {__index = function ( ... )return dend}setmetatable(t,mt)
end
tab = {x = 10,y = 20}
print(tab.x,tab.z)               --10,nil
setDefault(tab,0)
print(tab.x,tab.z)               --10,0

以上操作就为tab设置了默认值0,如果试图访问tab中没有定义或者不存在的元素,返回值将会是0
为多个不同的table执行设置多个不同默认值的操作
e.g.
local mt = {__index = function (t) return t.___ end}
function setDefault (t,d)t.___ = dsetmetatable(t,mt)
end

这里的技巧在于,元表的定义在函数的外部,且将默认值存储在了要设置默认值的table本身的内部。
避免命名冲突的操作:
e.g.
local key = {}          --以一个table作为key
local mt = {__index = function (t) return t[key] end}
function setDefault (t,d)t[key] = dsetmetatable(t,mt)
end

Tracking table accesses
有效的使用__index 和 __newindex 可以帮助我们监控对table的访问和赋值操作。结合使用 proxy (代理) 便可以追踪所有对table的访问操作并且追踪到其访问的值。书上提到的只有当table为空的时候才能捕获到所有对其的访问操作,为啥?
t = {}     --original table
--keep a private access to the original table
local _t = t
--create proxy 代理
t = {}
--create metatable
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) .. " to " .. tostring(v))_t[k] = v      --update original tableend
}
setmetatable(t,mt)t[2] = "hello"
print(t[2])

打印出来的,追踪了table从赋值到访问的过程
*update of element 2 to hello
*access to element 2
hello

Read-only tables
只读table
只读table的原理主要就是在试图给table赋值的时候做限制,这里就涉及到了__newindex 元方法的使用。
e.g.
--read only table
function readOnly (t)local proxy = {}local mt = {__index = t,__newindex = function (t,k,v)error("attempt to update a read-only table",2)      --在试图改变元素的时候抛出错误,且参数为2,表示在报错的地方将会是调用该方法的地方。end}setmetatable(proxy,mt)return proxy
end

通过在__newindex元方法里面做恰当的修改,便能将我们的table改写为只读table。

《Programming in Lua 3》读书笔记(十)相关推荐

  1. OREILLY Programming .NET 3.5 读书笔记之一

    OREILLY Programming .NET 3.5 读书笔记之一 <Programming .NET 3.5>是OREILLY 2008.08出版的.NET 3.5 开发书籍,作者是 ...

  2. 主成分分析碎石图_ISLR读书笔记十九:主成分分析(PCA)

    本文使用 Zhihu On VSCode 创作并发布 前面写的一些统计学习方法都是属于监督学习(supervised learning),这篇主成分分析(principal components an ...

  3. 计算机英语读书笔记,大学英文读书笔记范文英语读书笔记十篇带翻译.doc

    大学英文读书笔记范文英语读书笔记十篇带翻译 My Room This is my room. Near the window there is a desk. I often do my homewo ...

  4. 图解HTTP读书笔记(十)

    图解HTTP读书笔记(十) Web的攻击技术 HTTP协议本身并不存在安全性问题,因此协议本身几乎不会成为攻击对象.应用HTTP协议的服务器和客户端,以及运行在服务器上的Web应用资源才是攻击目标. ...

  5. 《Programming in Scala》读书笔记(持续更新) - passover的个人空间 - DOIT博客 - 多易网...

    <Programming in Scala>读书笔记(持续更新) - passover的个人空间 - DOIT博客 - 多易网 <Programming in Scala>读书 ...

  6. PHP第十次实验总结,The Clean Architecture in PHP 读书笔记(十)

    laravel 这是clean architecture的第十篇,也是具体案例的第二篇,本篇会通过使用laravel框架,来开发我们的应用. 本文为系列文章的第十篇,完成的目录请查看Clean Arc ...

  7. 《联邦学习实战》杨强 读书笔记十四——构建公平的大数据交易市场

    当数据具有资产属性之后,数据便可以直接或者间接地为公司.为社会创造价值和收益,并且可以作为一种特殊的商品在市场中进行交易. 与传统的商品交易相比,数据资产交易的市场前景更广阔,但同时也面临着很多的挑战 ...

  8. 《Programming in Lua 3》读书笔记(十二)

    日期:2014.7.14 PartⅡ Object-Oriented Programming Lua中实现面向对象编程. "如同OOP对象,table拥有状态:如同OOP对象,table拥有 ...

  9. 《把时间当作朋友》读书笔记(十四)--积累(二)

    节省与否 有些钱真不能省,否则注定"屌丝"一生,因为这种"节省"可能会伴随有很高的隐性成本,或者将来造成很大的负担----今天看来省了,可将来却要因此付出极大的 ...

最新文章

  1. 京东Vue组件库NutUI 2.0发布:将支持跨平台!
  2. Robots.txt和Robots META
  3. python 自学需要多久-怎么自学python,大概要多久?
  4. java 同步锁_java线程中的同步锁和互斥锁有什么区别?
  5. 前端学习(806):数据类型内存分配
  6. PyTorch 1.0 中文官方教程:使用 PyTorch C++ 前端
  7. python拦截tcp数据包_发送低级原始tcp数据包python
  8. Mysql 日志管理详解
  9. 使用fastjson读取超巨json文件引起的GC问题
  10. php 构造函数参数传值,php 构造函数参数
  11. 怎么隐藏电脑桌面计算机,怎么隐藏电脑桌面软件
  12. ubuntu20.04离线安装rabbitvcs
  13. oracle pdb启动日志,案例:Oracle 12C 数据库pdb丢失数据文件后的完整恢复过程
  14. win10证书服务器不可用怎么办,四种方法解决Win10专业版RPC服务器不可用的问题...
  15. 光缆定位仪光衰点定位光纤识别方法
  16. [BZOJ]4453: cys就是要拿英魂!
  17. java和大数据开发该选择哪个好就业?
  18. H5+Android混合开发电视APP
  19. 【Jsoup】 基本使用
  20. java中几种常用的对象类型(po,vo,bo,dto)

热门文章

  1. 如何将音乐添加到PowerPoint演示文稿
  2. 【C语言】#文件操作#有5个学生,每个学生有3门课程的成绩,从键盘输入以上数据(包括学号、姓名、3门课成绩),计算出平均成绩,将原有数据和计算出的平均分数存放在磁盘文件stud中。
  3. 有功功率、无功功率 和 视在功率之间的关联
  4. SQL SERVER(32)Transact-SQL概述
  5. 厦门大学计算机学院李平,学术盛会,知行合一 ——2020年厦门大学信息学院计算机科学系研究生学术论坛圆满举办...
  6. 对算法的时间复杂度的理解
  7. svg上传服务器无法显示,让WordPress支持上传SVG格式图片并显示在媒体库中的方法...
  8. 最近有朋友问我,如何在自媒体上快速发文章?
  9. PS初学者的一些实用技巧
  10. APP切换到后台时的运行规则以及如何实现后台运行