枚举器`Array#each`的{block}不能总是改变数组值?

jj_*_*jj_ 6 ruby arrays each block enumerator

好吧也许这很简单,但是......鉴于此:

arr = ("a".."z").to_a

arr

=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
Run Code Online (Sandbox Code Playgroud)

..而且我正在尝试将所有"arr"值更改为"bad"

为什么没有这方面的工作?

arr.each { |v| v = "bad" }

arr

=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
Run Code Online (Sandbox Code Playgroud)

答案表明"v"是块的局部变量(数组值的"副本"),我完全明白(以前从未困惑过我)但是

..如果数组元素是对象,它为什么工作?

class Person
  def initialize
    @age = 0
  end
  attr_accessor :age
end

kid = Person.new
man = Person.new
arr = [kid, man]


arr.each { |p| p.age = 50 }

arr[0]
=> #<Person:0xf98298 @age=50>
Run Code Online (Sandbox Code Playgroud)

这里的"p"还不在这里吗?但那真的影响了对象,怎么样?

iai*_*ain 11

我将扩展@pst的评论:

为什么这不起作用?

arr.each { |v| v = "bad" }
Run Code Online (Sandbox Code Playgroud)

因为each遍历数组并将每个项放入您作为局部变量给出的块中v,因为v它不是对数组的引用arr.

new_arr = arr.each { |v| v = "bad" }
Run Code Online (Sandbox Code Playgroud)

each因为你会使用map(不参考@ benjaminbenben的答案).因此,分配它不"有效".

arr.each { |v| arr[arr.index v] = "bad" }
Run Code Online (Sandbox Code Playgroud)

在这里,你把每个项目arr到局部变量v,但你提到了阵列本身的块,因此你能够分配给数组,并使用局部变量v找到对应的内容的索引v(但是你可能会发现当项目并非都是唯一的时候,这将无法正常工作.

arr.each { |p| p.age = 50 }

kid.age #-> 50
Run Code Online (Sandbox Code Playgroud)

在这里,您再次填充了p每个项目/对象的局部变量arr,但之后您通过方法访问了每个项目,因此您可以更改该项目 - 您不会更改数组.它是不同的,因为引用是局部变量的内容,你混淆了它是对数组的引用.它们是分开的东西.


回应以下评论:

arr[0]
# => #<Person:0xf98298 @age=50>
Run Code Online (Sandbox Code Playgroud)

这完全取决于谁在何时提到谁.

试试这个:

v = Person.new
# => #<Person:0x000001008de248 @age=0>
w = Person.new
# => #<Person:0x000001008d8050 @age=0>
x = v
# => #<Person:0x000001008de248 @age=0>
v = Person.new
# => #<Person:0x00000100877e80 @age=0>
arr = [v,w,x]
# => [#<Person:0x00000100877e80 @age=0>, #<Person:0x000001008d8050 @age=0>, #<Person:0x000001008de248 @age=0>]
Run Code Online (Sandbox Code Playgroud)

v在那里提到了2个不同的对象.v这不是一个固定的东西,它是一个名字.首先它指的是#<Person:0x000001008de248 @age=0>,然后它指的是#<Person:0x00000100877e80 @age=0>.

现在试试这个:

arr.each { |v| v = "bad" }
# => [#<Person:0x00000100877e80 @age=0>, #<Person:0x000001008d8050 @age=0>, #<Person:0x000001008de248 @age=0>]
Run Code Online (Sandbox Code Playgroud)

它们都是对象,但没有更新或"工作".为什么?因为首次输入块时,v引用数组中已生成(给定)的项.所以在第一次迭代v#<Person:0x00000100877e80 @age=0>.

但是,我们然后分配"bad"v.我们没有分配"bad"给数组的第一个索引,因为我们根本没有引用数组.arr是对数组的引用.放在arr块内,你可以改变它:

arr.each { |v| 
  arr[0] = "bad" # yes, a bad idea!
}
Run Code Online (Sandbox Code Playgroud)

为什么然后arr.each { |p| p.age = 50 }更新数组中的项目?因为p指的是也恰好在数组中的对象.在第一次迭代时p引用的对象也称为kid,并且kid有一个age=方法并且你会坚持50下去.kid也是数组中的第一个项目,但你所说的kid不是数组.你可以这样做:

arr.each { |p| p = "bad"; p.age }
NoMethodError: undefined method `age' for "bad":String
Run Code Online (Sandbox Code Playgroud)

首先,p提到也恰好在数组中的对象(就是它产生的地方),但后来p被引用"bad".

each迭代数组并在每次迭代时产生一个值.您只获得值而不是数组.如果要更新阵列,请执行以下操作:

new_arr = arr.map{|v| v = "bad" }
new_arr = arr.map{|v| "bad" } # same thing
Run Code Online (Sandbox Code Playgroud)

要么

arr.map!{|v| v = "bad"}
arr.map!{|v| "bad"}  # same thing
Run Code Online (Sandbox Code Playgroud)

as map返回一个填充了块的返回值的数组.map!将使用填充了块的返回值的数组更新您调用它的引用.一般来说,无论如何迭代它时更新对象是个坏主意.我发现将它想象为创建一个新数组总是更好,然后你可以使用这些!方法作为捷径.