不确定这个inout场景是如何安全的,因为数组在返回时会失效

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)

vac*_*ama 2

我相信这就是正在发生的事情。当你分配时

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 中,我们修改了原始数组,将元素变为45、 和6,并且 的赋值按预期b更改a[2]

在情况 2 中,我们分配[4, 5, 6]awhich 并没有将原始值更改为45、 和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]