我有一个主要在多维数组周围实现某些逻辑的类,它实际上是一个数字网格。这些数字可以交换位置等。但是,当它们交换时,同一类的其他对象也似乎被修改。我不确定为什么。
我正在使用实例变量来存储网格,所以我不明白为什么更改显然会影响其他类成员。
这是一个简化的例子;
class TestGrid
attr_accessor :grid
@grid = []
def initialize(newgrid)
@grid = newgrid
end
def to_s
out = ""
@grid.each{|row|
out += row.join("|") + "\n"
}
out
end
def swap(x, y)
@grid[x[0]][x[1]], @grid[y[0]][y[1]] = @grid[y[0]][y[1]], @grid[x[0]][x[1]]
end
end
当我们与IRB中的单个实例进行交互时,事情看起来很好;
1.9.3-p385 :001 > require './TestGrid.rb'
=> true
1.9.3-p385 :002 > x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
1.9.3-p385 :003 > x.swap([0,1],[1,1])
=> [4, 2]
1.9.3-p385 :004 > puts x
1|4
3|2
=> nil
但是,如果我通过克隆或重复创建第二个实例;
1.9.3-p385 :006 > x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
1.9.3-p385 :007 > y = x.clone
=> 1|2
3|4
1.9.3-p385 :008 > x.swap([0,1],[1,1])
=> [4, 2]
1.9.3-p385 :009 > puts x
1|4
3|2
=> nil
1.9.3-p385 :010 > puts y
1|4
3|2
=> nil
为什么我对x的更改也适用于y?根据我对Object#Clone的理解,这些应该是不同的实例,彼此无关。他们的对象ID似乎支持这种期望;
1.9.3-p385 :012 > puts "#{x.object_id} #{y.object_id}"
70124426240320 70124426232820
作为参考,我最终创建了一个initialize_copy方法,该方法可确保深度复制受影响的参数。我不太喜欢只是为了复制一个数组来编组对象的想法,所以我决定这样做。
def initialize_copy(original)
super
@grid = []
original.grid.each{|inner|
@grid << inner.dup
}
end
答案 0 :(得分:2)
默认情况下,dup
和clone
会生成对其调用的对象的浅副本。这意味着示例中的x
和y
仍然引用相同的内存区域。
http://ruby-doc.org/core-2.0/Object.html#method-i-dup
http://ruby-doc.org/core-2.0/Object.html#method-i-clone
您可以在自定义类中覆盖它们,以在不同的内存区域中生成深副本。
Ruby中常见的习惯用法是使用Object超类的Marshal#load
和Marshal#dump
方法来生成深层副本。 (注意:这些方法通常用于序列化/反序列化对象)。
def dup
new_grid = Marshal.load( Marshal.dump(@grid) )
new_grid
end
irb(main):007:0> x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
irb(main):008:0> y = x.dup
=> [[1, 2], [3, 4]]
irb(main):009:0> x.swap([0,1],[1,1])
=> [4, 2]
irb(main):010:0> puts x
1|4
3|2
=> nil
irb(main):011:0> y
=> [[1, 2], [3, 4]]
irb(main):012:0> puts y
1
2
3
4
=> nil
irb(main):013:0>
交换后, y
保持不变。
或者,创建一个新数组,遍历@grid并将其子数组推送到数组中。
def dup
new_grid = []
@grid.each do |g|
new_grid << g
end
new_grid
end