七.编译
我们知道一个lua文件是作为一个代码块(chunk)存在的,其实质就是一个函数。因此可以在一个lua文件中调用另一个lua文件。
1.loadfile(只编译不运行)
--Test.lua print("hello"); function hehe() print("hehe"); end
loadfile("Test.lua") print("end") --end
dofile("D:\\Lua\\HelloLua\\Test.lua") --hello print("end") --end
for i = 1,2 do require("Test") --hello end print("end") --end
f = loadstring("i = i + 1") i = 0 f();print(i) --1 f();print(i) --2上面的代码还可以写成:
i = 0 loadstring("i = i + 1")();print(i) --1 loadstring("i = i + 1")();print(i) --2
i = 0 assert(loadstring("i = i + 1"))() print(i) --1
通常,我们可以将数字相加,可以连接字符串等等,但是我们无法将两个table相加,无法对函数作比较等等。利用元表和元方法,我们就可以实现自定义的操作,实现table相加,函数比较等。例如,假如a和b都是table,当试图将它们相加时,lua会先检查两者之一是否有元表,然后检查该元表是否有一个__add(注意是两个下划线)的字段。如果lua找到了该字段,就会调用该字段对应的值。这个值就是所谓的"元方法",它应该是一个函数。
lua中的每一个值都有一个元表。table和userdata可以有各自独立的元表,而其他类型的值则共享其类型所属的单一元表。lua在创建新的table时不会创建元表。
t = {} print(getmetatable(t)) --nil t1 = {} setmetatable(t,t1) --设置t的metatable为t1 print(assert(getmetatable(t) == t1)) --true
table访问的元方法
1.__index元方法(注意是两个下划线)
当访问一个table中不存在的字段时,得到的结果为nil。这是对的,但并非完全正确。实际上,这些访问会促使解释器去查找一个叫__index的元方法。如果没有这个元方法,那么才会返回nil,否则,就由这个元方法来提供最终结果。
window = {} window.prototype = {x=0,y=0,width=100,height=100} window.mt = {} --创建元表 function window.new(o) --非全局函数 setmetatable(o,window.mt) return o end window.mt.__index = function (table,key) return window.prototype[key] end w = window.new{x=10,y=20} --只有一个参数时可省略括号 print(w.width) --100
在lua中,将__index元方法用于继承是很普遍的方法,因此lua还提供了一种更便捷的方式来实现此功能。__index元方法不必一定是一个函数,它还可以是一个table。当它是一个函数时,lua以table和不存在的key作为参数来调用该函数,这就如同上述内容。而当它是一个table时,lua就以相同的方式来重新访问这个table。因此,上例中__index的声明可以简单地写为:
window.mt.__index = window.prototype现在,当lua查找到__index字段时,发现__index字段的值是一个table,那么lua就会在window.prototype中继续查找。也就是说,lua会在这个table中重复这个访问过程,类似于执行这样的代码:
window.prototype["width"]然后由这次访问给出想要的结果。如果不想在访问一个table时涉及到它的__index元方法,可以使用函数rawget。调用rawget(t,i)就是对table进行了一个"原始的(raw)"访问,也就是一次不考虑元表的简单访问。
2.__newindex元方法(注意是两个下划线)
__newindex元方法与__index类似,不同之处在于前者用于table的更新,而后者用于table的查询。当对一个table中不存在的索引赋值时,解释器就会查找__newindex。如果有这个元方法,解释器就会调用它,而不是执行赋值。如果这个元方法是一个table,解释器就在此table中执行赋值,而不是对原来的table。此外,还有一个原始函数允许绕过元方法:调用rawset(t,k,v)就可以不涉及任何元方法而直接设置table t中与key k相关联的value v。
组合使用__index和__newindex元方法就可以实现出lua中的一些强大功能,例如,只读的table、具有默认值的table和面向对象编程中的继承。
具有默认值的table
function setDefault(t,defaultValue) local mt = {__index = function () return defaultValue end} setmetatable(t,mt) end a = {x=10,y=20} print(a.x,a.z) --10 nil setDefault(a,0) print(a.x,a.z) --10 0
只读的table
function readOnly(t) local proxy = {} local mt = { __index = t, __newindex = function (t,k,v) error("attempt ro update a readOnly table",2) end } setmetatable(proxy,mt) return proxy end days = readOnly({"Sunday","Monday","Tuesday"}) print(days[1]) --Sunday days[2] = "Noday" --弹出错误
3.__call元方法(注意是两个下划线)
function f1() return "hello"; end function f2(t) return t.n; end function f3(x) return x; end function f4(t,x,y) return t.n + x + y; end a = {} a.n = 100 b = {} setmetatable(a,b) b.__call = f1; print(a())--hello b.__call = f2; print(a())--100 b.__call = f3; print(a)--table: 003AE3A0 --默认第一个参数为调用者本身,此时的1000无用 print(a(1000))--table: 003AE3A0 b.__call = f4; print(a(1,2))--103