闭包,block

function make_counter()
local count = 0
function inc_count()
count = count + 1
return count
end
return inc_count
end

c1 = make_counter()
c2 = make_counter()
print(c1())
print(c2())


在这段程序中,函数 inc_count 定义在函数 make_counter 内部,并作为 make_counter 的返回值。变量 count 不是 inc_count 内的局部变量,按照最内嵌套作用域的规则,inc_count 中的 count 引用的是外层函数中的局部变量 count。接下来的代码中两次调用 make_counter() ,并把返回值分别赋值给 c1 和 c2 ,然后又依次打印调用 c1 和 c2 所得到的返回值。

这里存在一个问题,当调用 make_counter 时,在其执行上下文中生成了局部变量 count 的实例,所以函数 inc_count 中的 count 引用的就是这个实例。但是 inc_count 并没有在此时被执行,而是作为返回值返回。当 make_counter 返回后,其执行上下文将失效,count 实例的生命周期也就结束了,在后面对 c1 和 c2 调用实际是对 inc_count 的调用,而此处并不在 count 的作用域中,这看起来是无法正确执行的。

上面的例子说明了把函数作为返回值时需要面对的问题。当把函数作为参数时,也存在相似的问题。下面的例子演示了把函数作为参数的情况。


function do10times(fn)
for i = 0,9 do
fn(i)
end
end

sum = 0
function addsum(i)
sum = sum + i
end

do10times(addsum)

print(sum)

这里我们看到,函数 addsum 被传递给函数 do10times,被并在 do10times 中被调用10次。不难看出 addsum 实际的执行点在 do10times 内部,它要访问非局部变量 sum,而 do10times 并不在 sum 的作用域内。这看起来也是无法正常执行的。


这两种情况所面临的问题实质是相同的。在这样的语言中,如果按照作用域规则在执行(调用)时确定一个函数的引用(调用)环境,那么这个引用(调用)环境可能和函数定义时(函数定义时的环境)不同。要想使这两段程序正常执行,一个简单的办法是在函数定义时捕获当时的引用(定义时的环境)环境,并与函数代码组合成一个整体。当把这个整体当作函数调用时,先把其中的引用环境(调用时的环境)覆盖到当前的引用环境(定义时的环境)上,然后执行具体代码,并在调用结束后恢复原来的引用环境(定义时环境)。这样就保证了函数定义和执行时的引用环境是相同的。这种由引用环境与函数代码组成的实体就是闭包。当然如果编译器或解释器能够确定一个函数在定义和运行时的引用环境是相同的,那就没有必要把引用环境和代码组合起来了,这时只需要传递普通的函数就可以了。现在可以得出这样的结论:闭包不是函数,只是行为和函数相似,不是所有被传递的函数都需要转化为闭包,只有引用环境可能发生变化的函数才需要这样做。


再次观察上面两个例子会发现,代码中并没有通过名字来调用函数 inc_count 和 addsum,所以他们根本不需要名字。以第一段代码为例,它可以重写成下面这样:

function make_counter()
local count = 0
return function()
count = count + 1
return count
end
end

c1 = make_counter()
c2 = make_counter()

print(c1())
print(c2())


这里使用了匿名函数。使用匿名函数能使代码得到简化,同时我们也不必挖空心思地去给一个不需要名字的函数取名字了

Ruby 使用 Block 来定义闭包,Block 在 Ruby 中十分重要,几乎到处都可以看到它的身影,下面的代码就展示了一个 Block:

sum = 0
10.times{|n| sum += n}
print sum

10.times 表示调用对象10的 times 方法,紧跟在这个调用后面的大括号里面的部分就是Block。所谓 Block 是指紧跟在函数调用之后用大括号或 do/end 括起来的代码,Block 的开始部分(左大括号或 do)必须和函数调用在同一行。Block 也可以接受参数,参数列表必须用两个竖杠括起来放在最前面。Block 会被作为它前面的函数调用的参数,而在这个函数中可以使用关键字 yield 来调用该 Block。在这个例子中,10.times 会以数字0到9为参数调用 Block 10次。

Block 实际上就是匿名函数,它可以被调用,可以捕获上下文。由于语法上要求 Block 必须出现在函数调用的后面,所以 Block 不能直接作为函数的的返回值。要想从一个函数中返回 Block,必须使用 proc 或 lambda 函数把 Block 转化为对象才行


block: block就是匿名函数
闭包: 如果block在定义的时候,携带定义时的上下文,那么在调用这个block的时候,这个block就叫做闭包。(block可以携带定义时的上下文信息,即使在调用的时候,这些信息已经不存在,
但是调 用的时候依然可以使用当时的上下文信息,这个上下文信息包括方法,变量,常量等)
Proc对象: block不能作为函数的返回值,要想从函数中返回bloc,必须用proc 或者lambda函数把block转化为proc对象才行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值