淘先锋技术网

首页 1 2 3 4 5 6 7

七.编译

我们知道一个lua文件是作为一个代码块(chunk)存在的,其实质就是一个函数。因此可以在一个lua文件中调用另一个lua文件。

1.loadfile(只编译不运行)

--Test.lua
print("hello");
function hehe()
	print("hehe");
end
loadfile("Test.lua")
print("end")              --end


2.dofile(每调用一次就执行一次)

dofile("D:\\Lua\\HelloLua\\Test.lua")       --hello
print("end")              --end

3.require(在初次加载时执行一次)

for i = 1,2 do
	require("Test")       --hello
end
print("end")              --end

4.loadstring

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

为了清楚地显示错误消息,可以使用assert函数:

i = 0
assert(loadstring("i = i + 1"))()
print(i)                           --1


八.元表(metatable)和元方法(metamethod)

通常,我们可以将数字相加,可以连接字符串等等,但是我们无法将两个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都可以作为任何值的元表。在lua中,只能设置table的元表。若要设置其他类型的值的元表,则必须通过c代码来完成。


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检测到w中没有某字段,但在其元表中却有一个__index字段,那么lua就会以w(table)和width(不存在的key)来调用这个__index元方法。随后元方法用这个key来索引原型table,并返回结果。

在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