我有一个类,我在实例变量上使用Array#shift
实例方法。我以为我对我的实例变量进行了“复制”,但事实上我没有,shift
实际上是在改变实例变量。
例如,在我预期两次获得["foo", "bar", "baz"]
之前,给出以下内容:
class Foo
attr_reader :arr
def initialize arr
@arr = arr
end
def some_method
foo = arr
foo.shift
end
end
foo = Foo.new %w(foo bar baz)
p foo.arr #=> ["foo", "bar", "baz"]
foo.some_method
p foo.arr #=> ["bar", "baz"]
结果:
["foo", "bar", "baz"]
["bar", "baz"]
但如图所示,我的“副本”根本不是真正的副本。现在,我不确定我是否应该调用我想要的“复制”,“克隆”,“重复”,“深度克隆”,“深层复制”,“冻结克隆”等...
我真的很困惑要搜索什么,并发现了一堆疯狂的尝试,似乎“制作一个数组副本”。
然后我发现another answer字面上只有一行解决了我的问题:
class Foo
attr_reader :arr
def initialize arr
@arr = arr
end
def some_method
foo = [].replace arr
foo.shift
end
end
foo = Foo.new %w(foo bar baz)
p foo.arr #=> ["foo", "bar", "baz"]
foo.some_method
p foo.arr #=> ["foo", "bar", "baz"]
输出:
["foo", "bar", "baz"]
["foo", "bar", "baz"]
我理解Array#replace
是在Array
的实例上调用的实例方法恰好是一个空数组(例如foo = ["cats", "and", "dogs"].replace arr
仍然可以工作)并且它是有意义的我得到了实例变量@arr
的“副本”。
但这有什么不同于:
foo = arr
foo = arr.clone
foo = arr.dup
foo = arr.deep_clone
Marshal.load # something something
# etc...
或者我在SO上看到的dup
和map
以及inject
的任何其他疯狂组合?
答案 0 :(得分:2)
这是红宝石中可变性的棘手概念。就核心对象而言,这通常会产生数组和散列。字符串也是可变的,但可以通过脚本顶部的标志禁用。请参阅What does the comment "frozen_string_literal: true" do?。
在这种情况下,您可以轻松调用dup
,deep_dup
,clone
,效果与replace
相同:
['some', 'array'].dup
['some', 'array'].deep_dup
['some', 'array'].clone
Marshal.load Marshal::dump(['some', 'array'])
就差异而言,dup
和clone
是相同的,除了一些细微差别的细节 - 请参阅What's the difference between Ruby's dup and clone methods?
这些与deep_dup
之间的区别在于deep_dup
递归地起作用。例如,如果复制嵌套数组,则不会克隆内部数组:
a = [[1]]
b = a.clone
b[0][0] = 2
a # => [[2]]
哈希也会发生同样的事情。
Marshal.load Marshal::dump <object>
是深度克隆对象的一般方法,与deep_dup
不同,它位于ruby核心中。 Marshal::dump
返回一个字符串,因此在将对象序列化为文件时可以很方便。
如果你想避免像这样的意外错误,请保留哪些方法有副作用的心理索引,并且仅在有意义的时候调用它们。方法名称末尾的解释点表示它有副作用,但其他包括unshift,push,concat,delete和pop。功能编程的很大一部分是避免副作用。您可以看到https://www.sitepoint.com/functional-programming-techniques-with-ruby-part-i/
答案 1 :(得分:2)
首选方法是dup
array.dup
array.map(&:dup)
除非您真的想深度复制整个对象图,否则不要使用编组技巧。通常,您只想复制数组,而不是复制包含的元素。