我很擅长从Google那里获得答案,但是我只是不明白这一点。在以下代码中,为什么在调用“ addup”之后更改变量“ b”?我想我理解为什么'a'被更改了(尽管有点模糊),但是我想将原始数组'a'保存为'b',在'a'上运行该方法,所以我有两个内容不同的数组。我在做什么错了?
预先感谢
def addup(arr)
i=0
while i< arr.length
if arr[i]>3
arr.delete_at(i)
end
i += 1
end
return arr
end
a = [1,2,3,4]
b = a
puts "a=#{a}" # => [1,2,3,4]
puts "b=#{b}" # => [1,2,3,4]
puts "addup=#{addup(a)}" # => [1,2,3]
puts "a=#{a}" # => [1,2,3]
puts "b=#{b}" # => [1,2,3]
答案 0 :(得分:4)
a
和b
都拥有对内存中同一数组对象的引用。为了将原始数组保存在b
中,您需要复制该数组。
a = [1,2,3,4] # => [1, 2, 3, 4]
b = a # => [1, 2, 3, 4]
c = a.dup # => [1, 2, 3, 4]
a.push 5 # => [1, 2, 3, 4, 5]
a # => [1, 2, 3, 4, 5]
b # => [1, 2, 3, 4, 5]
c # => [1, 2, 3, 4]
要详细了解为什么会发生这种情况,请阅读Is Ruby pass by reference or by value?
答案 1 :(得分:3)
但是我想将原始数组'a'保存到'b'
您没有将原始的数组保存到b
中。值a
是数组的引用。您正在复制一个引用,它仍然指向同一数组。无论您使用哪个引用来改变数组,更改都将通过两个引用可见,因为它们再次指向同一数组。
要获取数组的副本,必须显式地执行该操作。对于具有原始值的浅数组,简单的a.dup
就足够了。对于嵌套的结构或包含对复杂对象的引用的结构,您可能需要深层副本。像这样:
b = Marhal.load(Marshal.dump(a))
答案 2 :(得分:2)
在下面的代码中,为什么调用“ addup”后变量“ b”却被更改了?
变量未更改。它仍然引用与之前完全相同的数组。
只有两种方法可以在Ruby中更改变量:
foo = :bar
)Binding#local_variable_set
,Object#instance_variable_set
,Module#class_variable_set
,Module#const_set
)这两个都不用。
我想我理解为什么'a'被更改了(尽管有点模糊)
a
也未更改。它还仍然引用与之前完全相同的数组。 (顺便说一下,b
引用的是同一数组。)
唯一要做的更改是a
和b
都引用的数组的内部状态。因此,如果您确实了解了a
引用的数组为何更改,那么您也了解了b
引用的数组为何更改,因为它是同一数组。您的代码中只有一个数组。
代码中的立即问题是,如果要复制数组,则需要实际复制数组。这就是Object#dup
和Object#clone
的用途:
b = a.clone
将修复您的代码。
但是!
您的代码中还有其他一些问题。主要问题是变异。如果可能的话,您应尽可能避免突变(通常是副作用,突变只是其中的一个例子),并且仅在确实需要时才使用它。特别是,您应该从不更改您不拥有的对象,这意味着您应该从不更改作为参数传递给您的对象。
但是,在您的addup
方法中,您对作为arr
传递给您的数组进行了突变。突变是问题的根源,如果您没有突变arr
而是返回了一个具有所需修改的新数组,那么您首先就不会遇到问题。不改变参数的一种方法是将clone
移到方法中,但是还有一种更好的方法。
代码的另一个问题是您正在使用循环。在Ruby中,几乎永远不会存在循环是最佳解决方案的情况。实际上,我什至会争辩说,如果您使用循环,那么您做错了。
循环容易出错,难以理解,难以正确执行,并且依赖。循环不能没有副作用,但是,我们只是说过要避免副作用!
关键点:您的循环包含一个严重的错误。如果我通过[1, 2, 3, 4, 5]
,结果将是[1, 2, 3, 5]
。为什么?由于突变和手动循环:
在循环的第四次迭代中,开始时,数组如下所示:
[1, 2, 3, 4, 5]
# ↑
# i
调用delete_at(i)
之后,数组如下所示:
[1, 2, 3, 5]
# ↑
# i
现在,您增加i
,因此情况如下:
[1, 2, 3, 5]
# ↑
# i
i
现在大于数组的长度,因此,遍历,循环结束,并且5
从未删除。
您真的想要的是这个
def addup(arr)
arr.reject {|el| el > 3 }
end
a = [1, 2, 3, 4, 5]
b = a
puts "a=#{a}" # => [1, 2, 3, 4, 5]
puts "b=#{b}" # => [1, 2, 3, 4, 5]
puts "addup=#{addup(a)}" # => [1, 2, 3]
puts "a=#{a}" # => [1, 2, 3, 4, 5]
puts "b=#{b}" # => [1, 2, 3, 4, 5]
如您所见,没有任何突变。 addup
仅返回具有所需修改的新数组。如果以后要引用该数组,可以将其分配给变量:
c = addup(a)
无需手动处理循环索引。无需复制或克隆任何东西。就像爱因斯坦所说的那样,没有“远距离的怪异动作”。我们修复了两个错误,并通过
删除了7行代码