我正在阅读文档,我经常对语言的一些设计决策摇头。但真正让我困惑的是数组是如何处理的。

我冲到操场上试了试。你也可以试试。第一个例子:

var a = [1, 2, 3]
var b = a
a[1] = 42
a
b

这里a和b都是[1,42,3],我可以接受。数组被引用-好的!

现在来看这个例子:

var c = [1, 2, 3]
var d = c
c.append(42)
c
d

c是[1,2,3,42]但d是[1,2,3]。也就是说,d在上一个例子中看到了变化,但在这个例子中没有看到。文档上说这是因为长度变了。

现在,看看这个:

var e = [1, 2, 3]
var f = e
e[0..2] = [4, 5]
e
f

E是[4,5,3],很酷。有一个多索引替换是很好的,但是f STILL没有看到变化,即使长度没有改变。

总而言之,如果更改了一个元素,对数组的常见引用就会看到变化,但如果更改了多个元素或附加项,则会生成一个副本。

在我看来,这是一个非常糟糕的设计。我这样想对吗?有什么原因让我不明白数组为什么应该这样做吗?

编辑:数组已经改变,现在有值语义。理智多了!


当前回答

为此我使用.copy()。

    var a = [1, 2, 3]
    var b = a.copy()
     a[1] = 42 

其他回答

注意,数组的语义和语法在Xcode beta 3版本中发生了改变,所以这个问题不再适用。以下答案适用于beta 2:


这是出于性能考虑。基本上,他们尽量避免复制数组(并声称“类似c的性能”)。引用语言书:

对于数组,只有在执行可能修改数组长度的操作时才会发生复制。这包括追加、插入或删除项,或使用范围下标替换数组中的一段项。

我同意这有点令人困惑,但至少对它的工作原理有一个清晰而简单的描述。

该部分还包括有关如何确保数组被唯一引用、如何强制复制数组以及如何检查两个数组是否共享存储的信息。

我发现:当且仅当操作有可能改变数组的长度时,该数组将是引用数组的可变副本。在上一个例子中,f[0..2]如果索引数量很多,操作就有可能改变它的长度(可能是不允许重复的),所以它会被复制。

var e = [1, 2, 3]
var f = e
e[0..2] = [4, 5]
e // 4,5,3
f // 1,2,3


var e1 = [1, 2, 3]
var f1 = e1

e1[0] = 4
e1[1] = 5

e1 //  - 4,5,3
f1 // - 4,5,3

Delphi的字符串和数组具有完全相同的“特征”。当您查看实现时,它是有意义的。

每个变量都是指向动态内存的指针。该内存包含一个引用计数,后面跟着数组中的数据。因此,您可以轻松地更改数组中的值,而无需复制整个数组或更改任何指针。如果要调整数组的大小,则必须分配更多内存。在这种情况下,当前变量将指向新分配的内存。但是你不能轻易地追踪到所有指向原始数组的其他变量,所以你就不用管它们了。

当然,要实现更加一致的实现并不难。如果你想让所有变量都看到一个大小调整,这样做: 每个变量都是一个指向动态内存中存储的容器的指针。容器只保存两个东西,一个引用计数和指向实际数组数据的指针。数组数据存储在一个独立的动态内存块中。现在只有一个指向数组数据的指针,所以你可以很容易地调整它的大小,所有变量都会看到变化。

来自Swift语言的官方文档:

请注意,使用下标语法设置新值时不会复制数组,因为使用下标语法设置单个值不会改变数组的长度。但是,如果将新项附加到数组,则会修改数组的长度。这会提示Swift在你附加新值的时候创建一个新的数组副本。因此,a是数组.....的一个单独的独立副本

阅读本文档中数组的赋值和复制行为的整个部分。您将发现,当您替换数组中的一组项时,数组将为所有项获取自身的副本。

许多Swift的早期采用者抱怨这种容易出错的数组语义,Chris Lattner曾写道,数组语义已经被修改为提供全值语义(有账号的人可以链接Apple Developer)。我们至少要等到下一个测试版才能知道这到底意味着什么。