调用方法时的Ruby变量行为

时间:2018-07-13 12:08:30

标签: ruby

我很擅长从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]

3 个答案:

答案 0 :(得分:4)

ab都拥有对内存中同一数组对象的引用。为了将原始数组保存在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中更改变量:

  1. 分配(foo = :bar
  2. 反射(Binding#local_variable_setObject#instance_variable_setModule#class_variable_setModule#const_set

这两个都不用。

  

我想我理解为什么'a'被更改了(尽管有点模糊)

a也未更改。它还仍然引用与之前完全相同的数组。 (顺便说一下,b引用的是同一数组。)

唯一要做的更改ab都引用的数组的内部状态。因此,如果您确实了解了a引用的数组为何更改,那么您也了解了b引用的数组为何更改,因为它是同一数组。您的代码中只有一个数组。

代码中的立即问题是,如果要复制数组,则需要实际复制数组。这就是Object#dupObject#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行代码
  • 避免突变
  • 避免循环