更改Ruby对象的状态会更改其他类成员吗?

我有一个主要在多维数组周围实现一些逻辑的类,它基本上是一个数字网格。 这些数字可以交换位置等。但是,当它们交换时,同一类的其他对象也似乎被修改。 我不知道为什么。

我正在使用实例变量来存储网格,所以我不明白为什么更改显然会影响其他类成员。

这是一个简化的例子;

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 

默认情况下, dupclone生成调用它们的对象的副本。 这意味着示例中的xy仍然引用相同的内存区域。

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#loadMarshal#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