Lua中的闭合函数、非全局函数与函数的尾调用详解_Lua

上一篇我们简单地介绍了Lua的函数,这次,我们来点特别的,来介绍一下Lua的函数(小若:等等,我是不是错过了什么?)

1.闭合函数(closure)

理论上来说,Lua的所有函数都应该称之为闭合函数,但是,这种反人类的做法,我们还是抛弃吧~

按书上的描述,一个闭合函数就是:一个函数加上该函数所需访问的所有”非局部的变量“。

理论什么的,很烦人,来看看一个函数:

复制代码 代码如下:

function count()
    local i = 0;
    return function()
              i = i + 1;
              return i;
           end
end

这个count函数会返回另外一个函数,重点是,这个返回的函数会使用count函数的局部变量。

先来运行,看看效果,使用如下方式调用:

复制代码 代码如下:

    local func = count();
    print(func());
    print(func());
    print(func());

输出结果如下:

复制代码 代码如下:

[LUA-print] 1
[LUA-print] 2
[LUA-print] 3

怎么旁白不出来咆哮一下?(小若:…咳咳…为毛线会这样?!不应该全部都输出1吗?!)
 
在这里,local i就属于一个非局部变量,因为它既不是全局变量,也不是单纯的局部变量(因为另外一个函数可以反问到它)。

再来回到定义,count函数里的那个函数,加上非局部变量i,就构成了一个闭合函数了,就这么简单。
 
对于闭合函数而已,属于它的非局部变量,并不是在调用它的时候临时产生的,而是和它一起存在的。

所以每次调用闭合函数,非局部变量的值都不会被重置。
 
如果大家还是不太清楚,那么,我们给这个闭合函数添加一个局部变量吧,修改count函数如下:

复制代码 代码如下:

function count()
    return function()
              local i = 999;
              i = i + 1;
              return i;
           end
end

这次,把i作为这个内部函数的局部变量了,它不再是“非局部变量”。
仍然像这样调用:

复制代码 代码如下:

    local func = count();
    print(func());
    print(func());
    print(func());

输出结果如下:

复制代码 代码如下:

[LUA-print] 1000
[LUA-print] 1000
[LUA-print] 1000

陷入, 每一次i变量的值都是全新的。
闭合函数的用处可大着了,我们在开发过程中使用的频率应该还算比较大的~

2.非全局函数

又是这种看着就反人类的名词,非全局,那就是说,不是全局的函数(小若:废你个话啊!)

来看看这样的一个函数:

复制代码 代码如下:

local function mutou()
  
end

这就是一个非全局函数,但,这么简单的东西我可不会拿出来说~
 
这里我想介绍一个“语法糖”,上面的mutou函数,其实相当于以下的代码:

复制代码 代码如下:

local mutou;
mutou = function ()
  
end

这就是我们函数的真实面貌,函数名称其实也是一个变量名而已。
所以,有时候,我们在定义函数的时候,要注意一下顺序。比如,这样的两个函数:

复制代码 代码如下:

local function mutou()
    print("mutou");
    return pangbai();
end
local function pangbai()
    print("pangbai");
end

mutou函数里要调用pangbai函数。
在编译的时候,mutou函数是编译不过的,因为这个时候pangbai函数未定义,换句话说,pangbai变量并不存在。
只要换一个方式就可以解决这个问题了:

复制代码 代码如下:

local mutou;
local pangbai;
mutou = function ()
    print("mutou");
    return pangbai();
end
pangbai = function ()
    print("pangbai");
end

这样编译就没问题了,好吧,也许Lua里不该叫编译,反正,不会报语法错误了~

3.尾调用

好了,一个名词比一个名词要反人类。

尾调用的大致意思是:一个函数的调用是另一个函数的最后一个动作时,这个调用就称之为尾调用。

比如这样的函数:

复制代码 代码如下:

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(99999));

 
结果就是不出所料地输出了end字符串。
当n > 0时,函数就直接返回foo(n – 1),接着就没有后续的动作了,所以这符合尾调用的定义。
因此,这个函数的调用不会引起栈溢出。
 
但,如果稍微改改,变成这样:

复制代码 代码如下:

function foo(n)
    if n > 0 then
        return foo(n - 1) + 0;
    else
        return "end";
    end
end

运行的时候,就会报这样的一个错误: [string "src/main.lua"]:57: stack overflow
 
由于我用Lua的情况不多,所以暂时没有举个比较实用的例子,但,这自然会是一个很好用的特性。
 
好了,关于Lua的函数,应该都介绍完了,我也是按着书的顺序在过一遍基础,然后把觉得有意思的部分用文章记录下来。

时间: 2024-10-26 09:39:38

Lua中的闭合函数、非全局函数与函数的尾调用详解_Lua的相关文章

Lua教程(四):函数详解_Lua

一.函数:     在Lua中函数的调用方式和C语言基本相同,如:print("Hello World")和a = add(x, y).唯一的差别是,如果函数只有一个参数,并且该参数的类型为字符串常量或table的构造器,那么圆括号可以省略,如print "Hello World"和f {x = 20, y = 20}.     Lua为面对对象式的调用也提供了一种特殊的语法--冒号操作符.表达式o.foo(o,x)的另一种写法是o:foo(x).冒号操作符使调用o

JS中artdialog弹出框控件之提交表单思路详解_javascript技巧

artDialog是一个基于javascript编写的对话框组件,它拥有精致的界面与友好的接口. 前言: 自适应内容 artDialog的特殊UI框架能够适应内容变化,甚至连外部程序动态插入的内容它仍然能自适应,因此你不必去考虑消息内容尺寸使用它.它的消息容器甚至能够根据宽度让文本居中或居左对齐--这一切全是XHTML+CSS原生实现. 完善的接口 它的接口完善,可以轻易与外部程序配合使用.如异步写入消息.控制位置.尺寸.显示与隐藏.关闭等. 细致的体验 如果不是在输入状态,它支持Esc快捷键关

Lua中的模块与module函数详解_Lua

很快就要开始介绍Lua里的"面向对象"了,在此之前,我们先来了解一下Lua的模块. 1.编写一个简单的模块 Lua的模块是什么东西呢?通常我们可以理解为是一个table,这个table里有一些变量.一些函数- 等等,这不就是我们所熟悉的类吗? 没错,和类很像(实际上我说不出它们的区别).   我们来看看一个简单的模块,新建一个文件,命名为game.lua,代码如下: 复制代码 代码如下: game = {} function game.play()     print("那么

Lua中的全局变量、非全局变量总结_Lua

前言 Lua将其所有的全局变量保存在一个常规的table中,这个table称为"环境".这种组织结构的优点在于,其一,不需要再为全局变量创造一种新的数据结构,因此简化了Lua的内部实现:另一个优点是,可以像其他table一样操作这个table.为了便于实施这种操作,Lua将环境table自身保存在一个全局变量_G中.例如,我们可以使用以下代码打印当前环境中所有全局变量的名称. 复制代码 代码如下: for n in pairs(_G) do print(n) end 在你的电脑上运行一

js函数在frame中的相互调用详解

 一个HTML页面可以有一个或多个子框架,这些子框架以<iframe>来标记,用来显示一个独立的HTML页面.这里所讲的框架编程包括框架的自我控制以及框架之间的互相访问,例如从一个框架中引用另一个框架中的JavaScript变量.调用其他框架内的函数.控制另一个框架中表单的行为等 框架间的互相引用   一个页面中的所有框架以集合的形式作为window对象的属性提供,例如:window.frames就表示该页面内所有框架的集合,这和表单对象.链接对象.图片对象等是类似的,不同的是,这些集合是do

js函数在frame中的相互调用详解_基础知识

框架间的互相引用 一个页面中的所有框架以集合的形式作为window对象的属性提供,例如:window.frames就表示该页面内所有框架的集合,这和表单对象.链接对象.图片对象等是类似的,不同的是,这些集合是document的属性.因此,要引用一个子框架,可以使用如下语法: 复制代码 代码如下: window.frames["frameName"];window.frames.frameNamewindow.frames[index] 其中,window字样也可以用self代替或省略,

谈函数指针(全局/类成员函数)和函数对象

函数指针(全局函数/类成员函数).函数对象(Function object) 一. 函数指针类型为全局函数. #include "stdafx.h"#include <iostream>using namespace std;class TestAction;typedef void (*fp)(int); void Drink(int i){ cout<<"No. "<<i<<" drink..."

Lua中的模块(module)和包(package)详解_Lua

前言 从Lua5.1版本开始,就对模块和包添加了新的支持,可是使用require和module来定义和使用模块和包.require用于使用模块,module用于创建模块.简单的说,一个模块就是一个程序库,可以通过require来加载.然后便得到了一个全局变量,表示一个table.这个table就像是一个命名空间,其内容就是模块中导出的所有东西,比如函数和常量,一个符合规范的模块还应使require返回这个table.现在就来具体的总结一下require和module这两个函数. require函

Lua中的面向对象编程详解_Lua

简单说说Lua中的面向对象 Lua中的table就是一种对象,看以下一段简单的代码: 复制代码 代码如下: local tb1 = {a = 1, b = 2} local tb2 = {a = 1, b = 2} local tb3 = tb1   if tb1 == tb2 then      print("tb1 == tb2") else      print("tb1 ~= tb2") end   tb3.a = 3 print(tb1.a) 上述代码会输