简体   繁体   English

调用方法时的Ruby变量行为

[英]Ruby variable behaviour when calling a method

I'm pretty good at getting answers from google, but I just don't get this. 我很擅长从Google那里获得答案,但我只是不明白这一点。 In the following code, why does variable 'b' get changed after calling 'addup'? 在以下代码中,为什么在调用“ addup”之后更改变量“ b”? I think I understand why 'a' gets changed (although its a bit fuzzy), but I want to save the original array 'a' into 'b', run the method on 'a' so I have two arrays with different content. 我想我理解为什么'a'被更改了(尽管有点模糊),但是我想将原始数组'a'保存为'b',在'a'上运行该方法,所以我有两个内容不同的数组。 What am I doing wrong? 我究竟做错了什么?

Thanks in advance 提前致谢

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]

Both a and b hold a reference to the same array object in memory. ab持有对内存中同一数组对象的引用。 In order to save the original array in b , you'd need to copy the array. 为了将原始数组保存在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]

For more information on why this is happening, read Is Ruby pass by reference or by value? 有关为什么发生这种情况的更多信息,请阅读Ruby是按引用传递还是按值传递?

but I want to save the original array 'a' into 'b' 但我想将原始数组'a'保存为'b'

You are not saving the original array into b . 您没有将原始数组保存到b Value of a is a reference to an array. 的值a是一个数组的引用 You are copying a reference, which still points to the same array. 您正在复制一个引用,它仍然指向同一数组。 No matter which reference you use to mutate the array, the changes will be visible through both references, because, again, they point to the same array. 无论您使用哪个引用来改变数组,更改都将通过两个引用可见,因为它们再次指向同一数组。

To get a copy of the array, you have to explicitly do that. 要获取数组的副本,必须显式地执行该操作。 For shallow arrays with primitive values, simple a.dup will suffice. 对于具有原始值的浅层数组,简单的a.dup就足够了。 For structures which are nested or contain references to complex objects, you likely need a deep copy. 对于嵌套的结构或包含对复杂对象的引用的结构,您可能需要深层副本。 Something like this: 像这样:

b = Marhal.load(Marshal.dump(a))

In the following code, why does variable 'b' get changed after calling 'addup'? 在以下代码中,为什么在调用“ addup”之后更改变量“ b”?

The variable doesn't get changed. 该变量不会更改。 It still references the exact same array it did before. 它仍然引用与之前完全相同的数组。

There are only two ways to change a variable in Ruby: 在Ruby中只有两种更改变量的方法:

  1. Assignment ( foo = :bar ) 赋值( foo = :bar
  2. Reflection ( Binding#local_variable_set , Object#instance_variable_set , Module#class_variable_set , Module#const_set ) 反射( Binding#local_variable_setObject#instance_variable_setModule#class_variable_setModule#const_set

Neither of those is used here. 这些都不在这里使用。

I think I understand why 'a' gets changed (although its a bit fuzzy) 我想我理解为什么“ a”会更改(尽管有点模糊)

a doesn't get changed either. a也不会改变。 It also still references the exact same array it did before. 它还仍然引用与之前完全相同的数组。 (Which, incidentally, is the same array that b references.) (顺便说一下,这是b引用的数组。)

The only thing which does change is the internal state of the array that is referenced by both a and b . 确实发生改变的唯一事情是受两个引用的数组的内部状态ab So, if you really understand why the array referenced by a changes, then you also understand why the array referenced by b changes, since it is the same array . 因此,如果您真正了解了为什么a引用的数组发生a变化,那么您也了解了为什么b引用的数组发生了变化, 因为它是同一array There is only one array in your code. 您的代码中只有一个数组。

The immediate problem with your code is that, if you want a copy of the array, then you need to actually make a copy of the array. 代码的直接问题是,如果您想要阵列的副本,那么您实际上需要制作阵列的副本。 That's what Object#dup and Object#clone are for: 这就是Object#dupObject#clone的用途:

b = a.clone

Will fix your code. 将修复您的代码。

BUT! 但!

There are some other problems in your code. 您的代码中还有其他一些问题。 The main problem is mutation . 主要问题是突变 If at all possible, you should avoid mutation (and side-effects in general, of which mutation is only one example) as much as possible and only use it when you really, REALLY have to. 如果可能的话,您应尽可能避免突变(通常是副作用,突变只是其中的一个例子),并且仅在确实需要时才使用它。 In particular, you should never mutate objects you don't own, and this means you should never mutate objects that were passed to you as arguments. 特别是,您绝对不要更改您不拥有的对象,这意味着您绝对不要更改作为参数传递给您的对象。

However, in your addup method, you mutate the array that is passed to you as arr . 但是,在您的addup方法中,您将以arr形式传递给您的数组变异。 Mutation is the source of your problem, if you didn't mutate arr but instead returned a new array with the modifications you want, then you wouldn't have the problem in the first place. 突变是问题的根源,如果您没有突变arr而是返回了一个具有所需修改的新数组,那么您就不会遇到问题了。 One way of not mutating the argument would be to move the clone ing into the method, but there is an even better way. 不改变参数的一种方法是将clone移到方法中,但是还有一种更好的方法。

Another problem with your code is that you are using a loop. 代码的另一个问题是您正在使用循环。 In Ruby, there is almost never a situation where a loop is the best solution. 在Ruby中,几乎永远不会存在循环是最佳解决方案的情况。 In fact, I would go so far as to argue that if you are using a loop, you are doing it wrong. 实际上,我什至会争辩说,如果您使用循环,那么您做错了。

Loops are error-prone, hard to understand, hard to get right, and they depend on side-effects. 循环容易出错,难以理解,难以正确处理,并且取决于副作用。 A loop cannot work without side-effects, yet, we just said we want to avoid side-effects! 没有副作用,循环是行不通的 ,但是,我们只是说过要避免副作用!

Case in point: your loop contains a serious bug. 恰当的例子:您的循环包含一个严重的错误。 If I pass [1, 2, 3, 4, 5] , the result will be [1, 2, 3, 5] . 如果我通过[1, 2, 3, 4, 5] ,结果将是[1, 2, 3, 5] Why? 为什么? Because of mutation and manual looping: 由于突变和手动循环:

In the fourth iteration of the loop, at the beginning, the array looks like this: 在循环的第四次迭代中,开始时,数组如下所示:

[1, 2, 3, 4, 5]
#         ↑
#         i

After the call to delete_at(i) , the array looks like this: 调用delete_at(i) ,数组如下所示:

[1, 2, 3, 5]
#         ↑
#         i

Now, you increment i , so the situation looks like this: 现在,您增加i ,因此情况如下所示:

[1, 2, 3, 5]
#            ↑
#            i

i is now greater than the length of the array, ergo, the loop ends, and the 5 never gets removed. i现在大于数组的长度,因此,ergo,循环结束,并且5永远都不会删除。

What you really want, is this: 真正想要的是:

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]

As you can see, nothing was mutated. 如您所见,没有任何变化。 addup simply returns the new array with the modifications you want. addup只是返回具有所需修改的新数组。 If you want to refer to that array later, you can assign it to a variable: 如果以后要引用该数组,可以将其分配给变量:

c = addup(a)

There is no need to manually fiddle with loop indices. 无需手动修改循环索引。 There is no need to copy or clone anything. 无需复制或克隆任何东西。 There is no "spooky action at a distance", as Albert Einstein called it. 就像爱因斯坦所说的那样,没有“远距离的怪异动作”。 We fixed two bugs and removed 7 lines of code, simply by 我们修复了两个错误, 删除了7行代码,只需通过

  • avoiding mutation 避免突变
  • avoiding loops 避免循环

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM