lua的全局变量、局部变量、函数闭包和非局部变量(upvalue)

我们知道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来获取和设置,具体函数说明如下:

  • getupvalue (f, up):此函数返回函数 f 的第 up 个上值的名字和值。 如果该函数没有那个上值,返回 nil 。 以 ‘(’ (开括号)打头的变量名表示没有名字的变量 (去除了调试信息的代码块)
  • setupvalue (f, up, value):这个函数将 value 设为函数 f 的第 up 个上值。 如果函数没有那个上值,返回 nil 否则,返回该上值的名字

这里补充一点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());

好了,以上这些就是我个人的理解,有不对的请指正,谢谢!

你可能感兴趣的:(lua,脚本语言)