何时改变一个对象也会改变该对象的副本?



我对copy()函数感到困惑。据我所知,=是指针式赋值,deepcopy()是创建一个新的独立副本。但是,我发现copy()不是很"稳定"。请看下面两个例子:

b = [[1,2,3], [4,5,6]];
a = copy(b);
b[1][1] = 10;
a
b

在上面的例子中,a在赋值b[1][1]后也发生了变化

而在第二个例子中:

b = [[1,2,3], [4,5,6]];
a = copy(b);
b[1] = [10,2,3];
a
b

b[1]的赋值并没有真正改变a。这真的很令人困惑。谁能简单解释一下发生了什么?谢谢你!

copy创建一个浅拷贝,因此在您的情况下,对对象的引用被复制而不是实际数据。这是因为你的b是向量的Vector所以它被记录为:

b = [<reference to the first vector>, <reference to the second vector>]

当你创建一个浅copy时,只复制那些引用,而不复制底层数据。因此,复制的引用仍然指向相同的内存地址。

在第二个示例中,您正在替换实际引用。由于对象a持有引用的副本,因此在a中看不到替换b中的整个引用。

这种行为将会出现在任何你在对象中有对象的地方。数据结构。另一方面,如果您有原语数组(在引用上),您将获得一个实际的副本,例如:

julia> a = [1 3; 3 4]
2×2 Matrix{Int64}:
1  3
3  4
julia> b = copy(a); b[1,1] = 100
100
julia> a
2×2 Matrix{Int64}:
1  3
3  4

这是对等号、copydeepcopy函数之间区别的更详细的解释,摘自我的《Julia快速语法参考:数据科学编程袖珍指南》的第二章。(Apress 2019)书:

内存和复制问题

为了避免复制大量数据,Julia默认情况下只复制对象的内存地址,除非程序员明确要求所谓的"深度"。拷贝或编译器"判断"实际复制效率更高。

当您不希望对复制对象的后续修改应用于原始对象时,请使用copy()deepcopy()

在细节:

等号(a=b)

  • 执行名称绑定,即将b引用的实体(对象)也绑定(分配)到a标识符(变量名)
  • 的结果是:
    • 如果b然后重新绑定到其他对象,a仍然引用到原始对象
    • 如果b引用的对象发生了变化(即它内部发生了变化),那么a引用的对象(作为相同的对象)也会发生变化
  • 如果b是不可变的并且在内存中很小,在某些情况下,编译器会创建一个新对象并将其绑定到a,但是对于用户来说是不可变的,这种差异不会明显
  • 对于许多高级语言,我们不需要显式地担心内存泄漏。垃圾收集器的存在是为了自动销毁不再可访问的对象。

a = copy(b)

  • 创建一个新的,"独立的";复制对象并将其绑定到a。然而,这个新对象可以通过内存地址依次引用其他对象。在这种情况下,复制的是它们的内存地址,而不是被引用的对象本身。
  • 的结果是:
    • 如果这些引用对象(例如a向量)被反弹到其他一些对象,新对象引用a维护对原始对象
    • 的引用
    • 如果这些被引用的对象发生了变化,那么被a
    • 引用的新对象所引用的对象(作为相同的对象)也会发生变化。

a = deepcopy(b)

  • 所有内容都是递归深度复制

下面的代码片段突出显示了"copying"的这三个方法之间的区别一个对象:

julia> a = [[[1,2],3],4]
2-element Array{Any,1}:
Any[[1, 2], 3]
4
julia> b = a
2-element Array{Any,1}:
Any[[1, 2], 3]
4
julia> c = copy(a)
2-element Array{Any,1}:
Any[[1, 2], 3]
4
julia> d = deepcopy(a)
2-element Array{Any,1}:
Any[[1, 2], 3]
4
# rebinds a[2] to an other objects.
# At the same time mutates object a:
julia> a[2] = 40
40
julia> b
2-element Array{Any,1}:
Any[[1, 2], 3]
40
julia> c
2-element Array{Any,1}:
Any[[1, 2], 3]
4
julia> d
2-element Array{Any,1}:
Any[[1, 2], 3]
4
# rebinds a[1][2] and at the same
# time mutates both a and a[1]:
julia> a[1][2] = 30
30
julia> b
2-element Array{Any,1}:
Any[[1, 2], 30]
40
julia> c
2-element Array{Any,1}:
Any[[1, 2], 30]
4
julia> d
2-element Array{Any,1}:
Any[[1, 2], 3]
4
# rebinds a[1][1][2] and at the same
# time mutates a, a[1] and a[1][1]:
julia> a[1][1][2] = 20
20
julia> b
2-element Array{Any,1}:
Any[[1, 20], 30]
40
julia> c
2-element Array{Any,1}:
Any[[1, 20], 30]
4
julia> d
2-element Array{Any,1}:
Any[[1, 2], 3]
4
# rebinds a:
julia> a = 5
5
julia> b
2-element Array{Any,1}:
Any[[1, 20], 30]
40
julia> c
2-element Array{Any,1}:
Any[[1, 20], 30]
4
julia> d
2-element Array{Any,1}:
Any[[1, 2], 3]
4

我们可以检查两个对象是否在==中具有相同的值,以及两个对象是否在===中实际上是相同的(在某种意义上,不可变对象在位级别检查,可变对象在其内存地址检查):

  • 给定a = [1, 2]; b = [1, 2];a == ba === a为真,但a === b为假;
  • 给定a = (1, 2); b = (1, 2);,所有a == b, a === aa === b为真。

最新更新