借鉴自:https://www.jb51.net/article/55117.htm
https://blog.csdn.net/mydad353193052/article/details/48731467
https://blog.csdn.net/qq826364410/article/details/88702685
一、词法域、闭包、第一类型
在C/C++,C#或者Java等传统语言中,一个函数A,如果想调用另一个函数B,并且B需要访问A中的变量,那么A就需要向B传递参数,参数形式可以是普通类型,指针,或者引用。(C#中有out输出参数和ref引用,专门的关键字来做这件事)但是在Lua中不必如此。 Lua中有一个“词法域”的概念。即B可以访问他所需要访问的所有“非局部变量”。
function count()
local i = 0
return function() --匿名函数
i = i + 1
print(i)
end
end
c1 = count()
c1() -- 输出 1
c1() -- 输出 2
c2 = count()
c2() -- 输出 1
一个函数定义在另一个函数中,位于内部的函数可以访问外部函数中的所有局部变量,这种特征就是词法作用域,这些局部变量(例如i)则称为该函数的upvalue(upvalue就是lua函数中引用到的外部变量),对于一个count函数中的局部变量i,当执行"c1 = count()" 后,它的生命周期本该结束,但是由于它已经成为了定义在count函数中的匿名函数的upvalue,返回的匿名函数以upvalue的方式把变量i的值保存下来,因此可以不断累加输出。这种局部变量在函数返回后继续存在,并且返回的函数可以正常的调用这个局部变量,独立执行其逻辑的现象,在lua中称之为闭包(closure)。
之所以说闭包是一个独立存在的个体,如c1跟c2都是相同的函数体,不过输出值却不一样,这主要还是因为闭包是由函数原型的引用和该函数所需访问的外部局部变量 upvalue 组成。当调用函数造成 upvalue 值被改变时,这只会改变对应闭包的 upvalue 值,不会影响到其他闭包里的 upvalue 值,所以 c1 被调用 2 次后,外部局部变量 i 的值的是 2,而新创建的 c2 初始的外部局部变量 i 是 0,被调用之后会是 1。
在Lua中,函数是“第一类型”。就是说函数和int,string,float等泛泛之辈一样,都是类型。而函数名则可以理解为,拿着函数实体的变量。请看下面这个例子:
a = {p = print}
a.p("hello bitch") ---hello bitch
print = math.sin ---'print'现在是正弦函数
a.p(print(1)) ---0.8414709848079
sin = a.p ---'sin'现在是print函数
sin(10 .. 20) ---1020
正所谓似鸡非鸡,似鸭非鸭。鸡有可能变鸭,鸭也可以手术变鸡。
二、函数就是变量:
function foo(x) return 2 * x end
foo = function (x) return 2 * x end
这俩句话的含义是一样的,所以一个函数的定义实际就是一条赋值语句,这条语句首先创建一个函数类型的值,然后将这个值赋予这个函数变量,也可以将表达式 function(x)<body>end 理解为一种函数的构造式,就像table的构造式{}一样,将这种函数构造式的结果称为一个“匿名函数”。
有时候在定义函数的时候,要注意一下顺序,比如这俩个函数
local function mutou()
print("mutou");
return pangbai();
end
local function pangbai()
print("pangbai");
end
mutou函数里要调用pangbai函数,在编译的时候,mutou函数编译不过的,因为这个时候pangbai函数未定义,只要换一个方式就可以解决这个问题了了。
local mutou;
local pangbai;
mutou = function ()
print("mutou");
return pangbai();
end
pangbai = function ()
print("pangbai");
end
三、尾调用:一个函数的调用是另一个函数的最后一个动作时,这个调用就称之为尾调用
function mutou()
return count();
end
当mutou函数调用完count函数之后,就没有其他事情要做了,所以,调用count函数,就属于尾调用。但如果是这样的函数:
function mutou()
return count() + 1;
end
这就不属于尾调用,因为调用完count函数之后,还要取得count的返回值,然后进行一次加法操作,这就不符合定义了
尾调用的意义:进行尾调用时不会消耗多余的栈空间
function foo(n)
if n > 0 then
return foo(n - 1);
else
return "end";
end
end
print(foo(999999)) --end
function foo(n)
if n > 0 then
return foo(n - 1) + 0;
else
return "end";
end
end
print(foo(999999)) --发送报错
第一个函数为尾调用的函数,会正常打印end,第二个不是尾调用函数,会产生错误[string "src/main.lua"]:57: stack overflow
四、闭合函数:一个函数加上函数所需访问的所有"非局部的变量"
function count()
local i = 0
return function ()
i = i + 1
return i
end
end
--count()返回的是一个函数,所以func为一个函数
local func = count()
print(func()) -- 1
print(func()) -- 2
print(func()) -- 3
在这里,i就属于一个非局部变量,因为它既不是全局变量,也不是单纯的局部变量(因为另外一个函数可以访问到它),再回到定义,count函数里的那个匿名函数,加上非局部变量i,就构成了一个闭合函数了,对于闭合函数而言,属于它的非局部变量i,并不是再调用它的时候临时产生的,而是和它一起存在的,所以每次调用闭合函数,非局部变量的值都不会被会重置。
如果大家还是不太清楚,那么,我们给这个闭合函数添加一个局部变量吧,修改count函数如下:
function count()
return function()
local i = 999;
i = i + 1;
return i;
end
end
local func = count();
print(func()); --1000
print(func()); --1000
print(func()); --1000
这次,把i作为这个内部函数的变量了,它不再是"非局部变量"了,所以在每次调用该函数时都重新定义i,所以打印值为1000
五、lua中实现C++的static变量
1.利用全局变量实现
local function staic_test()
n = n or 0;
n = n + 1;
return n;
end
print(staic_test())
print(staic_test())
2.利用闭合函数实现
local function staic_test2()
local i = 0;
return function()
i = i + 1;
return i;
end
end
staicor = staic_test2();
print(staicor())
print(staicor())