我们知道lua脚本语言的变量是弱类型的,即变量没有类型,值才有类型,同一名称的变量具体类型要看所赋值的类型,如下
a=1 --整型
a=1.0 --浮点型
a="ab" --string型
a={} --table型
a=function() ... end --function型
全局变量和局部变量,类似于shell脚本
全局变量:顾名思义,其生命有效期是全局的,整个lua文件中都可以使用,可以在任意地方定义(函数参数除外),但有个原则,使用时必须是先定义好的,否则就是nil,请看下面的代码
print(i);
function test(j)
i = 1;
end
test(); --如果不执行test(), i未定义,都是nil
print(i,j);
执行结果是
nil
1 nil
局部变量:只在某些特定的范围内有效的变量,称为局部变量,用local修饰。最主要的局部变量是定义在函数内部的局部变量。请看下面的代码片段
local j=2; --虽然是定义的local变量,但却是在函数外部,
--所以其作用域是它之后的整个文件,所以等同于全局变量
function test()
local i = 1; --局部变量,只在test函数内部有效
print("j="..j);
end
test();
print(i,j);
执行结果为:
j=2
nil 2
函数闭包
函数闭包:闭包(closure),通过调用含有一个内部函数加上该外部函数持有的外部局部变量(upvalue)的外部函数(就是工厂)产生的一个实例函数
闭包组成:外部函数+外部函数创建的upvalue+内部函数(闭包函数)
这里,test是外部函数, local i是upvalue, function() i=i+1.. 是内部函数
function test()
local i=0 --局部变量
return function() --尾调用,即尾部调用,将另一函数作为function类型返回
i = i+1
return i
end
end
c1=test()
c2=test(); --c1,c2是建立在同一函数,同一局部变量的不同实例上的两个不同的闭包
--闭包中的upvalue各自独立,调用一次test()就会产生一个新的闭包
print(c1()) -->1
print(c1()) -->2//重复调用时每一个调用都会记住上一次调用后的值,就是说i=1了已经被记住了
print(c2()) -->1//闭包不同所以upvalue不同
print(c2()) -->2
函数嵌套,即函数内部定义了另一个函数,注意不是函数调用。因为lua将函数也看成是一种基本数据类型,可以用来做赋值和返回。实际也是秉承了c语言指针概念的基础上做的转换,将函数的地址作为基本数据类型(个人理解,不确定正确,待考证)。
非局部变量,即上面提到的upvalue,这个值改如何理解,如果你有C语言基础,我们不妨先将它理解为函数内部定义的“静态(static)变量”,但upvalue它有自己的特点。
首先看upvalue的定义要求,一定是外部函数定义的的local局部变量,且在内部函数调用过。如下图中 j n k都是upvalue, 而h由于在内部函数中未调用,所以不是upvalue。(全局变量的作用域是全局的,所以谈论全局变量做upvalue无意义)
function newCounter() --外部函数
local j = 0; local n = 0;
local k = 0; local h = 0;
print("f1:",j,k,n); --cd1
return function() --内部函数
print("f2:",j,k,n); --cd2
j = n;
k = n;
n = n+1;
return j,k,n;
end
end
counter = newCounter(); --cd3
print("f3:",counter()); --cd4
print("----------分割线----------");
print("f4:",counter()); --cd5
执行结果
f1: 0 0 0
f2: 0 0 0
f3: 0 0 1
----------分割线----------
f2: 0 0 1
f4: 1 1 2
说明:cd3(code3)将函数赋值给counter,产生一个新实例,即生成一个闭包。从结果看cd4和cd5两次调用counter, 但外部函数的cd1只执行了一次,所以可以看出,闭包函数的外部函数部分只在第一次调用时执行,后面再次时调用直接跳到内部函数执行,所以upvalue的j k n的值只在第一次由外部函数初始化给定,后续的值仍然以某种形式“存活”下来,因此这个特性有点类似于c语言的静态变量。
如果上面的嵌套函数再次赋值给counter1,则会生成一个新的闭包实例,它和之前的counter之间是相互独立的关系,我们看下面的代码
function newCounter()
local j = 0;local n = 0;local k = 0; local h = 0;
print("f1:",j,k,n);
return function()
print("f2:",j,k,n);
k = n;
j = n;
n = n+1;
return j,k,n;
end
end
counter = newCounter();
print("f3:",counter());
print("----------分割线----------");
print("f4:",counter());
print("**********分割线**********");
counter1 = newCounter();
print("f5:",counter1());
print("----------分割线----------");
print("f6:",counter1());
执行结果:
f1: 0 0 0
f2: 0 0 0
f3: 0 0 1
----------分割线----------
f2: 0 0 1
f4: 1 1 2
**********分割线**********
f1: 0 0 0
f2: 0 0 0
f5: 0 0 1
----------分割线----------
f2: 0 0 1
f6: 1 1 2
说明: * * * * * * *分割线 * * * * * * *,上面是counter的执行结果,下面是counter1的,两次执行结果一样,证明counter和counter1之间是独立的。我们可以借助c++的概念来理解,将闭包函数看做是创建类(class)的实例,counter和counter1分别是相同类(class)的两个实例,之间没有关联关系,而upvalue则是对应的实例成员函数内部的静态变量。
upvalue非局部变量可以使用debug.getupvalue和debug.setupvalue来获取和设置,具体函数说明如下:
这里补充一点up的排序规律,我们看下面的代码
function newCounter()
local j = 0;local n = 0;local k = 0; local h = 0;
local l=0;
print("f1:",j,k,n);
return function()
print("f2:",j,k,n);
l = n;
h = n;
k = n;
j = n;
n = n+1;
return j,k,n;
end
end
counter = newCounter();
local i = 1;
print("-----------------------");
repeat
name, val = debug.getupvalue(counter, i);
if name then
print("index", i, name, "=", val);
if(name == "n") then
debug.setupvalue(counter, 2, 10);
end
i = i + 1;
end
until not name
print("-----------------------");
print("f3:",counter());
执行结果如下:
f1: 0 0 0
-----------------------
index 1 j = 0
index 2 k = 0
index 3 n = 0
index 4 l = 0
index 5 h = 0
-----------------------
f2: 0 10 0
f3: 0 0 1
从结果上看upvalue的顺序和内部函数return的顺序有关,return 前的排在前面(j k n),没有return的排在后面,且按照调用顺序排列(l h)。
补充一下闭包定义的两种写法:
--写法1
function f1(n)
--local i=0;
return function()
print(n);
return n;
end
end
g1=f1(200);
print(g1()); --如果写成g1(300),带入的参数会被忽略
--写法2
function f3(n)
local i=0;
function f4()
print(n);
return n;
end
return f4;
end
g2=f3(200);
print(g2());
好了,以上这些就是我个人的理解,有不对的请指正,谢谢!