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

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

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没有看到变化,即使长度没有改变。

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

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

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


当前回答

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

其他回答

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

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

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

我发现:当且仅当操作有可能改变数组的长度时,该数组将是引用数组的可变副本。在上一个例子中,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

对我来说,如果你先用变量替换常量,这更有意义:

a[i] = 42            // (1)
e[i..j] = [4, 5]     // (2)

第一行永远不需要改变a的大小,特别是,它永远不需要做任何内存分配。不管i的值是多少,这都是一个轻量级操作。如果你把a想象成一个指针,它可以是一个常量指针。

第二行可能要复杂得多。根据i和j的值,您可能需要进行内存管理。如果你想象e是一个指向数组内容的指针,你不能再假设它是一个常量指针;您可能需要分配一个新的内存块,将数据从旧的内存块复制到新的内存块,并更改指针。

看起来语言设计者们已经试图保持(1)尽可能的轻量级。因为(2)可能涉及复制,他们已经采取了解决方案,它总是表现得好像你做了一个复制。

这很复杂,但我很高兴他们没有让它变得更复杂,例如,特殊情况下,如“如果在(2)I和j是编译时常数,编译器可以推断出e的大小不会改变,那么我们不复制”。


最后,基于我对Swift语言设计原则的理解,我认为一般规则如下:

默认情况下,在所有地方都使用常量(let) always,这样就不会有什么大的意外了。 只有在绝对必要的情况下才使用变量(var),并在这些情况下要小心,因为会出现令人惊讶的情况[这里:在某些情况下,但不是所有情况下,数组的奇怪隐式副本]。

这种行为在Xcode 6 beta 3中有所改变。数组不再是引用类型,而是采用写时复制机制,这意味着只要从一个或另一个变量中更改数组的内容,数组就会被复制,而且只会更改一个副本。


旧的回答:

正如其他人指出的那样,Swift尽量避免复制数组,包括在一次更改单个索引的值时。

如果你想确保一个数组变量(!)是唯一的,即不与另一个变量共享,你可以调用unshare方法。这将复制数组,除非它已经只有一个引用。当然,您也可以调用copy方法,它将始终进行复制,但首选unshare以确保没有其他变量保留同一数组。

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

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


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

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

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

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