我对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
这是对等号、copy
和deepcopy
函数之间区别的更详细的解释,摘自我的《Julia快速语法参考:数据科学编程袖珍指南》的第二章。(Apress 2019)书:
内存和复制问题
为了避免复制大量数据,Julia默认情况下只复制对象的内存地址,除非程序员明确要求所谓的"深度"。拷贝或编译器"判断"实际复制效率更高。
当您不希望对复制对象的后续修改应用于原始对象时,请使用copy()
或deepcopy()
。
在细节:
等号(a=b)
- 执行名称绑定,即将
b
引用的实体(对象)也绑定(分配)到a
标识符(变量名) - 的结果是:
- 如果
b
然后重新绑定到其他对象,a
仍然引用到原始对象 - 如果
b
引用的对象发生了变化(即它内部发生了变化),那么a
引用的对象(作为相同的对象)也会发生变化
- 如果
- 如果
b
是不可变的并且在内存中很小,在某些情况下,编译器会创建一个新对象并将其绑定到a
,但是对于用户来说是不可变的,这种差异不会明显 对于许多高级语言,我们不需要显式地担心内存泄漏。垃圾收集器的存在是为了自动销毁不再可访问的对象。
a = copy(b)
- 创建一个新的,"独立的";复制对象并将其绑定到
a
。然而,这个新对象可以通过内存地址依次引用其他对象。在这种情况下,复制的是它们的内存地址,而不是被引用的对象本身。 - 的结果是:
- 如果这些引用对象(例如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 == b
和a === a
为真,但a === b
为假; - 给定
a = (1, 2); b = (1, 2);
,所有a == b, a === a
和a === b
为真。