`
chinamming
  • 浏览: 151286 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

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实际指的是变量而不是值)。试看如下代码:

  function f1(n)   -- 函数参数也是局部变量   local function f2()   print(n) -- 引用外包函数的局部变量   end   return f2   end   g1 = f1(1979)   g1() -- 打印出1979   g2 = f1(500)   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函数改成:

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

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

  function Test(n)   local function foo()   local function inner1()   print(n)   end   local function inner2()   n = n + 10   end   return inner1,inner2   end   return foo   end   t = Test(1979)   f1,f2 = t()   f1() -- 打印1979   f2()   f1() -- 打印1989   g1,g2 = t()   g1() -- 打印1989   g2()   g1() -- 打印1999   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将函数做为基本类型值并支持词法定界的特性使得语言具有强大的抽象能力。而透彻认识函数、闭包和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   g2 = f1(500)   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函数改成:

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

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

  function Test(n)   local function foo()   local function inner1()   print(n)   end   local function inner2()   n = n + 10   end   return inner1,inner2   end   return foo   end   t = Test(1979)   f1,f2 = t()   f1() -- 打印1979   f2()   f1() -- 打印1989   g1,g2 = t()   g1() -- 打印1989   g2()   g1() -- 打印1999   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将函数做为基本类型值并支持词法定界的特性使得语言具有强大的抽象能力。而透彻认识函数、闭包和upvalue将帮助程序员善用这种能力。

分享到:
评论

相关推荐

    lua学习笔记.txt

    lua学习笔记

    lua (vc环境)学习笔记(学习代码)

    **lua (VC环境) 学习笔记** 在深入学习lua编程语言并将其应用于Microsoft Visual C++(VC环境)时,我们需要了解几个关键知识点。lua是一种轻量级的脚本语言,设计目标是作为嵌入式语言,使得游戏开发、系统管理、...

    lua学习笔记

    自己学习lua记得笔记,做任务用的~很基础的东西,想要的拿走,免费

    cocos2dx_lua开发笔记

    【cocos2dx_lua开发笔记】是一篇个人实践总结,主要涵盖了使用cocos2dx_lua进行游戏开发的一些关键点,包括...通过学习和理解这些笔记,开发者能够更好地理解和运用cocos2dx_lua进行游戏场景、UI元素和动画效果的构建。

    Lua学习资料PDF

    Lua是一种轻量级的、可嵌入式的脚本语言,主要设计用于支持程序的配置、扩展和脚本编写。它的语法简洁清晰,易于学习,同时提供了丰富的标准库和强大的元编程能力。以下是对Lua学习的一些关键知识点的详细介绍: 1....

    lua基础笔记

    元表是Lua的特色之一,它可以关联到任何表,并定义表的行为,比如如何进行索引和比较操作。 3. ** 函数与闭包 **:Lua中的函数是一等公民,可以作为参数传递、返回结果或者在表中存储。闭包是指有权访问其自身自由...

    Lua学习笔记之函数、变长参数、closure(闭包)、select等

    Lua是一种轻量级的脚本语言,具有易于嵌入到应用程序中、支持面向过程和函数式编程、拥有灵活的变量作用域等特点。本文将介绍Lua中函数的基础用法、变长参数、closure(闭包)、以及select函数的使用,并结合代码...

    lua基础学习笔记

    ### Lua基础学习笔记 #### 一、Lua简介与特点 Lua是一种轻量级且高效的脚本语言,广泛应用于游戏开发、网页应用、图形界面等领域。对于已有其他编程语言基础的学习者来说,掌握Lua通常较为迅速。Lua的设计哲学是...

    Lua学习笔记之表达式

    【Lua学习笔记之表达式】 在Lua编程语言中,表达式是构成程序的基本元素,用于表示计算或逻辑操作。表达式的结果可以是一个值,也可以是一个布尔值,这取决于执行的操作。下面我们将深入探讨Lua中的各种表达式类型...

    LUAC脚本解密_luac解密在线_luac4加密_luac反编译_luac4解密工具_luac解密工具

    "luac4解密工具"和"luac解密工具"则是指用于处理LUAC 4或者其他LUAC版本的解密和反编译软件。这些工具可能包括离线的桌面应用程序,也可能是前面提到的在线服务。它们的工作原理可能包括破译LUAC的加密算法,然后...

    LUAC反编译_LUC_lua反编译工具_luac_luac解密工具_Lua解密_

    Lua是一种轻量级的脚本语言,常用于游戏开发、...尽管反编译的结果可能与原始源代码有所不同,但它仍然是了解和学习Lua程序的重要途径。在实际操作中,应遵循合法和道德的原则,尊重软件版权,仅用于学习和研究目的。

    Lua学习框架代码

    - 提到的csdn博客链接(http://blog.csdn.net/liuwumiyuhuiping/article/details/9196435)可能包含一些教程和示例,适合初学者了解和学习Lua的基本用法。 - 通过lua5.1ѧϰ文件夹中的示例代码,可以动手实践编写...

    Lua学习入门教程

    ### Lua学习入门教程 #### 一、引言 本文旨在为初学者提供Lua脚本语言的基础教程。假设读者已经具备一定的编程基础,如了解Basic或C等语言的基本概念。Lua是一种轻量级、高效的脚本语言,常用于游戏开发和其他需要...

    LUA学习资料(包括Programming in Lua+Lua 5.1 参考手册+LUA5.0.2解释器)

    本资源包包含了学习LUA的重要参考资料,包括《Programming in Lua》这本书、《Lua 5.1 参考手册》以及LUA 5.0.2的解释器。 《Programming in Lua》是由Pierre Giraud和Luiz Henrique de Figueiredo合著的一本权威...

    Lua 5.1 manual 笔记

    在 Lua 中,存在多种基本的数据类型,包括 `nil`、`boolean`、`number`、`string`、`function`、`userdata`、`thread` 和 `table`。 ### 数据类型详解 #### 1. Nil (空值) - **定义**:`nil` 是 Lua 中唯一的一个...

    lua (vc环境)学习笔记

    【Lua (VC环境) 学习笔记】 Lua是一种轻量级的、开源的脚本语言,主要用于嵌入式系统和游戏开发。它以其简洁的语法、高效性和易于集成的特点受到广泛欢迎。在VC(Visual C++)环境中使用Lua,可以为C++应用程序提供...

    Lua学习笔记之类型与值

    Lua的类型与值的学习笔记为我们提供了对这些基础知识的全面了解,对于Lua新手来说是一份宝贵的资料。通过这些知识点的学习,开发者可以更加有效地编写Lua程序,并在日常开发中避免一些常见错误。

    LUAC解密工具.zip_andlua解密工具_andlua解密软件_lua 4.2解密_luac转lua_lua解密工具

    Lua是一种轻量级的脚本语言,常用于游戏开发、嵌入式系统和服务器应用程序等。在编程领域,加密和解密是常见的安全措施,尤其是对于脚本代码,以保护源代码不被轻易查看和篡改。"LUAC解密工具"就是针对Lua编译后的二...

Global site tag (gtag.js) - Google Analytics