Kar*_*ett 5 arrays invalidation swift
下面的代码没有崩溃,但鉴于"有限"的文档可用,我无法解释原因.
func foo(inout a: [Int], inout b: Int) {
a = []
b = 99
}
var arr = [1,2,3]
// confusion: where does the "out" of b go to?
// storage for a was already cleared / invalidated
foo(&arr, b: &arr[2])
print(arr) // arr is empty
Run Code Online (Sandbox Code Playgroud)
我相信这就是正在发生的事情。当你分配时
a = []
Run Code Online (Sandbox Code Playgroud)
你指向a一个新数组。原始数组仍然存在于内存中,当您这样做时:
b = 99
Run Code Online (Sandbox Code Playgroud)
您正在修改原始数组,而不是a引用的新数组。
你有什么证据证明情况确实如此?
考虑对您的实验进行这样的修改:
情况1:
func foo(inout a: [Int], inout b: Int) {
a[0] = 4
a[1] = 5
a[2] = 6
b = 99
}
var arr = [1,2,3]
foo(&arr, b: &arr[2])
print(arr) // prints "[4, 5, 99]"
Run Code Online (Sandbox Code Playgroud)
现在考虑一下这个:
案例2:
func foo(inout a: [Int], inout b: Int) {
a = [4, 5, 6]
b = 99
}
var arr = [1,2,3]
foo(&arr, b: &arr[2])
print(arr) // prints "[4, 5, 6]"
Run Code Online (Sandbox Code Playgroud)
显然,修改 的各个元素与a将数组分配给 不同a。
在案例 1 中,我们修改了原始数组,将元素变为4、5、 和6,并且 的赋值按预期b更改a[2]。
在情况 2 中,我们分配[4, 5, 6]给awhich 并没有将原始值更改为4、5、 和6,而是指向a一个新数组。在这种情况下, 的分配b不会改变a[2],因为a现在指向内存中不同位置的新数组。
案例3:
func foo(inout a: [Int], inout b: Int) {
let a1 = a
a = [4, 5, 6]
b = 99
print(a) // prints "[4, 5, 6]"
print(a1) // prints "[1, 2, 99]"
}
var arr = [1,2,3]
foo(&arr, b: &arr[2])
print(arr) // prints "[4, 5, 6]"
Run Code Online (Sandbox Code Playgroud)
在情况 3 中,我们可以在将a1新数组分配给 之前将原始数组分配给a。这为我们提供了原始数组的名称。当b被分配时,a1[2]被修改。
来自评论:
您的答案解释了为什么函数内对 b 的赋值有效。然而,当 foo 结束并将 inout 变量复制回来时,我不知道 swift 如何知道将原始数组的释放推迟到 &[2] 赋值之后。
这很可能是 ARC 引用计数的结果。原始数组通过引用传递,foo并且引用计数递增。直到引用计数在 末尾递减之前,原始数组才会被释放foo。
它看起来就像文档已经不允许的那样毛茸茸的——将相同的变量传递两次作为 inout 。你的case3也令人惊讶。难道 let a1 = a 行不应该执行结构/值语义并立即复制数组的快照吗?
是的。我同意案例 3 令人惊讶,但它确实揭示了幕后发生的一些事情。通常,当您将一个数组分配给一个新变量时,Swift 不会立即复制一份。相反,它只是将第二个数组指向第一个数组,并且引用计数递增。这样做是为了提高效率。仅当其中一个数组被修改时才需要制作一份副本。在这种情况下,当a被修改时,a1将数组的原始副本保留在内存中。
这确实令人困惑;我不明白为什么 a1 不会得到 1,2,3。而且 let 应该是不可变的!
当设置a1时被修改的事实表明它指向原始数组的内存。Swift 显然不认为设置是修改。也许是因为它在用 调用时已经确定是可变的。ba1ba1afoo&a[2]