在一些 myClass
类中,我有:
foo
|i b|
i := 5.
b := [i := i * 2. i].
i :=3.
^b
我跑:
|a i b1 b2|
i := 4.
a := MyClass new.
b1 := a foo.
b2 := a foo.
Transcript show: b1 value; cr.
i := 1.
Transcript show: b1 value; cr.
Transcript show: b2 value; cr.
我不明白为什么输出是:
6
12
6
能否解释一下编译器是如何计算出来的?
最佳答案
您得到的是预期的行为。首先,脚本中定义的临时i
,无论其名称如何,与#foo
中的临时i
没有任何影响或关系。方法,这样你就可以从脚本中删除它。
其次,该方法以 BlockClosure
进行响应,即 [i := i*2。我]
。这意味着它将记住 i 的值,将其加倍,然后返回它。作为副作用,该方法还将 i
的值设置为 3
。
然后,当您第一次计算 b
时,i
从 3
开始,加倍为 6
> 然后返回。因此,您得到6
。第二次评估仅评估该 block ,因此它使用 i
的当前值(即 6
),将其加倍并返回结果,即 12
。再次计算 b
,您将得到 24
、48
等。
另请注意,每次发送 #foo
时,它应答的 block 的计算结果将为 6
。
附录
问题提到了编译器。好吧,编译器在脚本中做的事情很少。然而,在#foo 中,它做了一些更复杂的事情,它使用代码[i := i * 2.i]
创建了BlockClosure
。 block 中未使用的临时对象存在于堆栈中,但此临时 i
则不然。为什么?因为 block 是一个在方法执行后仍然存在的对象,并且一旦方法返回,堆栈就消失了。然后,该 block 将在 Array
中分配变量 i
,该变量表示该 block 需要继续工作的环境。这个数组不是在堆栈中分配的,而是在对象内存中分配的。这是 block 记住i
的最后一个值的地方,并且即使在调用#foo
之后也可以根据需要多次更新和使用它,以及它的堆栈使用的内容会被其他调用覆盖。 (是的,在 Smalltalk 中,编译器也是第一类对象。)
关于smalltalk - Block 在 Squeak 中使用外部变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57979654/