在Lua中,你可以像使用number和string一样使用function。可以将function存储到变量中,存储到table中,可以当作函数参数传递,可以作为函数的返回值。
在Lua中,function跟其他值一样,也是匿名的。function被作为一个值存储在变量中,下面这个例子有点2,可以帮助理解:
a = {p = print}
a.p("Hello World") --> Hello World
print = math.sin -- 'print' now refers to the sin function
a.p(print(1)) --> 0.841470
sin = a.p -- 'sin' now refers to the print function
sin(10, 20) --> 10 20
创建函数的表达式
function foo (x) return 2*x end
--实际上为:
foo = function (x) return 2*x end
从上面可以看出,function的定义实际上是创建一个'function'类型的值,并赋值给一个变量。我们可以将function (x) body end看成是function的构造函数,就像{}是table的构造函数一样。
table库有一个函数table.sort,接受一个table,然后对table中的元素排序。排序可能有各种规则,升序,降序,数字或字母表顺序,根据table中的key排序等。该函数没有提供各种各样所有的排序选项,而是提供了一个单独的选项,即函数的第二个参数,order函数,order函数,接受两个元素,返回一个布尔值,来指示是否第一个元素排在第二个元素之前。看下面的例子:
network = {
{name = "grauna", IP = "210.26.30.34"},
{name = "arraial", IP = "210.26.30.23"},
{name = "lua", IP = "210.26.23.12"},
{name = "derain", IP = "210.26.23.20"},
}
table.sort(network, function (a,b) return (a.name > b.name) end)
从上面看,匿名函数使用起来很方便吧。
这里先讲一个概念,higher-order function。它可以把其他函数当作参数。下面给个示例,求导函数(哎,好多年前的东西,一点都不记得了,还跟同事讨论了半天)
function derivative (f, delta)
delta = delta or 1e-4
return function (x)
return (f(x + delta) - f(x))/delta
end
end
c = derivative(math.sin)
print(math.cos(10), c(10))
--> -0.83907152907645 -0.83904432662041
文章开头也说了,Lua中的function可以像普通的值一样使用,可以存储到全局变量,局部变量,table中。往下看,function存储到table中,这是个牛B的特性,可以实现很多高级的功能,例如模块,面向对象等。
1. 闭合函数
先看一个示例,有两个table,一个table中是student names,另一个table里面有每个student的grade;现在要根据student的grade来对前一个table进行排序。根据之前所学,可以使用如下code:
names = {"Peter", "Paul", "Mary"}
grades = {Mary = 10, Paul = 7, Peter = 8}
table.sort(names, function (n1, n2)
return grades[n1] > grades[n2] -- compare the grades
end)
现在写一个function来实现这一个功能:
function sortbygrade (names, grades)
table.sort(names, function (n1, n2)
return grades[n1] > grades[n2] -- compare the grades
end)
end
上面这段code的有趣之处在于,sort中的匿名函数可以访问参数中的grades,grades是sortbygrade的局部变量。在该匿名函数中,grades既不是局部变量,也不是全局变量,而是非局部变量(智商有点捉鸡了)。
现在来看看这个非局部变量的妙用。看下面示例:
function newCounter ()
local i = 0
return function () -- anonymous function
i = i + 1
return i
end
end
c1 = newCounter()
print(c1()) --> 1
print(c1()) --> 2
c2 = newCounter()
print(c2()) --> 1
print(c1()) --> 3
print(c2()) --> 2
在上面的代码中,匿名函数引用了非局部变量i,来记数。但是,调用这个匿名函数的时候,i已经不再在有效作用域了,因为创建i的函数(newCounter)已经返回了。但是Lua可以使用闭合函数正确处理这种情况。简单地说,闭合函数就是一个函数加上要访问一个非局部变量所需要的所有元素。如果再调用一个newCounter,它会重新创建一个新的非局部变量i,
得到一个新的闭合函数。如上面示例,c1和c2是两个基于用一个函数的闭合函数,二者有两个独立的非局部变量i的实例。
闭合函数在很多场合都很好用。例如,在higer-order函数(如sort)中,可以作为参数;在newCounter这样的在自己函数体中创建其他的函数;作为回调函数。一个典型的例子就是,在传统的GUI工具箱中,创建按钮,每个按钮需要一个回调函数来响应按键动作。例如一个计算器,需要10个类似的按钮,每个数字一个。可以用下面的function来创建:
function digitButton (digit)
return Button{ label = tostring(digit),
action = function ()
add_to_display(digit)
end
}
end
在上面的示例中,我们假设digitButton是一个工具箱函数,用来创建一个按钮,lable是按钮的label, action是回调函数,响应按键操作。这个回调函数可以在digitButton完成工作以后很久,局部变量digit超出作用域后再调用,但是它仍然能够访问变量digit。
由于函数是存储在普通的变量中,Lua可以方便的重新定义一些函数,甚至Lua预定义的函数。下面看一个示例,重新定义sin函数,将参数由原来的弧度,改成度数。新的函数一定要转换一下参数的值,然后调用原来的sin函数来实现功能。
oldSin = math.sin
math.sin = function (x)
return oldSin(x*math.pi/180)
end
--建议用下面这种
do
local oldSin = math.sin
local k = math.pi/180
math.sin = function (x)
return oldSin(x*k)
end
end
推荐使用第二种实现,我们将原来的函数保存在一个局部变量中中,访问它的唯一途径就是通过新版本的函数。通过这种技巧,可以构建沙箱安全环境。这种安全环境在你需要运行一些不被信任的代码(例如从internet上收到的代码)时是很有必要的。例如,为了严格限制程序可以访问的文件,可以重新定义函数io.open:
do
local oldOpen = io.open
local access_OK = function (filename, mode)
--here to add some code to restrict access
<check access>
end
io.open = function (filename, mode)
if access_OK(filename, mode) then
return oldOpen(filename, mode)
else
return nil, "access denied"
end
end
end
如下代码,重定义open函数后,程序没办法访问无限制版本的open函数,只能使用这个限制版本。通过这种方法,Lua可以简便灵活地构建安全的沙箱环境。
2.非全局函数
Lua中的函数可以存储到全局变量中,也可以存储到table和局部变量中。
大多数的Lua库都是将函数存入table中。下面示例集中定义table中函数的方法:
Lib = {}
Lib.foo = function (x,y) return x + y end
Lib.goo = function (x,y) return x - y end
--使用构造函数
Lib = {
foo = function (x,y) return x + y end,
goo = function (x,y) return x - y end
}
--另一种语法
Lib = {}
function Lib.foo (x,y) return x + y end
function Lib.goo (x,y) return x - y end
当我们将函数存储到一个局部变量中,我们就得到一个局部函数,只在给定的作用域中有效。这个特性在包中经常用到,可以在一个包中定义局部函数,这些函数只在该包中可见,包中的其他函数可以调用这些局部函数:
local f = function (<params>)
<body>
end
local g = function (<params>)
<some code>
f() -- 'f' is visible here
<some code>
end
在递归函数中有一点微妙,看下面两份代码。
local fact = function (n)
if n == 0 then return 1
else return n*fact(n-1) -- buggy
end
end
--下面这个可以工作
local fact
fact = function (n)
if n == 0 then return 1
else return n*fact(n-1)
end
end
再看下面这个局部函数定义展开后的形式:
local function foo (<params>) <body> end
--expands to
local foo
foo = function (<params>) <body> end
因此,上面的递归函数也可以写成,注意3中实现方式的不同。
local function fact (n)
if n == 0 then return 1
else return n*fact(n-1)
end
end
但是,上面这个技巧在非直接递归函数中就不好使了啊。看下面的示例:
local f, g -- 'forward' declarations
function g ()
<some code>
f()
<some code>
end
--local f 如果这句放在这,那么上面的g()是引用不到正确的f函数的
function f ()
<some code>
g()
<some code>
end
--调用一下,要把上面的代码补全哦
g()
不信,试试把local f这句放到fucntion g ()的定义后面看有什么后果。
3.强大的尾调用
程序员都知道,函数的调用会产生调用堆栈。但是在Lua中,当然也有调用堆栈啦。但是尾调用在Lua中就不同于其他编程语言啦。我们先通过几行代码来看下什么是尾调用tailor call
function f (x) return g(x) end
function foo (n)
if n > 0 then
return foo(n - 1)
end
end
--下面这几个都不是
function f (x)
g(x) --after calling g, f still has to discard occasional results from g before returning
end
function f (x)
return g(x) + 1 -- must do the addition
end
function f (x)
return x or g(x) -- must adjust to 1 result
end
function f (x)
return (g(x)) -- must adjust to 1 result
end
在Lua中,只用格式为return func(args)的调用才是尾调用。即使func和args都是复杂的表达式也没关系,因为Lua在调用之前算得到它们的值。所以下面这个也是尾调用
return x[i].foo(x[j] + a*b, i + j)
我们再来针对下面这行代码讲讲Lua中对尾调用处理的强大。
function f (x) return g(x) end
Lua是怎么处理尾调用的呢。像C语言的话,上面代码中,f对g调用后,g执行完毕后,会返回到g的被调用处。但是在Lua中,这是一个尾调用,g执行完毕之后,会直接返回到f的被调用处。这样的话,可以节省很多堆栈空间。因此像下面这个函数,就不需担心n太大的话会有溢出。
在Lua中,对尾调用的一个很好的应用是状态机。可以用一个函数表示一个状态,改变状态就是跳转到一个指定的函数。下面我们用一个简单的迷宫程序示例:迷宫有几个房间(我们这里是4个),每个房间有4扇门,通向东,南,西,北。每一步,玩家指定一个移动的方向,如果这个方向有门,那么就进入对于的房间;否则,程序给一个警告;目标是从开始的房间走到目标房间。
这个程序是一个典型的状态机,状态就是当前的房间,每个房间写一个函数。用尾调用来从一个房间移动到另一个房间。如果不用尾调用的话,每一次移动都要将堆栈升级一个level,一定数量的移动后,可能就会导致程序溢出了。使用尾调用的话,就不需要担心这个问题了。废话少说,下面是代码,已经验证过了,比较简单。
function room1 ()
local move = io.read()
if move == "south" then return room3()
elseif move == "east" then return room2()
else
print("invalid move")
return room1() -- stay in the same room
end
end
function room2 ()
local move = io.read()
if move == "south" then return room4()
elseif move == "west" then return room1()
else
print("invalid move")
return room2()
end
end
function room3 ()
local move = io.read()
if move == "north" then return room1()
elseif move == "east" then return room4()
else
print("invalid move")
return room3()
end
end
function room4 ()
print("congratulations!")
end
--写完上面的四个room,调用一下就可以了。
room1()
水平有限,如果有朋友发现错误,欢迎留言交流。
分享到:
相关推荐
1. **Lua与C++的交互接口**:Lua提供了一个名为`luaL_newstate`的API来创建一个新的Lua状态机,这是与C++交互的基础。然后,通过`luaL_openlibs`打开标准库,使Lua具备基本功能。 2. **注册C++函数到Lua**:使用`...
在C++程序中使用Lua,首先要创建一个Lua状态机(`lua_State*`),这是所有Lua操作的基础。通过`luaL_newstate()`函数可以创建一个新的Lua环境,并通过`luaL_openlibs()`打开标准库,这样就可以在C++中使用Lua的基本...
1. **基础语法**:包括变量定义(全局与局部)、数据类型(如nil、boolean、number、string、table、function、userdata和thread)、操作符以及控制结构(如if-then-else、for循环、while循环和函数定义)。...
1. **Lua C API基础知识**: Lua C API 是一组C语言的函数,允许C/C++代码与Lua脚本进行交互。它提供了一系列的函数,如`luaL_newstate`用于创建一个新的Lua环境,`luaL_openlibs`打开默认的库,以及`luaL_...
在这个基础篇中,我们将深入探讨Lua中的函数机制,包括全局与局部变量、函数定义、局部函数、函数的赋值与匿名函数以及函数的多返回值。 首先,让我们来看一下**函数的定义**。在Lua中,函数的定义语法简洁明了,如...
这个"lua基础学习文档"涵盖了对初学者至关重要的几个主题:lua函数库、模式匹配、文件处理以及表和元方法。让我们逐一深入探讨这些核心概念。 1. Lua函数库: Lua标准库提供了一系列内置函数,方便用户进行各种常见...
本文将详细讲解如何在C++中调用Lua函数,并提供一个简单的源代码案例作为参考。 首先,我们需要了解C++与Lua交互的基础。Lua是一个轻量级的脚本语言,它的设计目标是易于嵌入到其他应用中,而C++则是一种通用的、...
### Lua基础教程(九)函数知识点详解 #### 一、函数概述 - **定义**:在Lua语言中,函数是一组语句的集合,用于执行一个特定的任务。 - **作用**:通过将代码分解成不同的函数,可以提高代码的复用性和可维护性。...
以下是一些关于"Unity中需要的Lua基础"的关键知识点,以及如何结合`ulua`插件和`tolua#`工程进行实践。 1. **Lua基础语法**:Lua是一种简洁、易读的脚本语言,它的基本语法包括变量(全局和局部)、数据类型(如...
1. 初始化Lua环境:创建一个LuaState实例,这是执行Lua脚本的基础。 ```csharp using LuaInterface; ... Lua lua = new Lua(); ``` 2. 注册C#函数到Lua:为了让Lua脚本能够调用C#的方法,你需要先注册这些方法。例如...
要从C/C++中调用Lua函数,我们需要创建一个Lua状态机(lua_State*),这是所有交互的基础。例如: ```c #include <lua.h> #include #include int main() { lua_State *L = luaL_newstate(); luaL_openlibs(L);...
这个环境是所有 Lua 操作的基础,它是一个独立的执行上下文。 2. **打开标准库**:调用 `luaL_openlibs(state)` 将 Lua 标准库加载到新创建的环境中。这使得你可以使用如 print、math 等内置函数。 3. **加载脚本*...
- 帮助文档:可能包含LUA语言的基础教程和常见问题解答。 对于初学者,这样的练习器可以帮助他们快速上手,理解LUA的基本语法和特性。对于有一定经验的开发者,它也能作为一个便捷的测试平台,验证和调试代码片段。...
首先,"Lua中文教程"是学习Lua编程的基础,它涵盖了从基础语法到高级特性的全方位介绍。在PDF文档中,你可以期待找到以下内容: 1. **基础语法**:包括变量定义(全局与局部)、数据类型(如数字、字符串、表、布尔...
在“lua基础笔记”中,我们可以探索以下关键知识点: 1. ** Lua语法基础 **:Lua的语法简洁明了,易于学习。变量声明不需要指定类型,其数据类型包括数字(浮点数和整数)、字符串、布尔值、表(类似数组或哈希表)...
1. Lua语言基础:了解变量、表、函数、控制结构等基本概念。 2. Lua与C/C++的交互:理解如何在C/C++中注册和调用Lua函数,以及如何在Lua中调用C/C++函数。 3. Lua脚本调试:学会使用如luadoc之类的工具来生成和查看...
本教程旨在提供一个全面且深入的中文学习资源,帮助读者从零基础到熟练掌握Lua。 首先,我们从基础开始。Lua的核心概念包括变量、数据类型和控制结构。在Lua中,变量是动态类型的,无需预先声明。它支持五种基本...
### Lua基础学习笔记 #### 一、Lua简介与特点 Lua是一种轻量级且高效的脚本语言,广泛应用于游戏开发、网页应用、图形界面等领域。对于已有其他编程语言基础的学习者来说,掌握Lua通常较为迅速。Lua的设计哲学是...
综上所述,lua中的eclipse插件lua development tools(LDT)在原有功能的基础上,通过实现工程函数跳转及提示,极大地提升了Lua开发者的编码效率和代码质量。这些高级特性使得LDT成为Lua开发者不可或缺的工具之一,...
### 脚本策划LUA基础入门基础培训 #### 一、引言 脚本策划在游戏开发领域扮演着至关重要的角色,特别是在游戏逻辑和交互设计方面。本篇旨在为初学者提供关于脚本策划及LUA语言的基础知识,帮助他们更好地理解脚本...