Lua 中的 function、closure、upvalue

时间:2021-10-23 09:27:10

Lua 中的 function、closure、upvalue

function,local,upvalue,closure

参考:

Lua基础 语句

lua学习笔记之Lua的function、closure和upvalue

Lua中的函数是一阶类型值(first-class value),定义函数就象创建普通类型值一样(只不过函数类型值的数据主要是一条条指令而已),所以在函数体中仍然可以定义函数。假设函数f2定义在函数f1中,那么就称f2为f1的内嵌(inner)函数,f1为f2的外包(enclosing)函数,外包和内嵌都具有传递性,即f2的内嵌必然是f1的内嵌,而f1的外包也一定是f2的外包。内嵌函数可以访问外包函数已经创建的所有局部变量,这种特性便是所谓的词法定界(lexical scoping),而这些局部变量则称为该内嵌函数的外部局部变量(external local variable)或者upvalue(这个词多少会让人产生误解,因为upvalue实际指的是变量而不是值)。试看如下代码:

  1. function f1(n)   

  2. -- 函数参数也是局部变量 

  3.   local function f2() 

  4.   print(n) -- 引用外包函数的局部变量 

  5.   end 

  6.   return f2 

  7. end 

  8.   g1 = f1(1979) 

  9.   g1() -- 打印出1979 

  10.   g2 = f1(500) 

  11.   g2() -- 打印出500 

  当执行完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函数改成:

  1. function f1(n) 

  2.  local function f2() 

  3.   print(n) 

  4.  end 

  5.  n = n + 10 

  6.  return f2 

  7. end 

  8. g1 = f1(1979) 

  9. 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还可以为闭包之间提供一种数据共享的机制。试看下例:

  1. function Create(n) 

  2.  local function foo1() 

  3.   print(n) 

  4.  end 

  5.  local function foo2() 

  6.   n = n + 10 

  7.  end 

  8.  return foo1,foo2 

  9. end 

  10. f1,f2 = Create(1979) 

  11. f1() -- 打印1979 

  12. f2() 

  13. f1() -- 打印1989 

  14. f2() 

  15. 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的这种语义很有价值,它使得闭包之间可以不依赖全局变量进行通讯,从而使代码的可靠性大大提高。

 闭包在创建之时其upvalue就已经不在堆栈上的情况也有可能发生,这是因为内嵌函数可以引用更外层外包函数的局部变量:

  1. function Test(n) 

  2.  local function foo() 

  3.   local function inner1() 

  4.   print(n) 

  5.   end 

  6.   local function inner2() 

  7.    n = n + 10 

  8.   end 

  9.   return inner1,inner2 

  10.  end 

  11.  return foo 

  12. end 

  13. t = Test(1979) 

  14. f1,f2 = t() 

  15. f1() -- 打印1979 

  16. f2() 

  17. f1() -- 打印1989 

  18. g1,g2 = t() 

  19. g1() -- 打印1989 

  20. g2() 

  21. g1() -- 打印1999 

  22. f1() -- 打印1999 

  执行完t = Test(1979)后,Test的局部变量n就“死”了,所以当f1,f2这两个闭包被创建时堆栈上根本找不到n的踪影,这叫它们如何取得n的值呢?呵呵,不要忘了Test函数的n不仅仅是inner1和inner2的upvalue,同时它也是foo的upvalue。t = Test(1979)之后,t这个闭包一定已经把n妥善保存好了,之后f1、f2如果在当前堆栈上找不到n就会自动到它们的外包闭包(姑且这么叫)的upvalue引用数组中去找,并把找到的引用值拷贝到自己的upvalue引用数组中。仔细观察上述代码,可以判定g1和g2与f1和f2共享同一个upvalue。这是为什么呢?其实,g1和g2与f1和f2都是同一个闭包(t)创建的,所以它们引用的upvalue(n)实际也是同一个变量,而刚才描述的搜索机制则保证了最后它们的upvalue引用都会指向同一个地方。

关于Lua变量作用域中文件相互调用的例子

  1. -- **************** fGlobal.lua ********************* 

  2. g_VarTest = 100 --全局的 


  3. -- ***************** file1.lua ************************ 

  4. require "fGlobal" 

  5. module( ..., package.seeall ) --file1是一个模块哦 

  6. print("g_VarTest in module file1 before modify:"..g_VarTest) 

  7. g_VarTest=999 --这里试图改变全局变量的值 我们可以看看他会不会成功 

  8. print("g_VarTest in module file1 after modify:"..g_VarTest) 


  9. -- ****************** ftest.lua ************************ 

  10. local file1 = require "file1" 

  11. print("g_VarTest in ftest:"..g_VarTest) --输出全局的g_VarTest 

  12. print("g_VarTest in file1:"..file1.g_VarTest) --输出file1的g_VarTest 

  13. if true then 

  14. g_VarTest = 888 --验证在是不是在chunk里修改全局变量都是新定义 

  15. end 

  16. print("g_VarTest in ftest after if modify:"..g_VarTest) 

说明,每个文件若修改了全局变量,则对该全局变量相当于进行了一份拷贝,作为自己的环境变量。而其他文件访问该全局变量时仍访问的是原始的值。所以上面代码输出为

  1. g_VarTest in module file1 before modify:100 

  2. g_VarTest in module file1 after modify:999 

  3. g_VarTest in ftest:100 

  4. g_VarTest in file1:999 

  5. g_VarTest in ftest after if modify:888 

!!!为了避免不必要的错误,尽量别在定义全局变量的文件之外直接使用变量名修改值!!!

Lua 中的 function、closure、upvalue的更多相关文章

  1. Lua中闭包详解 来自RingOfTheC[ring.of.the.c@gmail.com]

    这些东西是平时遇到的, 觉得有一定的价值, 所以记录下来, 以后遇到类似的问题可以查阅, 同时分享出来也能方便需要的人, 转载请注明来自RingOfTheC[ring.of.the.c@gmail.c ...

  2. Lua中的userdata

    [话从这里说起] 在我发表<Lua中的类型与值>这篇文章时,就有读者给我留言了,说:你应该好好总结一下Lua中的function和userdata类型.现在是时候总结了.对于functio ...

  3. Lua中的closure(闭合函数)

    词法域:若将一个函数写在另一个函数之内,那么这个位于内部的函数便可以访问外部函数中的局部变量,这项特征称之为“词法域”. 例:假设有一个学生姓名的列表和一个对应于没个姓名的年级列表,需要根据每个学生的 ...

  4. &lbrack;译&rsqb; Closures in Lua - Lua中的闭包

    原文:(PDF) . 摘要 一等(first-class)函数是一种非常强大的语言结构,并且是函数式语言的基础特性.少数过程式语言由于其基于栈的实现,也支持一等函数.本文讨论了Lua 5.x用于实现一 ...

  5. &lbrack;转&rsqb;&lbrack;译&rsqb; Closures in Lua - Lua中的闭包

    http://www.cnblogs.com/plodsoft/p/5900270.html?utm_source=tuicool&utm_medium=referral 原文:(PDF) . ...

  6. lua中的面向对象编程

    简单说说Lua中的面向对象 Lua中的table就是一种对象,看以下一段简单的代码: 上述代码会输出tb1 ~= tb2.说明两个具有相同值得对象是两个不同的对象,同时在Lua中table是引用类型的 ...

  7. Lua中的函数

    [前言] Lua中的函数和C++中的函数的含义是一致的,Lua中的函数格式如下: function MyFunc(param) -- Do something end 在调用函数时,也需要将对应的参数 ...

  8. Lua中的闭包

    [什么是闭包?] 闭包在Lua中是一个非常重要的概念,闭包是由函数和与其相关的引用环境组合而成的实体.我们再来看一段代码: function newCounter() return function ...

  9. lua中的闭包概念的学习笔记

    1.闭包的由来: 个人理解,lua中之所以出现闭包的概念,完全是因为lua中允许函数的嵌套定义,并且在内嵌函数中使用了外包函数中定义的局部变量,例如c.c#就不允许函数的嵌套定义(但是允许函数的嵌套调 ...

随机推荐

  1. 代码管理 – SVN

    代码管理工具也用了不少年了,整理下. 管理工具比较 用过VSS,CVS,TFS,SVN还有一个微软内部使用的不记得什么名字了.这里进行一些简单比较,重点介绍SVN的用法. CVS历史悠久,1986年就 ...

  2. wait&lpar;&rpar; notify&lpar;&rpar;搭配synchronize的使用

    一直以为自己动多线程,使用过好像就懂了原理一样,其实是按部就班的写自己不知道原理的代码而已. 一些概念: 监视器:将监视器比作一个建筑,建筑里面有个特别的房间,房间中有一些数据,这些数据在同一个时间只 ...

  3. codeforces B&period; Levko and Permutation 解题报告

    题目链接:http://codeforces.com/problemset/problem/361/B 题目意思:有n个数,这些数的范围是[1,n],并且每个数都是不相同的.你需要构造一个排列,使得这 ...

  4. BufferedInputStream&sol;BufferedOutputStream复制文件

    public class Test{ public static void main(String[] args) throws IOException{ FileInputStream in = n ...

  5. JAVA面试题集之基础知识

                           JAVA面试题集之基础知识 基础知识:  1.C 或Java中的异常处理机制的简单原理和应用. 当JAVA程序违反了JAVA的语义规则时,JAVA虚拟机就 ...

  6. bzoj4766 文艺计算姬

    Description "奋战三星期,造台计算机".小W响应号召,花了三星期造了台文艺计算姬.文艺计算姬比普通计算机有更多的艺术细胞.普通计算机能计算一个带标号完全图的生成树个数, ...

  7. JSTL的相关使用

    index.jsp <%@ page language="java" import="java.util.*" pageEncoding="UT ...

  8. HDU 2062 Subset sequence (找规律)

    题目链接 Problem Description Consider the aggregate An= { 1, 2, -, n }. For example, A1={1}, A3={1,2,3}. ...

  9. 20169205实验二 Java面向对象程序设计

    20169205实验二 Java面向对象程序设计 实验内容及步骤 (一)单元测试 1.三种代码 伪代码:以简洁的自然语言表明设计步骤: 产品代码:用以实现特定功能的程序或机器语言: 测试代码:用以对产 ...

  10. CentOS7下开启端口

    开启端口: firewall-cmd --zone=public --add-port=80/tcp --permanent 含义: --zone #作用域 --add-port=80/tcp #添加 ...