lua upvalue
Lua 中的函数是一阶类型值(first-class value),定义函数就象创建普通类型值一样(只不过函数类型值的数据主要是一条条指令而已),所以在函数体中仍然可以定义函数。假设函数f2定义在函数f1中,那么就称f2为f1的内嵌(inner)函数,f1为f2的外包(enclosing)函数,外包和内嵌都具有传递性,即f2的内嵌必然是f1 的内嵌,而f1的外包也一定是f2的外包。内嵌函数可以访问外包函数已经创建的所有局部变量,这种特性便是所谓的词法定界(lexical scoping),而这些局部变量则称为该内嵌函数的外部局部变量(external local variable)或者upvalue(这个词多少会让人产生误解,因为upvalue实际指的是变量而不是值)。试看如下代码:
function f1(n) -- 函数参数也是局部变量
local function f2()
print(n) -- 引用外包函数的局部变量
end
return f2
end
g1 = f1(1979)
g1() -- 打印出1979
当执行完g1 = f1(1979)后,局部变量n的生命本该结束,但因为它已经成了内嵌函数f2(它又被赋给了变量g1)的upvalue,所以它仍然能以某种形式继续“存活”下来,从而令g1()打印出正确的值。
可为什么g2与g1的函数体一样(都是f1的内嵌函数f2的函数体),但打印值不同?这就涉及到一个相当重要的概念——闭包(closure)。事实上,Lua编译一个函数时,会为它生成一个原型(prototype),其中包含了函数体对应的虚拟机指令、函数用到的常量值(数,文本字符串等等)和一些调试信息。在运行时,每当Lua执行一个形如function...end 这样的表达式时,它就会创建一个新的数据对象,其中包含了相应函数原型的引用、环境(environment,用来查找全局变量的表)的引用以及一个由所有upvalue引用组成的数组,而这个数据对象就称为闭包。由此可见,函数是编译期概念,是静态的,而闭包是运行期概念,是动态的。g1和g2的值严格来说不是函数而是闭包,并且是两个不相同的闭包,而每个闭包可以保有自己的upvalue值,所以g1和g2打印出的结果当然就不一样了。虽然闭包和函数是本质不同的概念,但为了方便,且在不引起混淆的情况下,我们对它们不做区分。
使用upvalue很方便,但它们的语义也很微妙,需要引起注意。比如将f1函数改成:
function f1(n)
local function f2()
print(n)
end
n = n + 10
return f2
end
g1 = f1(1979)g1() -- 打印出1989
内嵌函数定义在n = n + 10这条语句之前,可为什么g1()打印出的却是1989?upvalue实际是局部变量,而局部变量是保存在函数堆栈框架上(stack frame)的,所以只要upvalue还没有离开自己的作用域,它就一直生存在函数堆栈上。这种情况下,闭包将通过指向堆栈上的upvalue的引用来访问它们,一旦upvalue即将离开自己的作用域(这也意味着它马上要从堆栈中消失),闭包就会为它分配空间并保存当前的值,以后便可通过指向新分配空间的引用来访问该upvalue。当执行到f1(1979)的n = n + 10时,闭包已经创建了,但是n并没有离开作用域,所以闭包仍然引用堆栈上的n,当return f2完成时,n即将结束生命,此时闭包便将n(已经是1989了)复制到自己管理的空间中以便将来访问。弄清楚了内部的秘密后,运行结果就不难解释了。
upvalue还可以为闭包之间提供一种数据共享的机制。试看下例:
function Create(n)
local function foo1()
print(n)
end
local function foo2()
n = n + 10
end
return foo1,foo2
end
f1,f2 = Create(1979)
f1() -- 打印1979
f2() f1() -- 打印1989
f2() f1() -- 打印1999
f1, f2这两个闭包的原型分别是Create中的内嵌函数foo1和foo2,而foo1和foo2引用的upvalue是同一个,即Create的局部变量 n。前面已说过,执行完Create调用后,闭包会把堆栈上n的值复制出来,那么是否f1和f2就分别拥有一个n的拷贝呢?其实不然,当Lua发现两个闭包的upvalue指向的是当前堆栈上的相同变量时,会聪明地只生成一个拷贝,然后让这两个闭包共享该拷贝,这样任一个闭包对该upvalue进行修改都会被另一个探知。上述例子很清楚地说明了这点:每次调用f2都将upvalue的值增加了10,随后f1将更新后的值打印出来。upvalue的这种语义很有价值,它使得闭包之间可以不依赖全局变量进行通讯,从而使代码的可靠性大大提高。
lua upvalue的更多相关文章
- LUA upvalue使用陷阱一例
CA = {} CA.TestCb = function(self, cb) if not self._cb then self._cb = function() cb() end end self. ...
- Lua中如何实现类似gdb的断点调试--04优化钩子事件处理
在第一篇的01最小实现中,我们实现了一个断点调试的最小实现,在设置钩子函数时只加了line事件,显然这会对性能有很大的影响.而后来两篇02通用变量打印和03通用变量修改及调用栈回溯则是提供了一些辅助的 ...
- Lua的function、closure和upvalue
Lua中的函数是一阶类型值(first-class value),定义函数就象创建普通类型值一样(只不过函数类型值的数据主要是一条条指令而已),所以在函数体中仍然可以定义函数.假设函数f2定义在函数f ...
- Lua 中的 function、closure、upvalue
Lua 中的 function.closure.upvalue function,local,upvalue,closure 参考: Lua基础 语句 lua学习笔记之Lua的function.clo ...
- lua执行字节码的过程介绍
前面一篇文章中介绍了lua给下面代码生成最终的字节码的整个过程,这次我们来看看lua vm执行这些字节码的过程. foo = "bar" local a, b = "a& ...
- lua解析赋值类型代码的过程
我们来看看lua vm在解析下面源码并生成bytecode时的整个过程: foo = "bar" local a, b = "a", "b" ...
- lua解析脚本过程中的关键数据结构介绍
在这一篇文章中我先来介绍一下lua解析一个脚本文件时要用到的一些关键的数据结构,为将来的一系列代码分析打下一个良好的基础.在整个过程中,比较重要的几个源码文件分别是:llex.h,lparse.h.l ...
- Lua和C++交互详细总结
转自:http://cn.cocos2d-x.org/tutorial/show?id=1474 一.Lua堆栈 要理解Lua和C++交互,首先要理解Lua堆栈. 简单来说,Lua和C/C++语言通信 ...
- Lua小技巧
来公司以后,业务逻辑都用lua写.写了好长时间了,到最近才觉得有点掌握了Lua的灵活.最近用Lua写了个类似集合一样的东西,如果两次向集合里放入同一个元素,就会报错,方便检查配置.代码如下: -- k ...
随机推荐
- Sharepoint 2010 工作流启动时处理表单出错
问题: Shareoint 2010 列表工作流启动时,显示“处理表单时出现严重错误”. Error Message-1: Object doesn't support property or met ...
- 转如何分析解决Android ANR
一:什么是ANR ANR:Application Not Responding,即应用无响应 二:ANR的类型 ANR定义:在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示 ...
- tomcat <context path>的意义及作用
context path 是在tomcat 要支持多个应用时对每个应用的docBase做区别时的区分符. 打个比方假如你有两个请求:一个为 http:localhost:8080/test1/hell ...
- Swift-Switch穿透
switch vegetable { case "celery": print("Add some raisins and make ...
- 关于JNI的使用方法
1首先在java里面定义你需要的native方法 2打开cmd,进入doc窗口,如果是android项目就进入到你当前项目的bin目录下,在doc里面输入cd E:\workspace\Test1 也 ...
- iOS开发之多线程技术
本篇争取一篇讲清讲透,依然将通过四大方面清晰的对iOS开发中多线程的用法进行详尽的讲解: 一.什么是多线程 1)多线程执行原理 2)线程与进程 3)多线程的优缺点 二.我们为什么要用多线程编程技术 三 ...
- GIT在iOS开发中的使用
前言 在iOS开发中,很多公司对项目的版本控制管理都使用了git,当然也有部分公司使用的是svn.当年我最初接触的是svn,觉得使用起来挺方便的,但是每次切分支都需要下载一份新的代码起来,这实在太麻烦 ...
- nodejs get/request
灌水评论示例: var http = require('http'); var querystring = require('querystring'); var postData = queryst ...
- Android Build Error(1)
Type 1 —— Build Path Problem : **.jar包文件缺失 1.在Android项目根目录下新建一个libs文件夹: 2.把你需要的导入的第三方Jar包复制进这个目录: 3. ...
- WPExpress78_update 离线包
http://115.com/lb/5lb7rnbr#Windows Phone SDK update for WP 7.8115网盘礼包码:5lb7rnbr 使用/layout下载的,相信很多朋友已 ...